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