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