1 //------------------------------------------------------------------------------
2 // <copyright file="XPathDocumentNavigator.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">Microsoft</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