1 //------------------------------------------------------------------------------
2 // <copyright file="XPathDocumentNavigator.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">[....]</owner>
6 //------------------------------------------------------------------------------
9 using System.Collections;
10 using System.Globalization;
12 using System.Diagnostics;
14 using System.Xml.XPath;
15 using System.Xml.Schema;
17 namespace MS.Internal.Xml.Cache {
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.
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 //-----------------------------------------------
33 //-----------------------------------------------
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).
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;
51 public XPathDocumentNavigator(XPathDocumentNavigator nav) : this(nav.pageCurrent, nav.idxCurrent, nav.pageParent, nav.idxParent) {
52 this.atomizedLocalName = nav.atomizedLocalName;
56 //-----------------------------------------------
58 //-----------------------------------------------
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.
66 public override string Value {
69 XPathNode[] page, pageEnd;
72 // Try to get the pre-computed string value of the node
73 value = this.pageCurrent[this.idxCurrent].Value;
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.");
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.");
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)) {
111 while (XPathNodeHelper.GetTextFollowing(ref page, ref idx, pageEnd, idxEnd)) {
112 Debug.Assert(page[idx].NodeType == XPathNodeType.Element || page[idx].IsText);
119 bldr = new StringBuilder();
122 bldr.Append(page[idx].Value);
126 return (bldr != null) ? bldr.ToString() : s;
131 //-----------------------------------------------
133 //-----------------------------------------------
136 /// Create a copy of this navigator, positioned to the same node in the tree.
138 public override XPathNavigator Clone() {
139 return new XPathDocumentNavigator(this.pageCurrent, this.idxCurrent, this.pageParent, this.idxParent);
143 /// Get the XPath node type of the current node.
145 public override XPathNodeType NodeType {
146 get { return this.pageCurrent[this.idxCurrent].NodeType; }
150 /// Get the local name portion of the current node's name.
152 public override string LocalName {
153 get { return this.pageCurrent[this.idxCurrent].LocalName; }
157 /// Get the namespace portion of the current node's name.
159 public override string NamespaceURI {
160 get { return this.pageCurrent[this.idxCurrent].NamespaceUri; }
164 /// Get the name of the current node.
166 public override string Name {
167 get { return this.pageCurrent[this.idxCurrent].Name; }
171 /// Get the prefix portion of the current node's name.
173 public override string Prefix {
174 get { return this.pageCurrent[this.idxCurrent].Prefix; }
178 /// Get the base URI of the current node.
180 public override string BaseURI {
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;
191 page = this.pageCurrent;
192 idx = this.idxCurrent;
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);
214 /// Return true if this is an element which used a shortcut tag in its Xml 1.0 serialized form.
216 public override bool IsEmptyElement {
217 get { return this.pageCurrent[this.idxCurrent].AllowShortcutTag; }
221 /// Return the xml name table which was used to atomize all prefixes, local-names, and
222 /// namespace uris in the document.
224 public override XmlNameTable NameTable {
225 get { return this.pageCurrent[this.idxCurrent].Document.NameTable; }
229 /// Position the navigator on the first attribute of the current node and return true. If no attributes
230 /// can be found, return false.
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;
247 /// If positioned on an attribute, move to its next sibling attribute. If no attributes can be found,
250 public override bool MoveToNextAttribute() {
251 return XPathNodeHelper.GetNextAttribute(ref this.pageCurrent, ref this.idxCurrent);
255 /// True if the current node has one or more attributes.
257 public override bool HasAttributes {
258 get { return this.pageCurrent[this.idxCurrent].HasAttribute; }
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.
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;
284 /// Position the navigator on the namespace within the specified scope. If no matching namespace
285 /// can be found, return false.
287 public override bool MoveToFirstNamespace(XPathNamespaceScope namespaceScope) {
291 if (namespaceScope == XPathNamespaceScope.Local) {
292 // Get local namespaces only
293 idx = XPathNodeHelper.GetLocalNamespaces(this.pageCurrent, this.idxCurrent, out page);
296 // Get all in-scope namespaces
297 idx = XPathNodeHelper.GetInScopeNamespaces(this.pageCurrent, this.idxCurrent, out page);
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;
310 // Skip past xmlns:xml
311 idx = page[idx].GetSibling(out page);
318 /// Position the navigator on the next namespace within the specified scope. If no matching namespace
319 /// can be found, return false.
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)
330 // Get next namespace sibling
331 idx = page[idx].GetSibling(out page);
333 // If there are no more nodes, return false
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)
345 case XPathNamespaceScope.ExcludeXml:
346 // If node is xmlns:xml, then skip it
347 if (page[idx].IsXmlNamespaceNode)
352 // Found a matching next namespace node, so return it
356 this.pageCurrent = page;
357 this.idxCurrent = idx;
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.
365 public override bool MoveToNext() {
366 return XPathNodeHelper.GetContentSibling(ref this.pageCurrent, ref this.idxCurrent);
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.
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)
379 return XPathNodeHelper.GetPreviousContentSibling(ref this.pageCurrent, ref this.idxCurrent);
383 /// Move to the first content-typed child of the current node. Return false if the current
384 /// node has no content children.
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);
395 return XPathNodeHelper.GetContentChild(ref this.pageCurrent, ref this.idxCurrent);
399 /// Position the navigator on the parent of the current node. If the current node has no parent,
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;
417 return XPathNodeHelper.GetParent(ref this.pageCurrent, ref this.idxCurrent);
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.
424 public override bool MoveTo(XPathNavigator other) {
425 XPathDocumentNavigator that = other as XPathDocumentNavigator;
427 this.pageCurrent = that.pageCurrent;
428 this.idxCurrent = that.idxCurrent;
429 this.pageParent = that.pageParent;
430 this.idxParent = that.idxParent;
437 /// Position to the navigator to the element whose id is equal to the specified "id" string.
439 public override bool MoveToId(string id) {
443 idx = this.pageCurrent[this.idxCurrent].Document.LookupIdElement(id, out page);
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;
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.
461 public override bool IsSamePosition(XPathNavigator other) {
462 XPathDocumentNavigator that = other as XPathDocumentNavigator;
464 return this.idxCurrent == that.idxCurrent && this.pageCurrent == that.pageCurrent &&
465 this.idxParent == that.idxParent && this.pageParent == that.pageParent;
471 /// Returns true if the current node has children.
473 public override bool HasChildren {
474 get { return this.pageCurrent[this.idxCurrent].HasContentChild; }
478 /// Position the navigator on the root node of the current document.
480 public override void MoveToRoot() {
481 if (this.idxParent != 0) {
482 // Clear parent state
483 this.pageParent = null;
486 this.idxCurrent = this.pageCurrent[this.idxCurrent].GetRoot(out this.pageCurrent);
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.
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);
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.
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);
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.
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)
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);
528 return XPathNodeHelper.GetContentChild(ref this.pageCurrent, ref this.idxCurrent, type);
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.
535 public override bool MoveToNext(XPathNodeType type) {
536 return XPathNodeHelper.GetContentSibling(ref this.pageCurrent, ref this.idxCurrent, type);
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.
546 public override bool MoveToFollowing(string localName, string namespaceURI, XPathNavigator end) {
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))
561 this.pageCurrent = this.pageParent;
562 this.idxCurrent = this.idxParent;
563 this.pageParent = null;
568 return XPathNodeHelper.GetElementFollowing(ref this.pageCurrent, ref this.idxCurrent, pageEnd, idxEnd, this.atomizedLocalName, namespaceURI);
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.
578 public override bool MoveToFollowing(XPathNodeType type, XPathNavigator end) {
579 XPathDocumentNavigator endTiny = end as XPathDocumentNavigator;
580 XPathNode[] page, pageEnd;
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
592 this.pageParent = this.pageCurrent;
593 this.idxParent = this.idxCurrent;
594 this.idxCurrent = this.pageCurrent[this.idxCurrent].Document.GetCollapsedTextNode(out this.pageCurrent);
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;
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)
616 // Get all virtual (collapsed) and physical text nodes which follow the current node
617 if (!XPathNodeHelper.GetTextFollowing(ref page, ref idx, pageEnd, idxEnd))
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;
628 // Physical text node
629 Debug.Assert(page[idx].IsText);
630 this.pageCurrent = page;
631 this.idxCurrent = idx;
632 this.pageParent = null;
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))
647 this.pageCurrent = this.pageParent;
648 this.idxCurrent = this.idxParent;
649 this.pageParent = null;
654 return XPathNodeHelper.GetContentFollowing(ref this.pageCurrent, ref this.idxCurrent, pageEnd, idxEnd, type);
658 /// Return an iterator that ranges over all children of the current node that match the specified XPathNodeType.
660 public override XPathNodeIterator SelectChildren(XPathNodeType type) {
661 return new XPathDocumentKindChildIterator(this, type);
665 /// Return an iterator that ranges over all children of the current node that match the specified QName.
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);
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.
679 public override XPathNodeIterator SelectDescendants(XPathNodeType type, bool matchSelf) {
680 return new XPathDocumentKindDescendantIterator(this, type, matchSelf);
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.
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);
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.
705 public override XmlNodeOrder ComparePosition(XPathNavigator other) {
706 XPathDocumentNavigator that = other as XPathDocumentNavigator;
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;
728 /// Return true if the "other" navigator's current node is a descendant of this navigator's current node.
730 public override bool IsDescendant(XPathNavigator other) {
731 XPathDocumentNavigator that = other as XPathDocumentNavigator;
733 XPathNode[] pageThat;
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;
742 idxThat = that.pageCurrent[that.idxCurrent].GetParent(out pageThat);
745 while (idxThat != 0) {
746 if (idxThat == this.idxCurrent && pageThat == this.pageCurrent)
748 idxThat = pageThat[idxThat].GetParent(out pageThat);
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
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);
772 /// Construct a secondary location for this navigator. This location should only be used if
773 /// primary locations previously compared equal.
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)
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);
794 // Collapsed text nodes are always last
800 /// Create a unique id for the current node. This is used by the generate-id() function.
802 internal override string UniqueId {
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];
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);
816 buf[idx++] = UniqueIdTbl[loc & 0x1f];
822 // Code the node itself
823 loc = (this.pageCurrent[0].PageInfo.PageNumber - 1) << 16 | (this.idxCurrent - 1);
825 buf[idx++] = UniqueIdTbl[loc & 0x1f];
829 return new string(buf, 0, idx);
833 public override object UnderlyingObject {
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)
843 //-----------------------------------------------
845 //-----------------------------------------------
848 /// Return true if line number information is recorded in the cache.
850 public bool HasLineInfo() {
851 return this.pageCurrent[this.idxCurrent].Document.HasLineInfo;
855 /// Return the source line number of the current node.
857 public int LineNumber {
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;
868 /// Return the source line position of the current node.
870 public int LinePosition {
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 //-----------------------------------------------
883 //-----------------------------------------------
886 /// Get hashcode based on current position of the navigator.
888 public int GetPositionHashCode() {
889 return this.idxCurrent ^ this.idxParent;
893 /// Return true if navigator is positioned to an element having the specified name.
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)
903 return this.pageCurrent[this.idxCurrent].ElementMatch(this.atomizedLocalName, namespaceURI);
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).
910 public bool IsContentKindMatch(XPathNodeType typ) {
911 return (((1 << (int) this.pageCurrent[this.idxCurrent].NodeType) & GetContentKindMask(typ)) != 0);
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).
918 public bool IsKindMatch(XPathNodeType typ) {
919 return (((1 << (int) this.pageCurrent[this.idxCurrent].NodeType) & GetKindMask(typ)) != 0);
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.
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