XPathDocumentNavigator.cs 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949
  1. //------------------------------------------------------------------------------
  2. // <copyright file="XPathDocumentNavigator.cs" company="Microsoft">
  3. // Copyright (c) Microsoft Corporation. All rights reserved.
  4. // </copyright>
  5. // <owner current="true" primary="true">Microsoft</owner>
  6. //------------------------------------------------------------------------------
  7. using System;
  8. using System.IO;
  9. using System.Collections;
  10. using System.Globalization;
  11. using System.Text;
  12. using System.Diagnostics;
  13. using System.Xml;
  14. using System.Xml.XPath;
  15. using System.Xml.Schema;
  16. namespace MS.Internal.Xml.Cache {
  17. /// <summary>
  18. /// This is the default XPath/XQuery data model cache implementation. It will be used whenever
  19. /// the user does not supply his own XPathNavigator implementation.
  20. /// </summary>
  21. internal sealed class XPathDocumentNavigator : XPathNavigator, IXmlLineInfo {
  22. private XPathNode[] pageCurrent;
  23. private XPathNode[] pageParent;
  24. private int idxCurrent;
  25. private int idxParent;
  26. private string atomizedLocalName;
  27. //-----------------------------------------------
  28. // Constructors
  29. //-----------------------------------------------
  30. /// <summary>
  31. /// Create a new navigator positioned on the specified current node. If the current node is a namespace or a collapsed
  32. /// text node, then the parent is a virtualized parent (may be different than .Parent on the current node).
  33. /// </summary>
  34. public XPathDocumentNavigator(XPathNode[] pageCurrent, int idxCurrent, XPathNode[] pageParent, int idxParent) {
  35. Debug.Assert(pageCurrent != null && idxCurrent != 0);
  36. Debug.Assert((pageParent == null) == (idxParent == 0));
  37. this.pageCurrent = pageCurrent;
  38. this.pageParent = pageParent;
  39. this.idxCurrent = idxCurrent;
  40. this.idxParent = idxParent;
  41. }
  42. /// <summary>
  43. /// Copy constructor.
  44. /// </summary>
  45. public XPathDocumentNavigator(XPathDocumentNavigator nav) : this(nav.pageCurrent, nav.idxCurrent, nav.pageParent, nav.idxParent) {
  46. this.atomizedLocalName = nav.atomizedLocalName;
  47. }
  48. //-----------------------------------------------
  49. // XPathItem
  50. //-----------------------------------------------
  51. /// <summary>
  52. /// Get the string value of the current node, computed using data model dm:string-value rules.
  53. /// If the node has a typed value, return the string representation of the value. If the node
  54. /// is not a parent type (comment, text, pi, etc.), get its simple text value. Otherwise,
  55. /// concatenate all text node descendants of the current node.
  56. /// </summary>
  57. public override string Value {
  58. get {
  59. string value;
  60. XPathNode[] page, pageEnd;
  61. int idx, idxEnd;
  62. // Try to get the pre-computed string value of the node
  63. value = this.pageCurrent[this.idxCurrent].Value;
  64. if (value != null)
  65. return value;
  66. #if DEBUG
  67. switch (this.pageCurrent[this.idxCurrent].NodeType) {
  68. case XPathNodeType.Namespace:
  69. case XPathNodeType.Attribute:
  70. case XPathNodeType.Comment:
  71. case XPathNodeType.ProcessingInstruction:
  72. Debug.Assert(false, "ReadStringValue() should have taken care of these node types.");
  73. break;
  74. case XPathNodeType.Text:
  75. Debug.Assert(this.idxParent != 0 && this.pageParent[this.idxParent].HasCollapsedText,
  76. "ReadStringValue() should have taken care of anything but collapsed text.");
  77. break;
  78. }
  79. #endif
  80. // If current node is collapsed text, then parent element has a simple text value
  81. if (this.idxParent != 0) {
  82. Debug.Assert(this.pageCurrent[this.idxCurrent].NodeType == XPathNodeType.Text);
  83. return this.pageParent[this.idxParent].Value;
  84. }
  85. // Must be node with complex content, so concatenate the string values of all text descendants
  86. string s = string.Empty;
  87. StringBuilder bldr = null;
  88. // Get all text nodes which follow the current node in document order, but which are still descendants
  89. page = pageEnd = this.pageCurrent;
  90. idx = idxEnd = this.idxCurrent;
  91. if (!XPathNodeHelper.GetNonDescendant(ref pageEnd, ref idxEnd)) {
  92. pageEnd = null;
  93. idxEnd = 0;
  94. }
  95. while (XPathNodeHelper.GetTextFollowing(ref page, ref idx, pageEnd, idxEnd)) {
  96. Debug.Assert(page[idx].NodeType == XPathNodeType.Element || page[idx].IsText);
  97. if (s.Length == 0) {
  98. s = page[idx].Value;
  99. }
  100. else {
  101. if (bldr == null) {
  102. bldr = new StringBuilder();
  103. bldr.Append(s);
  104. }
  105. bldr.Append(page[idx].Value);
  106. }
  107. }
  108. return (bldr != null) ? bldr.ToString() : s;
  109. }
  110. }
  111. //-----------------------------------------------
  112. // XPathNavigator
  113. //-----------------------------------------------
  114. /// <summary>
  115. /// Create a copy of this navigator, positioned to the same node in the tree.
  116. /// </summary>
  117. public override XPathNavigator Clone() {
  118. return new XPathDocumentNavigator(this.pageCurrent, this.idxCurrent, this.pageParent, this.idxParent);
  119. }
  120. /// <summary>
  121. /// Get the XPath node type of the current node.
  122. /// </summary>
  123. public override XPathNodeType NodeType {
  124. get { return this.pageCurrent[this.idxCurrent].NodeType; }
  125. }
  126. /// <summary>
  127. /// Get the local name portion of the current node's name.
  128. /// </summary>
  129. public override string LocalName {
  130. get { return this.pageCurrent[this.idxCurrent].LocalName; }
  131. }
  132. /// <summary>
  133. /// Get the namespace portion of the current node's name.
  134. /// </summary>
  135. public override string NamespaceURI {
  136. get { return this.pageCurrent[this.idxCurrent].NamespaceUri; }
  137. }
  138. /// <summary>
  139. /// Get the name of the current node.
  140. /// </summary>
  141. public override string Name {
  142. get { return this.pageCurrent[this.idxCurrent].Name; }
  143. }
  144. /// <summary>
  145. /// Get the prefix portion of the current node's name.
  146. /// </summary>
  147. public override string Prefix {
  148. get { return this.pageCurrent[this.idxCurrent].Prefix; }
  149. }
  150. /// <summary>
  151. /// Get the base URI of the current node.
  152. /// </summary>
  153. public override string BaseURI {
  154. get {
  155. XPathNode[] page;
  156. int idx;
  157. if (this.idxParent != 0) {
  158. // Get BaseUri of parent for attribute, namespace, and collapsed text nodes
  159. page = this.pageParent;
  160. idx = this.idxParent;
  161. }
  162. else {
  163. page = this.pageCurrent;
  164. idx = this.idxCurrent;
  165. }
  166. do {
  167. switch (page[idx].NodeType) {
  168. case XPathNodeType.Element:
  169. case XPathNodeType.Root:
  170. case XPathNodeType.ProcessingInstruction:
  171. // BaseUri is always stored with Elements, Roots, and PIs
  172. return page[idx].BaseUri;
  173. }
  174. // Get BaseUri of parent
  175. idx = page[idx].GetParent(out page);
  176. }
  177. while (idx != 0);
  178. return string.Empty;
  179. }
  180. }
  181. /// <summary>
  182. /// Return true if this is an element which used a shortcut tag in its Xml 1.0 serialized form.
  183. /// </summary>
  184. public override bool IsEmptyElement {
  185. get { return this.pageCurrent[this.idxCurrent].AllowShortcutTag; }
  186. }
  187. /// <summary>
  188. /// Return the xml name table which was used to atomize all prefixes, local-names, and
  189. /// namespace uris in the document.
  190. /// </summary>
  191. public override XmlNameTable NameTable {
  192. get { return this.pageCurrent[this.idxCurrent].Document.NameTable; }
  193. }
  194. /// <summary>
  195. /// Position the navigator on the first attribute of the current node and return true. If no attributes
  196. /// can be found, return false.
  197. /// </summary>
  198. public override bool MoveToFirstAttribute() {
  199. XPathNode[] page = this.pageCurrent;
  200. int idx = this.idxCurrent;
  201. if (XPathNodeHelper.GetFirstAttribute(ref this.pageCurrent, ref this.idxCurrent)) {
  202. // Save element parent in order to make node-order comparison simpler
  203. this.pageParent = page;
  204. this.idxParent = idx;
  205. return true;
  206. }
  207. return false;
  208. }
  209. /// <summary>
  210. /// If positioned on an attribute, move to its next sibling attribute. If no attributes can be found,
  211. /// return false.
  212. /// </summary>
  213. public override bool MoveToNextAttribute() {
  214. return XPathNodeHelper.GetNextAttribute(ref this.pageCurrent, ref this.idxCurrent);
  215. }
  216. /// <summary>
  217. /// True if the current node has one or more attributes.
  218. /// </summary>
  219. public override bool HasAttributes {
  220. get { return this.pageCurrent[this.idxCurrent].HasAttribute; }
  221. }
  222. /// <summary>
  223. /// Position the navigator on the attribute with the specified name and return true. If no matching
  224. /// attribute can be found, return false. Don't assume the name parts are atomized with respect
  225. /// to this document.
  226. /// </summary>
  227. public override bool MoveToAttribute(string localName, string namespaceURI) {
  228. XPathNode[] page = this.pageCurrent;
  229. int idx = this.idxCurrent;
  230. if ((object) localName != (object) this.atomizedLocalName)
  231. this.atomizedLocalName = (localName != null) ? NameTable.Get(localName) : null;
  232. if (XPathNodeHelper.GetAttribute(ref this.pageCurrent, ref this.idxCurrent, this.atomizedLocalName, namespaceURI)) {
  233. // Save element parent in order to make node-order comparison simpler
  234. this.pageParent = page;
  235. this.idxParent = idx;
  236. return true;
  237. }
  238. return false;
  239. }
  240. /// <summary>
  241. /// Position the navigator on the namespace within the specified scope. If no matching namespace
  242. /// can be found, return false.
  243. /// </summary>
  244. public override bool MoveToFirstNamespace(XPathNamespaceScope namespaceScope) {
  245. XPathNode[] page;
  246. int idx;
  247. if (namespaceScope == XPathNamespaceScope.Local) {
  248. // Get local namespaces only
  249. idx = XPathNodeHelper.GetLocalNamespaces(this.pageCurrent, this.idxCurrent, out page);
  250. }
  251. else {
  252. // Get all in-scope namespaces
  253. idx = XPathNodeHelper.GetInScopeNamespaces(this.pageCurrent, this.idxCurrent, out page);
  254. }
  255. while (idx != 0) {
  256. // Don't include the xmlns:xml namespace node if scope is ExcludeXml
  257. if (namespaceScope != XPathNamespaceScope.ExcludeXml || !page[idx].IsXmlNamespaceNode) {
  258. this.pageParent = this.pageCurrent;
  259. this.idxParent = this.idxCurrent;
  260. this.pageCurrent = page;
  261. this.idxCurrent = idx;
  262. return true;
  263. }
  264. // Skip past xmlns:xml
  265. idx = page[idx].GetSibling(out page);
  266. }
  267. return false;
  268. }
  269. /// <summary>
  270. /// Position the navigator on the next namespace within the specified scope. If no matching namespace
  271. /// can be found, return false.
  272. /// </summary>
  273. public override bool MoveToNextNamespace(XPathNamespaceScope scope) {
  274. XPathNode[] page = this.pageCurrent, pageParent;
  275. int idx = this.idxCurrent, idxParent;
  276. // If current node is not a namespace node, return false
  277. if (page[idx].NodeType != XPathNodeType.Namespace)
  278. return false;
  279. while (true) {
  280. // Get next namespace sibling
  281. idx = page[idx].GetSibling(out page);
  282. // If there are no more nodes, return false
  283. if (idx == 0)
  284. return false;
  285. switch (scope) {
  286. case XPathNamespaceScope.Local:
  287. // Once parent changes, there are no longer any local namespaces
  288. idxParent = page[idx].GetParent(out pageParent);
  289. if (idxParent != this.idxParent || (object) pageParent != (object) this.pageParent)
  290. return false;
  291. break;
  292. case XPathNamespaceScope.ExcludeXml:
  293. // If node is xmlns:xml, then skip it
  294. if (page[idx].IsXmlNamespaceNode)
  295. continue;
  296. break;
  297. }
  298. // Found a matching next namespace node, so return it
  299. break;
  300. }
  301. this.pageCurrent = page;
  302. this.idxCurrent = idx;
  303. return true;
  304. }
  305. /// <summary>
  306. /// If the current node is an attribute or namespace (not content), return false. Otherwise,
  307. /// move to the next content node. Return false if there are no more content nodes.
  308. /// </summary>
  309. public override bool MoveToNext() {
  310. return XPathNodeHelper.GetContentSibling(ref this.pageCurrent, ref this.idxCurrent);
  311. }
  312. /// <summary>
  313. /// If the current node is an attribute or namespace (not content), return false. Otherwise,
  314. /// move to the previous (sibling) content node. Return false if there are no previous content nodes.
  315. /// </summary>
  316. public override bool MoveToPrevious() {
  317. // If parent exists, then this is a namespace, an attribute, or a collapsed text node, all of which do
  318. // not have previous siblings.
  319. if (this.idxParent != 0)
  320. return false;
  321. return XPathNodeHelper.GetPreviousContentSibling(ref this.pageCurrent, ref this.idxCurrent);
  322. }
  323. /// <summary>
  324. /// Move to the first content-typed child of the current node. Return false if the current
  325. /// node has no content children.
  326. /// </summary>
  327. public override bool MoveToFirstChild() {
  328. if (this.pageCurrent[this.idxCurrent].HasCollapsedText) {
  329. // Virtualize collapsed text nodes
  330. this.pageParent = this.pageCurrent;
  331. this.idxParent = this.idxCurrent;
  332. this.idxCurrent = this.pageCurrent[this.idxCurrent].Document.GetCollapsedTextNode(out this.pageCurrent);
  333. return true;
  334. }
  335. return XPathNodeHelper.GetContentChild(ref this.pageCurrent, ref this.idxCurrent);
  336. }
  337. /// <summary>
  338. /// Position the navigator on the parent of the current node. If the current node has no parent,
  339. /// return false.
  340. /// </summary>
  341. public override bool MoveToParent() {
  342. if (this.idxParent != 0) {
  343. // 1. For attribute nodes, element parent is always stored in order to make node-order
  344. // comparison simpler.
  345. // 2. For namespace nodes, parent is always stored in navigator in order to virtualize
  346. // XPath 1.0 namespaces.
  347. // 3. For collapsed text nodes, element parent is always stored in navigator.
  348. Debug.Assert(this.pageParent != null);
  349. this.pageCurrent = this.pageParent;
  350. this.idxCurrent = this.idxParent;
  351. this.pageParent = null;
  352. this.idxParent = 0;
  353. return true;
  354. }
  355. return XPathNodeHelper.GetParent(ref this.pageCurrent, ref this.idxCurrent);
  356. }
  357. /// <summary>
  358. /// Position this navigator to the same position as the "other" navigator. If the "other" navigator
  359. /// is not of the same type as this navigator, then return false.
  360. /// </summary>
  361. public override bool MoveTo(XPathNavigator other) {
  362. XPathDocumentNavigator that = other as XPathDocumentNavigator;
  363. if (that != null) {
  364. this.pageCurrent = that.pageCurrent;
  365. this.idxCurrent = that.idxCurrent;
  366. this.pageParent = that.pageParent;
  367. this.idxParent = that.idxParent;
  368. return true;
  369. }
  370. return false;
  371. }
  372. /// <summary>
  373. /// Position to the navigator to the element whose id is equal to the specified "id" string.
  374. /// </summary>
  375. public override bool MoveToId(string id) {
  376. XPathNode[] page;
  377. int idx;
  378. idx = this.pageCurrent[this.idxCurrent].Document.LookupIdElement(id, out page);
  379. if (idx != 0) {
  380. // Move to ID element and clear parent state
  381. Debug.Assert(page[idx].NodeType == XPathNodeType.Element);
  382. this.pageCurrent = page;
  383. this.idxCurrent = idx;
  384. this.pageParent = null;
  385. this.idxParent = 0;
  386. return true;
  387. }
  388. return false;
  389. }
  390. /// <summary>
  391. /// Returns true if this navigator is positioned to the same node as the "other" navigator. Returns false
  392. /// if not, or if the "other" navigator is not the same type as this navigator.
  393. /// </summary>
  394. public override bool IsSamePosition(XPathNavigator other) {
  395. XPathDocumentNavigator that = other as XPathDocumentNavigator;
  396. if (that != null) {
  397. return this.idxCurrent == that.idxCurrent && this.pageCurrent == that.pageCurrent &&
  398. this.idxParent == that.idxParent && this.pageParent == that.pageParent;
  399. }
  400. return false;
  401. }
  402. /// <summary>
  403. /// Returns true if the current node has children.
  404. /// </summary>
  405. public override bool HasChildren {
  406. get { return this.pageCurrent[this.idxCurrent].HasContentChild; }
  407. }
  408. /// <summary>
  409. /// Position the navigator on the root node of the current document.
  410. /// </summary>
  411. public override void MoveToRoot() {
  412. if (this.idxParent != 0) {
  413. // Clear parent state
  414. this.pageParent = null;
  415. this.idxParent = 0;
  416. }
  417. this.idxCurrent = this.pageCurrent[this.idxCurrent].GetRoot(out this.pageCurrent);
  418. }
  419. /// <summary>
  420. /// Move to the first element child of the current node with the specified name. Return false
  421. /// if the current node has no matching element children.
  422. /// </summary>
  423. public override bool MoveToChild(string localName, string namespaceURI) {
  424. if ((object) localName != (object) this.atomizedLocalName)
  425. this.atomizedLocalName = (localName != null) ? NameTable.Get(localName) : null;
  426. return XPathNodeHelper.GetElementChild(ref this.pageCurrent, ref this.idxCurrent, this.atomizedLocalName, namespaceURI);
  427. }
  428. /// <summary>
  429. /// Move to the first element sibling of the current node with the specified name. Return false
  430. /// if the current node has no matching element siblings.
  431. /// </summary>
  432. public override bool MoveToNext(string localName, string namespaceURI) {
  433. if ((object) localName != (object) this.atomizedLocalName)
  434. this.atomizedLocalName = (localName != null) ? NameTable.Get(localName) : null;
  435. return XPathNodeHelper.GetElementSibling(ref this.pageCurrent, ref this.idxCurrent, this.atomizedLocalName, namespaceURI);
  436. }
  437. /// <summary>
  438. /// Move to the first content child of the current node with the specified type. Return false
  439. /// if the current node has no matching children.
  440. /// </summary>
  441. public override bool MoveToChild(XPathNodeType type) {
  442. if (this.pageCurrent[this.idxCurrent].HasCollapsedText) {
  443. // Only XPathNodeType.Text and XPathNodeType.All matches collapsed text node
  444. if (type != XPathNodeType.Text && type != XPathNodeType.All)
  445. return false;
  446. // Virtualize collapsed text nodes
  447. this.pageParent = this.pageCurrent;
  448. this.idxParent = this.idxCurrent;
  449. this.idxCurrent = this.pageCurrent[this.idxCurrent].Document.GetCollapsedTextNode(out this.pageCurrent);
  450. return true;
  451. }
  452. return XPathNodeHelper.GetContentChild(ref this.pageCurrent, ref this.idxCurrent, type);
  453. }
  454. /// <summary>
  455. /// Move to the first content sibling of the current node with the specified type. Return false
  456. /// if the current node has no matching siblings.
  457. /// </summary>
  458. public override bool MoveToNext(XPathNodeType type) {
  459. return XPathNodeHelper.GetContentSibling(ref this.pageCurrent, ref this.idxCurrent, type);
  460. }
  461. /// <summary>
  462. /// Move to the next element that:
  463. /// 1. Follows the current node in document order (includes descendants, unlike XPath following axis)
  464. /// 2. Precedes "end" in document order (if end is null, then all following nodes in the document are considered)
  465. /// 3. Has the specified QName
  466. /// Return false if the current node has no matching following elements.
  467. /// </summary>
  468. public override bool MoveToFollowing(string localName, string namespaceURI, XPathNavigator end) {
  469. XPathNode[] pageEnd;
  470. int idxEnd;
  471. if ((object) localName != (object) this.atomizedLocalName)
  472. this.atomizedLocalName = (localName != null) ? NameTable.Get(localName) : null;
  473. // Get node on which scan ends (null if rest of document should be scanned)
  474. idxEnd = GetFollowingEnd(end as XPathDocumentNavigator, false, out pageEnd);
  475. // If this navigator is positioned on a virtual node, then compute following of parent
  476. if (this.idxParent != 0) {
  477. if (!XPathNodeHelper.GetElementFollowing(ref this.pageParent, ref this.idxParent, pageEnd, idxEnd, this.atomizedLocalName, namespaceURI))
  478. return false;
  479. this.pageCurrent = this.pageParent;
  480. this.idxCurrent = this.idxParent;
  481. this.pageParent = null;
  482. this.idxParent = 0;
  483. return true;
  484. }
  485. return XPathNodeHelper.GetElementFollowing(ref this.pageCurrent, ref this.idxCurrent, pageEnd, idxEnd, this.atomizedLocalName, namespaceURI);
  486. }
  487. /// <summary>
  488. /// Move to the next node that:
  489. /// 1. Follows the current node in document order (includes descendants, unlike XPath following axis)
  490. /// 2. Precedes "end" in document order (if end is null, then all following nodes in the document are considered)
  491. /// 3. Has the specified XPathNodeType
  492. /// Return false if the current node has no matching following nodes.
  493. /// </summary>
  494. public override bool MoveToFollowing(XPathNodeType type, XPathNavigator end) {
  495. XPathDocumentNavigator endTiny = end as XPathDocumentNavigator;
  496. XPathNode[] page, pageEnd;
  497. int idx, idxEnd;
  498. // If searching for text, make sure to handle collapsed text nodes correctly
  499. if (type == XPathNodeType.Text || type == XPathNodeType.All) {
  500. if (this.pageCurrent[this.idxCurrent].HasCollapsedText) {
  501. // Positioned on an element with collapsed text, so return the virtual text node, assuming it's before "end"
  502. if (endTiny != null && this.idxCurrent == endTiny.idxParent && this.pageCurrent == endTiny.pageParent) {
  503. // "end" is positioned to a virtual attribute, namespace, or text node
  504. return false;
  505. }
  506. this.pageParent = this.pageCurrent;
  507. this.idxParent = this.idxCurrent;
  508. this.idxCurrent = this.pageCurrent[this.idxCurrent].Document.GetCollapsedTextNode(out this.pageCurrent);
  509. return true;
  510. }
  511. if (type == XPathNodeType.Text) {
  512. // Get node on which scan ends (null if rest of document should be scanned, parent if positioned on virtual node)
  513. idxEnd = GetFollowingEnd(endTiny, true, out pageEnd);
  514. // If this navigator is positioned on a virtual node, then compute following of parent
  515. if (this.idxParent != 0) {
  516. page = this.pageParent;
  517. idx = this.idxParent;
  518. }
  519. else {
  520. page = this.pageCurrent;
  521. idx = this.idxCurrent;
  522. }
  523. // If ending node is a virtual node, and current node is its parent, then we're done
  524. if (endTiny != null && endTiny.idxParent != 0 && idx == idxEnd && page == pageEnd)
  525. return false;
  526. // Get all virtual (collapsed) and physical text nodes which follow the current node
  527. if (!XPathNodeHelper.GetTextFollowing(ref page, ref idx, pageEnd, idxEnd))
  528. return false;
  529. if (page[idx].NodeType == XPathNodeType.Element) {
  530. // Virtualize collapsed text nodes
  531. Debug.Assert(page[idx].HasCollapsedText);
  532. this.idxCurrent = page[idx].Document.GetCollapsedTextNode(out this.pageCurrent);
  533. this.pageParent = page;
  534. this.idxParent = idx;
  535. }
  536. else {
  537. // Physical text node
  538. Debug.Assert(page[idx].IsText);
  539. this.pageCurrent = page;
  540. this.idxCurrent = idx;
  541. this.pageParent = null;
  542. this.idxParent = 0;
  543. }
  544. return true;
  545. }
  546. }
  547. // Get node on which scan ends (null if rest of document should be scanned, parent + 1 if positioned on virtual node)
  548. idxEnd = GetFollowingEnd(endTiny, false, out pageEnd);
  549. // If this navigator is positioned on a virtual node, then compute following of parent
  550. if (this.idxParent != 0) {
  551. if (!XPathNodeHelper.GetContentFollowing(ref this.pageParent, ref this.idxParent, pageEnd, idxEnd, type))
  552. return false;
  553. this.pageCurrent = this.pageParent;
  554. this.idxCurrent = this.idxParent;
  555. this.pageParent = null;
  556. this.idxParent = 0;
  557. return true;
  558. }
  559. return XPathNodeHelper.GetContentFollowing(ref this.pageCurrent, ref this.idxCurrent, pageEnd, idxEnd, type);
  560. }
  561. /// <summary>
  562. /// Return an iterator that ranges over all children of the current node that match the specified XPathNodeType.
  563. /// </summary>
  564. public override XPathNodeIterator SelectChildren(XPathNodeType type) {
  565. return new XPathDocumentKindChildIterator(this, type);
  566. }
  567. /// <summary>
  568. /// Return an iterator that ranges over all children of the current node that match the specified QName.
  569. /// </summary>
  570. public override XPathNodeIterator SelectChildren(string name, string namespaceURI) {
  571. // If local name is wildcard, then call XPathNavigator.SelectChildren
  572. if (name == null || name.Length == 0)
  573. return base.SelectChildren(name, namespaceURI);
  574. return new XPathDocumentElementChildIterator(this, name, namespaceURI);
  575. }
  576. /// <summary>
  577. /// Return an iterator that ranges over all descendants of the current node that match the specified
  578. /// XPathNodeType. If matchSelf is true, then also perform the match on the current node.
  579. /// </summary>
  580. public override XPathNodeIterator SelectDescendants(XPathNodeType type, bool matchSelf) {
  581. return new XPathDocumentKindDescendantIterator(this, type, matchSelf);
  582. }
  583. /// <summary>
  584. /// Return an iterator that ranges over all descendants of the current node that match the specified
  585. /// QName. If matchSelf is true, then also perform the match on the current node.
  586. /// </summary>
  587. public override XPathNodeIterator SelectDescendants(string name, string namespaceURI, bool matchSelf) {
  588. // If local name is wildcard, then call XPathNavigator.SelectDescendants
  589. if (name == null || name.Length == 0)
  590. return base.SelectDescendants(name, namespaceURI, matchSelf);
  591. return new XPathDocumentElementDescendantIterator(this, name, namespaceURI, matchSelf);
  592. }
  593. /// <summary>
  594. /// Returns:
  595. /// XmlNodeOrder.Unknown -- This navigator and the "other" navigator are not of the same type, or the
  596. /// navigator's are not positioned on nodes in the same document.
  597. /// XmlNodeOrder.Before -- This navigator's current node is before the "other" navigator's current node
  598. /// in document order.
  599. /// XmlNodeOrder.After -- This navigator's current node is after the "other" navigator's current node
  600. /// in document order.
  601. /// XmlNodeOrder.Same -- This navigator is positioned on the same node as the "other" navigator.
  602. /// </summary>
  603. public override XmlNodeOrder ComparePosition(XPathNavigator other) {
  604. XPathDocumentNavigator that = other as XPathDocumentNavigator;
  605. if (that != null) {
  606. XPathDocument thisDoc = this.pageCurrent[this.idxCurrent].Document;
  607. XPathDocument thatDoc = that.pageCurrent[that.idxCurrent].Document;
  608. if ((object) thisDoc == (object) thatDoc) {
  609. int locThis = GetPrimaryLocation();
  610. int locThat = that.GetPrimaryLocation();
  611. if (locThis == locThat) {
  612. locThis = GetSecondaryLocation();
  613. locThat = that.GetSecondaryLocation();
  614. if (locThis == locThat)
  615. return XmlNodeOrder.Same;
  616. }
  617. return (locThis < locThat) ? XmlNodeOrder.Before : XmlNodeOrder.After;
  618. }
  619. }
  620. return XmlNodeOrder.Unknown;
  621. }
  622. /// <summary>
  623. /// Return true if the "other" navigator's current node is a descendant of this navigator's current node.
  624. /// </summary>
  625. public override bool IsDescendant(XPathNavigator other) {
  626. XPathDocumentNavigator that = other as XPathDocumentNavigator;
  627. if (that != null) {
  628. XPathNode[] pageThat;
  629. int idxThat;
  630. // If that current node's parent is virtualized, then start with the virtual parent
  631. if (that.idxParent != 0) {
  632. pageThat = that.pageParent;
  633. idxThat = that.idxParent;
  634. }
  635. else {
  636. idxThat = that.pageCurrent[that.idxCurrent].GetParent(out pageThat);
  637. }
  638. while (idxThat != 0) {
  639. if (idxThat == this.idxCurrent && pageThat == this.pageCurrent)
  640. return true;
  641. idxThat = pageThat[idxThat].GetParent(out pageThat);
  642. }
  643. }
  644. return false;
  645. }
  646. /// <summary>
  647. /// Construct a primary location for this navigator. The location is an integer that can be
  648. /// easily compared with other locations in the same document in order to determine the relative
  649. /// document order of two nodes. If two locations compare equal, then secondary locations should
  650. /// be compared.
  651. /// </summary>
  652. private int GetPrimaryLocation() {
  653. // Is the current node virtualized?
  654. if (this.idxParent == 0) {
  655. // No, so primary location should be derived from current node
  656. return XPathNodeHelper.GetLocation(this.pageCurrent, this.idxCurrent);
  657. }
  658. // Yes, so primary location should be derived from parent node
  659. return XPathNodeHelper.GetLocation(this.pageParent, this.idxParent);
  660. }
  661. /// <summary>
  662. /// Construct a secondary location for this navigator. This location should only be used if
  663. /// primary locations previously compared equal.
  664. /// </summary>
  665. private int GetSecondaryLocation() {
  666. // Is the current node virtualized?
  667. if (this.idxParent == 0) {
  668. // No, so secondary location is int.MinValue (always first)
  669. return int.MinValue;
  670. }
  671. // Yes, so secondary location should be derived from current node
  672. // This happens with attributes nodes, namespace nodes, collapsed text nodes, and atomic values
  673. switch (this.pageCurrent[this.idxCurrent].NodeType) {
  674. case XPathNodeType.Namespace:
  675. // Namespace nodes come first (make location negative, but greater than int.MinValue)
  676. return int.MinValue + 1 + XPathNodeHelper.GetLocation(this.pageCurrent, this.idxCurrent);
  677. case XPathNodeType.Attribute:
  678. // Attribute nodes come next (location is always positive)
  679. return XPathNodeHelper.GetLocation(this.pageCurrent, this.idxCurrent);
  680. default:
  681. // Collapsed text nodes are always last
  682. return int.MaxValue;
  683. }
  684. }
  685. /// <summary>
  686. /// Create a unique id for the current node. This is used by the generate-id() function.
  687. /// </summary>
  688. internal override string UniqueId {
  689. get {
  690. // 32-bit integer is split into 5-bit groups, the maximum number of groups is 7
  691. char[] buf = new char[1+7+1+7];
  692. int idx = 0;
  693. int loc;
  694. // Ensure distinguishing attributes, namespaces and child nodes
  695. buf[idx++] = NodeTypeLetter[(int)this.pageCurrent[this.idxCurrent].NodeType];
  696. // If the current node is virtualized, code its parent
  697. if (this.idxParent != 0) {
  698. loc = (this.pageParent[0].PageInfo.PageNumber - 1) << 16 | (this.idxParent - 1);
  699. do {
  700. buf[idx++] = UniqueIdTbl[loc & 0x1f];
  701. loc >>= 5;
  702. } while (loc != 0);
  703. buf[idx++] = '0';
  704. }
  705. // Code the node itself
  706. loc = (this.pageCurrent[0].PageInfo.PageNumber - 1) << 16 | (this.idxCurrent - 1);
  707. do {
  708. buf[idx++] = UniqueIdTbl[loc & 0x1f];
  709. loc >>= 5;
  710. } while (loc != 0);
  711. return new string(buf, 0, idx);
  712. }
  713. }
  714. public override object UnderlyingObject {
  715. get {
  716. // Since we don't have any underlying PUBLIC object
  717. // the best one we can return is a clone of the navigator.
  718. // Note that it should be a clone as the user might Move the returned navigator
  719. // around and thus cause unexpected behavior of the caller of this class (For example the validator)
  720. return this.Clone();
  721. }
  722. }
  723. //-----------------------------------------------
  724. // IXmlLineInfo
  725. //-----------------------------------------------
  726. /// <summary>
  727. /// Return true if line number information is recorded in the cache.
  728. /// </summary>
  729. public bool HasLineInfo() {
  730. return this.pageCurrent[this.idxCurrent].Document.HasLineInfo;
  731. }
  732. /// <summary>
  733. /// Return the source line number of the current node.
  734. /// </summary>
  735. public int LineNumber {
  736. get {
  737. // If the current node is a collapsed text node, then return parent element's line number
  738. if (this.idxParent != 0 && NodeType == XPathNodeType.Text)
  739. return this.pageParent[this.idxParent].LineNumber;
  740. return this.pageCurrent[this.idxCurrent].LineNumber;
  741. }
  742. }
  743. /// <summary>
  744. /// Return the source line position of the current node.
  745. /// </summary>
  746. public int LinePosition {
  747. get {
  748. // If the current node is a collapsed text node, then get position from parent element
  749. if (this.idxParent != 0 && NodeType == XPathNodeType.Text)
  750. return this.pageParent[this.idxParent].CollapsedLinePosition;
  751. return this.pageCurrent[this.idxCurrent].LinePosition;
  752. }
  753. }
  754. //-----------------------------------------------
  755. // Helper methods
  756. //-----------------------------------------------
  757. /// <summary>
  758. /// Get hashcode based on current position of the navigator.
  759. /// </summary>
  760. public int GetPositionHashCode() {
  761. return this.idxCurrent ^ this.idxParent;
  762. }
  763. /// <summary>
  764. /// Return true if navigator is positioned to an element having the specified name.
  765. /// </summary>
  766. public bool IsElementMatch(string localName, string namespaceURI) {
  767. if ((object) localName != (object) this.atomizedLocalName)
  768. this.atomizedLocalName = (localName != null) ? NameTable.Get(localName) : null;
  769. // Cannot be an element if parent is stored
  770. if (this.idxParent != 0)
  771. return false;
  772. return this.pageCurrent[this.idxCurrent].ElementMatch(this.atomizedLocalName, namespaceURI);
  773. }
  774. /// <summary>
  775. /// Return true if navigator is positioned to a content node of the specified kind. Whitespace/SignficantWhitespace/Text are
  776. /// all treated the same (i.e. they all match each other).
  777. /// </summary>
  778. public bool IsContentKindMatch(XPathNodeType typ) {
  779. return (((1 << (int) this.pageCurrent[this.idxCurrent].NodeType) & GetContentKindMask(typ)) != 0);
  780. }
  781. /// <summary>
  782. /// Return true if navigator is positioned to a node of the specified kind. Whitespace/SignficantWhitespace/Text are
  783. /// all treated the same (i.e. they all match each other).
  784. /// </summary>
  785. public bool IsKindMatch(XPathNodeType typ) {
  786. return (((1 << (int) this.pageCurrent[this.idxCurrent].NodeType) & GetKindMask(typ)) != 0);
  787. }
  788. /// <summary>
  789. /// "end" is positioned on a node which terminates a following scan. Return the page and index of "end" if it
  790. /// is positioned to a non-virtual node. If "end" is positioned to a virtual node:
  791. /// 1. If useParentOfVirtual is true, then return the page and index of the virtual node's parent
  792. /// 2. If useParentOfVirtual is false, then return the page and index of the virtual node's parent + 1.
  793. /// </summary>
  794. private int GetFollowingEnd(XPathDocumentNavigator end, bool useParentOfVirtual, out XPathNode[] pageEnd) {
  795. // If ending navigator is positioned to a node in another document, then return null
  796. if (end != null && this.pageCurrent[this.idxCurrent].Document == end.pageCurrent[end.idxCurrent].Document) {
  797. // If the ending navigator is not positioned on a virtual node, then return its current node
  798. if (end.idxParent == 0) {
  799. pageEnd = end.pageCurrent;
  800. return end.idxCurrent;
  801. }
  802. // If the ending navigator is positioned on an attribute, namespace, or virtual text node, then use the
  803. // next physical node instead, as the results will be the same.
  804. pageEnd = end.pageParent;
  805. return (useParentOfVirtual) ? end.idxParent : end.idxParent + 1;
  806. }
  807. // No following, so set pageEnd to null and return an index of 0
  808. pageEnd = null;
  809. return 0;
  810. }
  811. }
  812. }