Updates referencesource to .NET 4.7
[mono-project.git] / mcs / class / referencesource / System.Xml / System / Xml / Cache / XPathDocumentNavigator.cs
blobf46468ff6ab8477a2b8fa4c0c1316a0556fb6e74
1 //------------------------------------------------------------------------------
2 // <copyright file="XPathDocumentNavigator.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">Microsoft</owner>
6 //------------------------------------------------------------------------------
7 using System;
8 using System.IO;
9 using System.Collections;
10 using System.Globalization;
11 using System.Text;
12 using System.Diagnostics;
13 using System.Xml;
14 using System.Xml.XPath;
15 using System.Xml.Schema;
17 namespace MS.Internal.Xml.Cache {
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;
31 //-----------------------------------------------
32 // Constructors
33 //-----------------------------------------------
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;
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;
56 //-----------------------------------------------
57 // XPathItem
58 //-----------------------------------------------
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;
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;
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;
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;
91 #endif
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;
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;
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;
111 while (XPathNodeHelper.GetTextFollowing(ref page, ref idx, pageEnd, idxEnd)) {
112 Debug.Assert(page[idx].NodeType == XPathNodeType.Element || page[idx].IsText);
114 if (s.Length == 0) {
115 s = page[idx].Value;
117 else {
118 if (bldr == null) {
119 bldr = new StringBuilder();
120 bldr.Append(s);
122 bldr.Append(page[idx].Value);
126 return (bldr != null) ? bldr.ToString() : s;
131 //-----------------------------------------------
132 // XPathNavigator
133 //-----------------------------------------------
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);
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; }
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; }
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; }
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; }
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; }
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;
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;
190 else {
191 page = this.pageCurrent;
192 idx = this.idxCurrent;
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;
204 // Get BaseUri of parent
205 idx = page[idx].GetParent(out page);
207 while (idx != 0);
209 return string.Empty;
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; }
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; }
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;
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;
243 return false;
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);
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; }
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;
270 if ((object) localName != (object) this.atomizedLocalName)
271 this.atomizedLocalName = (localName != null) ? NameTable.Get(localName) : null;
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;
280 return false;
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;
291 if (namespaceScope == XPathNamespaceScope.Local) {
292 // Get local namespaces only
293 idx = XPathNodeHelper.GetLocalNamespaces(this.pageCurrent, this.idxCurrent, out page);
295 else {
296 // Get all in-scope namespaces
297 idx = XPathNodeHelper.GetInScopeNamespaces(this.pageCurrent, this.idxCurrent, out page);
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;
310 // Skip past xmlns:xml
311 idx = page[idx].GetSibling(out page);
314 return false;
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;
325 // If current node is not a namespace node, return false
326 if (page[idx].NodeType != XPathNodeType.Namespace)
327 return false;
329 while (true) {
330 // Get next namespace sibling
331 idx = page[idx].GetSibling(out page);
333 // If there are no more nodes, return false
334 if (idx == 0)
335 return false;
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;
345 case XPathNamespaceScope.ExcludeXml:
346 // If node is xmlns:xml, then skip it
347 if (page[idx].IsXmlNamespaceNode)
348 continue;
349 break;
352 // Found a matching next namespace node, so return it
353 break;
356 this.pageCurrent = page;
357 this.idxCurrent = idx;
358 return true;
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);
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;
379 return XPathNodeHelper.GetPreviousContentSibling(ref this.pageCurrent, ref this.idxCurrent);
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;
395 return XPathNodeHelper.GetContentChild(ref this.pageCurrent, ref this.idxCurrent);
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;
417 return XPathNodeHelper.GetParent(ref this.pageCurrent, ref this.idxCurrent);
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;
433 return false;
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;
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;
454 return false;
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;
467 return false;
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; }
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;
486 this.idxCurrent = this.pageCurrent[this.idxCurrent].GetRoot(out this.pageCurrent);
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;
497 return XPathNodeHelper.GetElementChild(ref this.pageCurrent, ref this.idxCurrent, this.atomizedLocalName, namespaceURI);
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;
508 return XPathNodeHelper.GetElementSibling(ref this.pageCurrent, ref this.idxCurrent, this.atomizedLocalName, namespaceURI);
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;
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;
528 return XPathNodeHelper.GetContentChild(ref this.pageCurrent, ref this.idxCurrent, type);
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);
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;
550 if ((object) localName != (object) this.atomizedLocalName)
551 this.atomizedLocalName = (localName != null) ? NameTable.Get(localName) : null;
553 // Get node on which scan ends (null if rest of document should be scanned)
554 idxEnd = GetFollowingEnd(end as XPathDocumentNavigator, false, out pageEnd);
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;
561 this.pageCurrent = this.pageParent;
562 this.idxCurrent = this.idxParent;
563 this.pageParent = null;
564 this.idxParent = 0;
565 return true;
568 return XPathNodeHelper.GetElementFollowing(ref this.pageCurrent, ref this.idxCurrent, pageEnd, idxEnd, this.atomizedLocalName, namespaceURI);
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;
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;
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;
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);
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;
607 else {
608 page = this.pageCurrent;
609 idx = this.idxCurrent;
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;
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;
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;
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;
635 return true;
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);
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;
647 this.pageCurrent = this.pageParent;
648 this.idxCurrent = this.idxParent;
649 this.pageParent = null;
650 this.idxParent = 0;
651 return true;
654 return XPathNodeHelper.GetContentFollowing(ref this.pageCurrent, ref this.idxCurrent, pageEnd, idxEnd, type);
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);
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);
672 return new XPathDocumentElementChildIterator(this, name, namespaceURI);
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);
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);
692 return new XPathDocumentElementDescendantIterator(this, name, namespaceURI, matchSelf);
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();
714 if (locThis == locThat) {
715 locThis = GetSecondaryLocation();
716 locThat = that.GetSecondaryLocation();
718 if (locThis == locThat)
719 return XmlNodeOrder.Same;
721 return (locThis < locThat) ? XmlNodeOrder.Before : XmlNodeOrder.After;
724 return XmlNodeOrder.Unknown;
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;
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;
741 else {
742 idxThat = that.pageCurrent[that.idxCurrent].GetParent(out pageThat);
745 while (idxThat != 0) {
746 if (idxThat == this.idxCurrent && pageThat == this.pageCurrent)
747 return true;
748 idxThat = pageThat[idxThat].GetParent(out pageThat);
751 return false;
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);
767 // Yes, so primary location should be derived from parent node
768 return XPathNodeHelper.GetLocation(this.pageParent, this.idxParent);
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;
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);
789 case XPathNodeType.Attribute:
790 // Attribute nodes come next (location is always positive)
791 return XPathNodeHelper.GetLocation(this.pageCurrent, this.idxCurrent);
793 default:
794 // Collapsed text nodes are always last
795 return int.MaxValue;
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;
809 // Ensure distinguishing attributes, namespaces and child nodes
810 buf[idx++] = NodeTypeLetter[(int)this.pageCurrent[this.idxCurrent].NodeType];
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';
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);
829 return new string(buf, 0, idx);
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();
843 //-----------------------------------------------
844 // IXmlLineInfo
845 //-----------------------------------------------
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;
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;
863 return this.pageCurrent[this.idxCurrent].LineNumber;
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;
876 return this.pageCurrent[this.idxCurrent].LinePosition;
881 //-----------------------------------------------
882 // Helper methods
883 //-----------------------------------------------
885 /// <summary>
886 /// Get hashcode based on current position of the navigator.
887 /// </summary>
888 public int GetPositionHashCode() {
889 return this.idxCurrent ^ this.idxParent;
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;
899 // Cannot be an element if parent is stored
900 if (this.idxParent != 0)
901 return false;
903 return this.pageCurrent[this.idxCurrent].ElementMatch(this.atomizedLocalName, namespaceURI);
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);
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);
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) {
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;
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;
944 // No following, so set pageEnd to null and return an index of 0
945 pageEnd = null;
946 return 0;