XPathNodeHelper.cs 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682
  1. //------------------------------------------------------------------------------
  2. // <copyright file="XPathNodeHelper.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.Diagnostics;
  9. using System.Text;
  10. using System.Xml;
  11. using System.Xml.XPath;
  12. using System.Xml.Schema;
  13. namespace MS.Internal.Xml.Cache {
  14. /// <summary>
  15. /// Library of XPathNode helper routines.
  16. /// </summary>
  17. internal abstract class XPathNodeHelper {
  18. /// <summary>
  19. /// Return chain of namespace nodes. If specified node has no local namespaces, then 0 will be
  20. /// returned. Otherwise, the first node in the chain is guaranteed to be a local namespace (its
  21. /// parent is this node). Subsequent nodes may not have the same node as parent, so the caller will
  22. /// need to test the parent in order to terminate a search that processes only local namespaces.
  23. /// </summary>
  24. public static int GetLocalNamespaces(XPathNode[] pageElem, int idxElem, out XPathNode[] pageNmsp) {
  25. if (pageElem[idxElem].HasNamespaceDecls) {
  26. // Only elements have namespace nodes
  27. Debug.Assert(pageElem[idxElem].NodeType == XPathNodeType.Element);
  28. return pageElem[idxElem].Document.LookupNamespaces(pageElem, idxElem, out pageNmsp);
  29. }
  30. pageNmsp = null;
  31. return 0;
  32. }
  33. /// <summary>
  34. /// Return chain of in-scope namespace nodes for nodes of type Element. Nodes in the chain might not
  35. /// have this element as their parent. Since the xmlns:xml namespace node is always in scope, this
  36. /// method will never return 0 if the specified node is an element.
  37. /// </summary>
  38. public static int GetInScopeNamespaces(XPathNode[] pageElem, int idxElem, out XPathNode[] pageNmsp) {
  39. XPathDocument doc;
  40. // Only elements have namespace nodes
  41. if (pageElem[idxElem].NodeType == XPathNodeType.Element) {
  42. doc = pageElem[idxElem].Document;
  43. // Walk ancestors, looking for an ancestor that has at least one namespace declaration
  44. while (!pageElem[idxElem].HasNamespaceDecls) {
  45. idxElem = pageElem[idxElem].GetParent(out pageElem);
  46. if (idxElem == 0) {
  47. // There are no namespace nodes declared on ancestors, so return xmlns:xml node
  48. return doc.GetXmlNamespaceNode(out pageNmsp);
  49. }
  50. }
  51. // Return chain of in-scope namespace nodes
  52. return doc.LookupNamespaces(pageElem, idxElem, out pageNmsp);
  53. }
  54. pageNmsp = null;
  55. return 0;
  56. }
  57. /// <summary>
  58. /// Return the first attribute of the specified node. If no attribute exist, do not
  59. /// set pageNode or idxNode and return false.
  60. /// </summary>
  61. public static bool GetFirstAttribute(ref XPathNode[] pageNode, ref int idxNode) {
  62. Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)");
  63. if (pageNode[idxNode].HasAttribute) {
  64. GetChild(ref pageNode, ref idxNode);
  65. Debug.Assert(pageNode[idxNode].NodeType == XPathNodeType.Attribute);
  66. return true;
  67. }
  68. return false;
  69. }
  70. /// <summary>
  71. /// Return the next attribute sibling of the specified node. If the node is not itself an
  72. /// attribute, or if there are no siblings, then do not set pageNode or idxNode and return false.
  73. /// </summary>
  74. public static bool GetNextAttribute(ref XPathNode[] pageNode, ref int idxNode) {
  75. XPathNode[] page;
  76. int idx;
  77. Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)");
  78. idx = pageNode[idxNode].GetSibling(out page);
  79. if (idx != 0 && page[idx].NodeType == XPathNodeType.Attribute) {
  80. pageNode = page;
  81. idxNode = idx;
  82. return true;
  83. }
  84. return false;
  85. }
  86. /// <summary>
  87. /// Return the first content-typed child of the specified node. If the node has no children, or
  88. /// if the node is not content-typed, then do not set pageNode or idxNode and return false.
  89. /// </summary>
  90. public static bool GetContentChild(ref XPathNode[] pageNode, ref int idxNode) {
  91. XPathNode[] page = pageNode;
  92. int idx = idxNode;
  93. Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)");
  94. if (page[idx].HasContentChild) {
  95. GetChild(ref page, ref idx);
  96. // Skip past attribute children
  97. while (page[idx].NodeType == XPathNodeType.Attribute) {
  98. idx = page[idx].GetSibling(out page);
  99. Debug.Assert(idx != 0);
  100. }
  101. pageNode = page;
  102. idxNode = idx;
  103. return true;
  104. }
  105. return false;
  106. }
  107. /// <summary>
  108. /// Return the next content-typed sibling of the specified node. If the node has no siblings, or
  109. /// if the node is not content-typed, then do not set pageNode or idxNode and return false.
  110. /// </summary>
  111. public static bool GetContentSibling(ref XPathNode[] pageNode, ref int idxNode) {
  112. XPathNode[] page = pageNode;
  113. int idx = idxNode;
  114. Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)");
  115. if (!page[idx].IsAttrNmsp) {
  116. idx = page[idx].GetSibling(out page);
  117. if (idx != 0) {
  118. pageNode = page;
  119. idxNode = idx;
  120. return true;
  121. }
  122. }
  123. return false;
  124. }
  125. /// <summary>
  126. /// Return the parent of the specified node. If the node has no parent, do not set pageNode
  127. /// or idxNode and return false.
  128. /// </summary>
  129. public static bool GetParent(ref XPathNode[] pageNode, ref int idxNode) {
  130. XPathNode[] page = pageNode;
  131. int idx = idxNode;
  132. Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)");
  133. idx = page[idx].GetParent(out page);
  134. if (idx != 0) {
  135. pageNode = page;
  136. idxNode = idx;
  137. return true;
  138. }
  139. return false;
  140. }
  141. /// <summary>
  142. /// Return a location integer that can be easily compared with other locations from the same document
  143. /// in order to determine the relative document order of two nodes.
  144. /// </summary>
  145. public static int GetLocation(XPathNode[] pageNode, int idxNode) {
  146. Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)");
  147. Debug.Assert(idxNode <= UInt16.MaxValue);
  148. Debug.Assert(pageNode[0].PageInfo.PageNumber <= Int16.MaxValue);
  149. return (pageNode[0].PageInfo.PageNumber << 16) | idxNode;
  150. }
  151. /// <summary>
  152. /// Return the first element child of the specified node that has the specified name. If no such child exists,
  153. /// then do not set pageNode or idxNode and return false. Assume that the localName has been atomized with respect
  154. /// to this document's name table, but not the namespaceName.
  155. /// </summary>
  156. public static bool GetElementChild(ref XPathNode[] pageNode, ref int idxNode, string localName, string namespaceName) {
  157. XPathNode[] page = pageNode;
  158. int idx = idxNode;
  159. Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)");
  160. // Only check children if at least one element child exists
  161. if (page[idx].HasElementChild) {
  162. GetChild(ref page, ref idx);
  163. Debug.Assert(idx != 0);
  164. // Find element with specified localName and namespaceName
  165. do {
  166. if (page[idx].ElementMatch(localName, namespaceName)) {
  167. pageNode = page;
  168. idxNode = idx;
  169. return true;
  170. }
  171. idx = page[idx].GetSibling(out page);
  172. }
  173. while (idx != 0);
  174. }
  175. return false;
  176. }
  177. /// <summary>
  178. /// Return a following sibling element of the specified node that has the specified name. If no such
  179. /// sibling exists, or if the node is not content-typed, then do not set pageNode or idxNode and
  180. /// return false. Assume that the localName has been atomized with respect to this document's name table,
  181. /// but not the namespaceName.
  182. /// </summary>
  183. public static bool GetElementSibling(ref XPathNode[] pageNode, ref int idxNode, string localName, string namespaceName) {
  184. XPathNode[] page = pageNode;
  185. int idx = idxNode;
  186. Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)");
  187. // Elements should not be returned as "siblings" of attributes (namespaces don't link to elements, so don't need to check them)
  188. if (page[idx].NodeType != XPathNodeType.Attribute) {
  189. while (true) {
  190. idx = page[idx].GetSibling(out page);
  191. if (idx == 0)
  192. break;
  193. if (page[idx].ElementMatch(localName, namespaceName)) {
  194. pageNode = page;
  195. idxNode = idx;
  196. return true;
  197. }
  198. }
  199. }
  200. return false;
  201. }
  202. /// <summary>
  203. /// Return the first child of the specified node that has the specified type (must be a content type). If no such
  204. /// child exists, then do not set pageNode or idxNode and return false.
  205. /// </summary>
  206. public static bool GetContentChild(ref XPathNode[] pageNode, ref int idxNode, XPathNodeType typ) {
  207. XPathNode[] page = pageNode;
  208. int idx = idxNode;
  209. int mask;
  210. Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)");
  211. // Only check children if at least one content-typed child exists
  212. if (page[idx].HasContentChild) {
  213. mask = XPathNavigator.GetContentKindMask(typ);
  214. GetChild(ref page, ref idx);
  215. do {
  216. if (((1 << (int) page[idx].NodeType) & mask) != 0) {
  217. // Never return attributes, as Attribute is not a content type
  218. if (typ == XPathNodeType.Attribute)
  219. return false;
  220. pageNode = page;
  221. idxNode = idx;
  222. return true;
  223. }
  224. idx = page[idx].GetSibling(out page);
  225. }
  226. while (idx != 0);
  227. }
  228. return false;
  229. }
  230. /// <summary>
  231. /// Return a following sibling of the specified node that has the specified type. If no such
  232. /// sibling exists, then do not set pageNode or idxNode and return false.
  233. /// </summary>
  234. public static bool GetContentSibling(ref XPathNode[] pageNode, ref int idxNode, XPathNodeType typ) {
  235. XPathNode[] page = pageNode;
  236. int idx = idxNode;
  237. int mask = XPathNavigator.GetContentKindMask(typ);
  238. Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)");
  239. if (page[idx].NodeType != XPathNodeType.Attribute) {
  240. while (true) {
  241. idx = page[idx].GetSibling(out page);
  242. if (idx == 0)
  243. break;
  244. if (((1 << (int) page[idx].NodeType) & mask) != 0) {
  245. Debug.Assert(typ != XPathNodeType.Attribute && typ != XPathNodeType.Namespace);
  246. pageNode = page;
  247. idxNode = idx;
  248. return true;
  249. }
  250. }
  251. }
  252. return false;
  253. }
  254. /// <summary>
  255. /// Return the first preceding sibling of the specified node. If no such sibling exists, then do not set
  256. /// pageNode or idxNode and return false.
  257. /// </summary>
  258. public static bool GetPreviousContentSibling(ref XPathNode[] pageNode, ref int idxNode) {
  259. XPathNode[] pageParent = pageNode, pagePrec, pageAnc;
  260. int idxParent = idxNode, idxPrec, idxAnc;
  261. Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)");
  262. Debug.Assert(pageNode[idxNode].NodeType != XPathNodeType.Attribute);
  263. // Since nodes are laid out in document order on pages, the algorithm is:
  264. // 1. Get parent of current node
  265. // 2. If no parent, then there is no previous sibling, so return false
  266. // 3. Get node that immediately precedes the current node in document order
  267. // 4. If preceding node is parent, then there is no previous sibling, so return false
  268. // 5. Walk ancestors of preceding node, until parent of current node is found
  269. idxParent = pageParent[idxParent].GetParent(out pageParent);
  270. if (idxParent != 0) {
  271. idxPrec = idxNode - 1;
  272. if (idxPrec == 0) {
  273. // Need to get previous page
  274. pagePrec = pageNode[0].PageInfo.PreviousPage;
  275. idxPrec = pagePrec.Length - 1;
  276. }
  277. else {
  278. // Previous node is on the same page
  279. pagePrec = pageNode;
  280. }
  281. // If parent node is previous node, then no previous sibling
  282. if (idxParent == idxPrec && pageParent == pagePrec)
  283. return false;
  284. // Find child of parent node by walking ancestor chain
  285. pageAnc = pagePrec;
  286. idxAnc = idxPrec;
  287. do {
  288. pagePrec = pageAnc;
  289. idxPrec = idxAnc;
  290. idxAnc = pageAnc[idxAnc].GetParent(out pageAnc);
  291. Debug.Assert(idxAnc != 0 && pageAnc != null);
  292. }
  293. while (idxAnc != idxParent || pageAnc != pageParent);
  294. // We found the previous sibling, but if it's an attribute node, then return false
  295. if (pagePrec[idxPrec].NodeType != XPathNodeType.Attribute) {
  296. pageNode = pagePrec;
  297. idxNode = idxPrec;
  298. return true;
  299. }
  300. }
  301. return false;
  302. }
  303. /// <summary>
  304. /// Return a previous sibling element of the specified node that has the specified name. If no such
  305. /// sibling exists, or if the node is not content-typed, then do not set pageNode or idxNode and
  306. /// return false. Assume that the localName has been atomized with respect to this document's name table,
  307. /// but not the namespaceName.
  308. /// </summary>
  309. public static bool GetPreviousElementSibling(ref XPathNode[] pageNode, ref int idxNode, string localName, string namespaceName) {
  310. XPathNode[] page = pageNode;
  311. int idx = idxNode;
  312. Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)");
  313. if (page[idx].NodeType != XPathNodeType.Attribute) {
  314. while (true) {
  315. if (!GetPreviousContentSibling(ref page, ref idx))
  316. break;
  317. if (page[idx].ElementMatch(localName, namespaceName)) {
  318. pageNode = page;
  319. idxNode = idx;
  320. return true;
  321. }
  322. }
  323. }
  324. return false;
  325. }
  326. /// <summary>
  327. /// Return a previous sibling of the specified node that has the specified type. If no such
  328. /// sibling exists, then do not set pageNode or idxNode and return false.
  329. /// </summary>
  330. public static bool GetPreviousContentSibling(ref XPathNode[] pageNode, ref int idxNode, XPathNodeType typ) {
  331. XPathNode[] page = pageNode;
  332. int idx = idxNode;
  333. int mask = XPathNavigator.GetContentKindMask(typ);
  334. Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)");
  335. while (true) {
  336. if (!GetPreviousContentSibling(ref page, ref idx))
  337. break;
  338. if (((1 << (int) page[idx].NodeType) & mask) != 0) {
  339. pageNode = page;
  340. idxNode = idx;
  341. return true;
  342. }
  343. }
  344. return false;
  345. }
  346. /// <summary>
  347. /// Return the attribute of the specified node that has the specified name. If no such attribute exists,
  348. /// then do not set pageNode or idxNode and return false. Assume that the localName has been atomized with respect
  349. /// to this document's name table, but not the namespaceName.
  350. /// </summary>
  351. public static bool GetAttribute(ref XPathNode[] pageNode, ref int idxNode, string localName, string namespaceName) {
  352. XPathNode[] page = pageNode;
  353. int idx = idxNode;
  354. Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)");
  355. // Find attribute with specified localName and namespaceName
  356. if (page[idx].HasAttribute) {
  357. GetChild(ref page, ref idx);
  358. do {
  359. if (page[idx].NameMatch(localName, namespaceName)) {
  360. pageNode = page;
  361. idxNode = idx;
  362. return true;
  363. }
  364. idx = page[idx].GetSibling(out page);
  365. }
  366. while (idx != 0 && page[idx].NodeType == XPathNodeType.Attribute);
  367. }
  368. return false;
  369. }
  370. /// <summary>
  371. /// Get the next non-virtual (not collapsed text, not namespaces) node that follows the specified node in document order.
  372. /// If no such node exists, then do not set pageNode or idxNode and return false.
  373. /// </summary>
  374. public static bool GetFollowing(ref XPathNode[] pageNode, ref int idxNode) {
  375. XPathNode[] page = pageNode;
  376. int idx = idxNode;
  377. do {
  378. // Next non-virtual node is in next slot within the page
  379. if (++idx < page[0].PageInfo.NodeCount) {
  380. pageNode = page;
  381. idxNode = idx;
  382. return true;
  383. }
  384. // Otherwise, start at the beginning of the next page
  385. page = page[0].PageInfo.NextPage;
  386. idx = 0;
  387. }
  388. while (page != null);
  389. return false;
  390. }
  391. /// <summary>
  392. /// Get the next element node that:
  393. /// 1. Follows the current node in document order (includes descendants, unlike XPath following axis)
  394. /// 2. Precedes the ending node in document order (if pageEnd is null, then all following nodes in the document are considered)
  395. /// 3. Has the specified QName
  396. /// If no such element exists, then do not set pageCurrent or idxCurrent and return false.
  397. /// Assume that the localName has been atomized with respect to this document's name table, but not the namespaceName.
  398. /// </summary>
  399. public static bool GetElementFollowing(ref XPathNode[] pageCurrent, ref int idxCurrent, XPathNode[] pageEnd, int idxEnd, string localName, string namespaceName) {
  400. XPathNode[] page = pageCurrent;
  401. int idx = idxCurrent;
  402. Debug.Assert(pageCurrent != null && idxCurrent != 0, "Cannot pass null argument(s)");
  403. // If current node is an element having a matching name,
  404. if (page[idx].NodeType == XPathNodeType.Element && (object) page[idx].LocalName == (object) localName) {
  405. // Then follow similar element name pointers
  406. int idxPageEnd = 0;
  407. int idxPageCurrent;
  408. if (pageEnd != null) {
  409. idxPageEnd = pageEnd[0].PageInfo.PageNumber;
  410. idxPageCurrent = page[0].PageInfo.PageNumber;
  411. // If ending node is <= starting node in document order, then scan to end of document
  412. if (idxPageCurrent > idxPageEnd || (idxPageCurrent == idxPageEnd && idx >= idxEnd))
  413. pageEnd = null;
  414. }
  415. while (true) {
  416. idx = page[idx].GetSimilarElement(out page);
  417. if (idx == 0)
  418. break;
  419. // Only scan to ending node
  420. if (pageEnd != null) {
  421. idxPageCurrent = page[0].PageInfo.PageNumber;
  422. if (idxPageCurrent > idxPageEnd)
  423. break;
  424. if (idxPageCurrent == idxPageEnd && idx >= idxEnd)
  425. break;
  426. }
  427. if ((object) page[idx].LocalName == (object) localName && page[idx].NamespaceUri == namespaceName)
  428. goto FoundNode;
  429. }
  430. return false;
  431. }
  432. // Since nodes are laid out in document order on pages, scan them sequentially
  433. // rather than following links.
  434. idx++;
  435. do {
  436. if ((object) page == (object) pageEnd && idx <= idxEnd) {
  437. // Only scan to termination point
  438. while (idx != idxEnd) {
  439. if (page[idx].ElementMatch(localName, namespaceName))
  440. goto FoundNode;
  441. idx++;
  442. }
  443. break;
  444. }
  445. else {
  446. // Scan all nodes in the page
  447. while (idx < page[0].PageInfo.NodeCount) {
  448. if (page[idx].ElementMatch(localName, namespaceName))
  449. goto FoundNode;
  450. idx++;
  451. }
  452. }
  453. page = page[0].PageInfo.NextPage;
  454. idx = 1;
  455. }
  456. while (page != null);
  457. return false;
  458. FoundNode:
  459. // Found match
  460. pageCurrent = page;
  461. idxCurrent = idx;
  462. return true;
  463. }
  464. /// <summary>
  465. /// Get the next node that:
  466. /// 1. Follows the current node in document order (includes descendants, unlike XPath following axis)
  467. /// 2. Precedes the ending node in document order (if pageEnd is null, then all following nodes in the document are considered)
  468. /// 3. Has the specified XPathNodeType (but Attributes and Namespaces never match)
  469. /// If no such node exists, then do not set pageCurrent or idxCurrent and return false.
  470. /// </summary>
  471. public static bool GetContentFollowing(ref XPathNode[] pageCurrent, ref int idxCurrent, XPathNode[] pageEnd, int idxEnd, XPathNodeType typ) {
  472. XPathNode[] page = pageCurrent;
  473. int idx = idxCurrent;
  474. int mask = XPathNavigator.GetContentKindMask(typ);
  475. Debug.Assert(pageCurrent != null && idxCurrent != 0, "Cannot pass null argument(s)");
  476. Debug.Assert(typ != XPathNodeType.Text, "Text should be handled by GetTextFollowing in order to take into account collapsed text.");
  477. Debug.Assert(page[idx].NodeType != XPathNodeType.Attribute, "Current node should never be an attribute or namespace--caller should handle this case.");
  478. // Since nodes are laid out in document order on pages, scan them sequentially
  479. // rather than following sibling/child/parent links.
  480. idx++;
  481. do {
  482. if ((object) page == (object) pageEnd && idx <= idxEnd) {
  483. // Only scan to termination point
  484. while (idx != idxEnd) {
  485. if (((1 << (int) page[idx].NodeType) & mask) != 0)
  486. goto FoundNode;
  487. idx++;
  488. }
  489. break;
  490. }
  491. else {
  492. // Scan all nodes in the page
  493. while (idx < page[0].PageInfo.NodeCount) {
  494. if (((1 << (int) page[idx].NodeType) & mask) != 0)
  495. goto FoundNode;
  496. idx++;
  497. }
  498. }
  499. page = page[0].PageInfo.NextPage;
  500. idx = 1;
  501. }
  502. while (page != null);
  503. return false;
  504. FoundNode:
  505. Debug.Assert(!page[idx].IsAttrNmsp, "GetContentFollowing should never return attributes or namespaces.");
  506. // Found match
  507. pageCurrent = page;
  508. idxCurrent = idx;
  509. return true;
  510. }
  511. /// <summary>
  512. /// Scan all nodes that follow the current node in document order, but precede the ending node in document order.
  513. /// Return two types of nodes with non-null text:
  514. /// 1. Element parents of collapsed text nodes (since it is the element parent that has the collapsed text)
  515. /// 2. Non-collapsed text nodes
  516. /// If no such node exists, then do not set pageCurrent or idxCurrent and return false.
  517. /// </summary>
  518. public static bool GetTextFollowing(ref XPathNode[] pageCurrent, ref int idxCurrent, XPathNode[] pageEnd, int idxEnd) {
  519. XPathNode[] page = pageCurrent;
  520. int idx = idxCurrent;
  521. Debug.Assert(pageCurrent != null && idxCurrent != 0, "Cannot pass null argument(s)");
  522. Debug.Assert(!page[idx].IsAttrNmsp, "Current node should never be an attribute or namespace--caller should handle this case.");
  523. // Since nodes are laid out in document order on pages, scan them sequentially
  524. // rather than following sibling/child/parent links.
  525. idx++;
  526. do {
  527. if ((object) page == (object) pageEnd && idx <= idxEnd) {
  528. // Only scan to termination point
  529. while (idx != idxEnd) {
  530. if (page[idx].IsText || (page[idx].NodeType == XPathNodeType.Element && page[idx].HasCollapsedText))
  531. goto FoundNode;
  532. idx++;
  533. }
  534. break;
  535. }
  536. else {
  537. // Scan all nodes in the page
  538. while (idx < page[0].PageInfo.NodeCount) {
  539. if (page[idx].IsText || (page[idx].NodeType == XPathNodeType.Element && page[idx].HasCollapsedText))
  540. goto FoundNode;
  541. idx++;
  542. }
  543. }
  544. page = page[0].PageInfo.NextPage;
  545. idx = 1;
  546. }
  547. while (page != null);
  548. return false;
  549. FoundNode:
  550. // Found match
  551. pageCurrent = page;
  552. idxCurrent = idx;
  553. return true;
  554. }
  555. /// <summary>
  556. /// Get the next non-virtual (not collapsed text, not namespaces) node that follows the specified node in document order,
  557. /// but is not a descendant. If no such node exists, then do not set pageNode or idxNode and return false.
  558. /// </summary>
  559. public static bool GetNonDescendant(ref XPathNode[] pageNode, ref int idxNode) {
  560. XPathNode[] page = pageNode;
  561. int idx = idxNode;
  562. // Get page, idx at which to end sequential scan of nodes
  563. do {
  564. // If the current node has a sibling,
  565. if (page[idx].HasSibling) {
  566. // Then that is the first non-descendant
  567. pageNode = page;
  568. idxNode = page[idx].GetSibling(out pageNode);
  569. return true;
  570. }
  571. // Otherwise, try finding a sibling at the parent level
  572. idx = page[idx].GetParent(out page);
  573. }
  574. while (idx != 0);
  575. return false;
  576. }
  577. /// <summary>
  578. /// Return the page and index of the first child (attribute or content) of the specified node.
  579. /// </summary>
  580. private static void GetChild(ref XPathNode[] pageNode, ref int idxNode) {
  581. Debug.Assert(pageNode[idxNode].HasAttribute || pageNode[idxNode].HasContentChild, "Caller must check HasAttribute/HasContentChild on parent before calling GetChild.");
  582. Debug.Assert(pageNode[idxNode].HasAttribute || !pageNode[idxNode].HasCollapsedText, "Text child is virtualized and therefore is not present in the physical node page.");
  583. if (++idxNode >= pageNode.Length) {
  584. // Child is first node on next page
  585. pageNode = pageNode[0].PageInfo.NextPage;
  586. idxNode = 1;
  587. }
  588. // Else child is next node on this page
  589. }
  590. }
  591. }