1 //------------------------------------------------------------------------------
2 // <copyright file="XPathNodeHelper.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">[....]</owner>
6 //------------------------------------------------------------------------------
8 using System.Diagnostics;
11 using System.Xml.XPath;
12 using System.Xml.Schema;
14 namespace MS.Internal.Xml.Cache {
17 /// Library of XPathNode helper routines.
19 internal abstract class XPathNodeHelper {
22 /// Return chain of namespace nodes. If specified node has no local namespaces, then 0 will be
23 /// returned. Otherwise, the first node in the chain is guaranteed to be a local namespace (its
24 /// parent is this node). Subsequent nodes may not have the same node as parent, so the caller will
25 /// need to test the parent in order to terminate a search that processes only local namespaces.
27 public static int GetLocalNamespaces(XPathNode[] pageElem, int idxElem, out XPathNode[] pageNmsp) {
28 if (pageElem[idxElem].HasNamespaceDecls) {
29 // Only elements have namespace nodes
30 Debug.Assert(pageElem[idxElem].NodeType == XPathNodeType.Element);
31 return pageElem[idxElem].Document.LookupNamespaces(pageElem, idxElem, out pageNmsp);
38 /// Return chain of in-scope namespace nodes for nodes of type Element. Nodes in the chain might not
39 /// have this element as their parent. Since the xmlns:xml namespace node is always in scope, this
40 /// method will never return 0 if the specified node is an element.
42 public static int GetInScopeNamespaces(XPathNode[] pageElem, int idxElem, out XPathNode[] pageNmsp) {
45 // Only elements have namespace nodes
46 if (pageElem[idxElem].NodeType == XPathNodeType.Element) {
47 doc = pageElem[idxElem].Document;
49 // Walk ancestors, looking for an ancestor that has at least one namespace declaration
50 while (!pageElem[idxElem].HasNamespaceDecls) {
51 idxElem = pageElem[idxElem].GetParent(out pageElem);
53 // There are no namespace nodes declared on ancestors, so return xmlns:xml node
54 return doc.GetXmlNamespaceNode(out pageNmsp);
57 // Return chain of in-scope namespace nodes
58 return doc.LookupNamespaces(pageElem, idxElem, out pageNmsp);
65 /// Return the first attribute of the specified node. If no attribute exist, do not
66 /// set pageNode or idxNode and return false.
68 public static bool GetFirstAttribute(ref XPathNode[] pageNode, ref int idxNode) {
69 Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)");
71 if (pageNode[idxNode].HasAttribute) {
72 GetChild(ref pageNode, ref idxNode);
73 Debug.Assert(pageNode[idxNode].NodeType == XPathNodeType.Attribute);
80 /// Return the next attribute sibling of the specified node. If the node is not itself an
81 /// attribute, or if there are no siblings, then do not set pageNode or idxNode and return false.
83 public static bool GetNextAttribute(ref XPathNode[] pageNode, ref int idxNode) {
86 Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)");
88 idx = pageNode[idxNode].GetSibling(out page);
89 if (idx != 0 && page[idx].NodeType == XPathNodeType.Attribute) {
98 /// Return the first content-typed child of the specified node. If the node has no children, or
99 /// if the node is not content-typed, then do not set pageNode or idxNode and return false.
101 public static bool GetContentChild(ref XPathNode[] pageNode, ref int idxNode) {
102 XPathNode[] page = pageNode;
104 Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)");
106 if (page[idx].HasContentChild) {
107 GetChild(ref page, ref idx);
109 // Skip past attribute children
110 while (page[idx].NodeType == XPathNodeType.Attribute) {
111 idx = page[idx].GetSibling(out page);
112 Debug.Assert(idx != 0);
123 /// Return the next content-typed sibling of the specified node. If the node has no siblings, or
124 /// if the node is not content-typed, then do not set pageNode or idxNode and return false.
126 public static bool GetContentSibling(ref XPathNode[] pageNode, ref int idxNode) {
127 XPathNode[] page = pageNode;
129 Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)");
131 if (!page[idx].IsAttrNmsp) {
132 idx = page[idx].GetSibling(out page);
143 /// Return the parent of the specified node. If the node has no parent, do not set pageNode
144 /// or idxNode and return false.
146 public static bool GetParent(ref XPathNode[] pageNode, ref int idxNode) {
147 XPathNode[] page = pageNode;
149 Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)");
151 idx = page[idx].GetParent(out page);
161 /// Return a location integer that can be easily compared with other locations from the same document
162 /// in order to determine the relative document order of two nodes.
164 public static int GetLocation(XPathNode[] pageNode, int idxNode) {
165 Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)");
166 Debug.Assert(idxNode <= UInt16.MaxValue);
167 Debug.Assert(pageNode[0].PageInfo.PageNumber <= Int16.MaxValue);
168 return (pageNode[0].PageInfo.PageNumber << 16) | idxNode;
172 /// Return the first element child of the specified node that has the specified name. If no such child exists,
173 /// then do not set pageNode or idxNode and return false. Assume that the localName has been atomized with respect
174 /// to this document's name table, but not the namespaceName.
176 public static bool GetElementChild(ref XPathNode[] pageNode, ref int idxNode, string localName, string namespaceName) {
177 XPathNode[] page = pageNode;
179 Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)");
181 // Only check children if at least one element child exists
182 if (page[idx].HasElementChild) {
183 GetChild(ref page, ref idx);
184 Debug.Assert(idx != 0);
186 // Find element with specified localName and namespaceName
188 if (page[idx].ElementMatch(localName, namespaceName)) {
193 idx = page[idx].GetSibling(out page);
201 /// Return a following sibling element of the specified node that has the specified name. If no such
202 /// sibling exists, or if the node is not content-typed, then do not set pageNode or idxNode and
203 /// return false. Assume that the localName has been atomized with respect to this document's name table,
204 /// but not the namespaceName.
206 public static bool GetElementSibling(ref XPathNode[] pageNode, ref int idxNode, string localName, string namespaceName) {
207 XPathNode[] page = pageNode;
209 Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)");
211 // Elements should not be returned as "siblings" of attributes (namespaces don't link to elements, so don't need to check them)
212 if (page[idx].NodeType != XPathNodeType.Attribute) {
214 idx = page[idx].GetSibling(out page);
219 if (page[idx].ElementMatch(localName, namespaceName)) {
231 /// Return the first child of the specified node that has the specified type (must be a content type). If no such
232 /// child exists, then do not set pageNode or idxNode and return false.
234 public static bool GetContentChild(ref XPathNode[] pageNode, ref int idxNode, XPathNodeType typ) {
235 XPathNode[] page = pageNode;
238 Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)");
240 // Only check children if at least one content-typed child exists
241 if (page[idx].HasContentChild) {
242 mask = XPathNavigator.GetContentKindMask(typ);
244 GetChild(ref page, ref idx);
246 if (((1 << (int) page[idx].NodeType) & mask) != 0) {
247 // Never return attributes, as Attribute is not a content type
248 if (typ == XPathNodeType.Attribute)
256 idx = page[idx].GetSibling(out page);
265 /// Return a following sibling of the specified node that has the specified type. If no such
266 /// sibling exists, then do not set pageNode or idxNode and return false.
268 public static bool GetContentSibling(ref XPathNode[] pageNode, ref int idxNode, XPathNodeType typ) {
269 XPathNode[] page = pageNode;
271 int mask = XPathNavigator.GetContentKindMask(typ);
272 Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)");
274 if (page[idx].NodeType != XPathNodeType.Attribute) {
276 idx = page[idx].GetSibling(out page);
281 if (((1 << (int) page[idx].NodeType) & mask) != 0) {
282 Debug.Assert(typ != XPathNodeType.Attribute && typ != XPathNodeType.Namespace);
294 /// Return the first preceding sibling of the specified node. If no such sibling exists, then do not set
295 /// pageNode or idxNode and return false.
297 public static bool GetPreviousContentSibling(ref XPathNode[] pageNode, ref int idxNode) {
298 XPathNode[] pageParent = pageNode, pagePrec, pageAnc;
299 int idxParent = idxNode, idxPrec, idxAnc;
300 Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)");
301 Debug.Assert(pageNode[idxNode].NodeType != XPathNodeType.Attribute);
303 // Since nodes are laid out in document order on pages, the algorithm is:
304 // 1. Get parent of current node
305 // 2. If no parent, then there is no previous sibling, so return false
306 // 3. Get node that immediately precedes the current node in document order
307 // 4. If preceding node is parent, then there is no previous sibling, so return false
308 // 5. Walk ancestors of preceding node, until parent of current node is found
309 idxParent = pageParent[idxParent].GetParent(out pageParent);
310 if (idxParent != 0) {
311 idxPrec = idxNode - 1;
313 // Need to get previous page
314 pagePrec = pageNode[0].PageInfo.PreviousPage;
315 idxPrec = pagePrec.Length - 1;
318 // Previous node is on the same page
322 // If parent node is previous node, then no previous sibling
323 if (idxParent == idxPrec && pageParent == pagePrec)
326 // Find child of parent node by walking ancestor chain
332 idxAnc = pageAnc[idxAnc].GetParent(out pageAnc);
333 Debug.Assert(idxAnc != 0 && pageAnc != null);
335 while (idxAnc != idxParent || pageAnc != pageParent);
337 // We found the previous sibling, but if it's an attribute node, then return false
338 if (pagePrec[idxPrec].NodeType != XPathNodeType.Attribute) {
349 /// Return a previous sibling element of the specified node that has the specified name. If no such
350 /// sibling exists, or if the node is not content-typed, then do not set pageNode or idxNode and
351 /// return false. Assume that the localName has been atomized with respect to this document's name table,
352 /// but not the namespaceName.
354 public static bool GetPreviousElementSibling(ref XPathNode[] pageNode, ref int idxNode, string localName, string namespaceName) {
355 XPathNode[] page = pageNode;
357 Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)");
359 if (page[idx].NodeType != XPathNodeType.Attribute) {
361 if (!GetPreviousContentSibling(ref page, ref idx))
364 if (page[idx].ElementMatch(localName, namespaceName)) {
376 /// Return a previous sibling of the specified node that has the specified type. If no such
377 /// sibling exists, then do not set pageNode or idxNode and return false.
379 public static bool GetPreviousContentSibling(ref XPathNode[] pageNode, ref int idxNode, XPathNodeType typ) {
380 XPathNode[] page = pageNode;
382 int mask = XPathNavigator.GetContentKindMask(typ);
383 Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)");
386 if (!GetPreviousContentSibling(ref page, ref idx))
389 if (((1 << (int) page[idx].NodeType) & mask) != 0) {
400 /// Return the attribute of the specified node that has the specified name. If no such attribute exists,
401 /// then do not set pageNode or idxNode and return false. Assume that the localName has been atomized with respect
402 /// to this document's name table, but not the namespaceName.
404 public static bool GetAttribute(ref XPathNode[] pageNode, ref int idxNode, string localName, string namespaceName) {
405 XPathNode[] page = pageNode;
407 Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)");
409 // Find attribute with specified localName and namespaceName
410 if (page[idx].HasAttribute) {
411 GetChild(ref page, ref idx);
413 if (page[idx].NameMatch(localName, namespaceName)) {
418 idx = page[idx].GetSibling(out page);
420 while (idx != 0 && page[idx].NodeType == XPathNodeType.Attribute);
427 /// Get the next non-virtual (not collapsed text, not namespaces) node that follows the specified node in document order.
428 /// If no such node exists, then do not set pageNode or idxNode and return false.
430 public static bool GetFollowing(ref XPathNode[] pageNode, ref int idxNode) {
431 XPathNode[] page = pageNode;
435 // Next non-virtual node is in next slot within the page
436 if (++idx < page[0].PageInfo.NodeCount) {
442 // Otherwise, start at the beginning of the next page
443 page = page[0].PageInfo.NextPage;
446 while (page != null);
452 /// Get the next element node that:
453 /// 1. Follows the current node in document order (includes descendants, unlike XPath following axis)
454 /// 2. Precedes the ending node in document order (if pageEnd is null, then all following nodes in the document are considered)
455 /// 3. Has the specified QName
456 /// If no such element exists, then do not set pageCurrent or idxCurrent and return false.
457 /// Assume that the localName has been atomized with respect to this document's name table, but not the namespaceName.
459 public static bool GetElementFollowing(ref XPathNode[] pageCurrent, ref int idxCurrent, XPathNode[] pageEnd, int idxEnd, string localName, string namespaceName) {
460 XPathNode[] page = pageCurrent;
461 int idx = idxCurrent;
462 Debug.Assert(pageCurrent != null && idxCurrent != 0, "Cannot pass null argument(s)");
464 // If current node is an element having a matching name,
465 if (page[idx].NodeType == XPathNodeType.Element && (object) page[idx].LocalName == (object) localName) {
466 // Then follow similar element name pointers
470 if (pageEnd != null) {
471 idxPageEnd = pageEnd[0].PageInfo.PageNumber;
472 idxPageCurrent = page[0].PageInfo.PageNumber;
474 // If ending node is <= starting node in document order, then scan to end of document
475 if (idxPageCurrent > idxPageEnd || (idxPageCurrent == idxPageEnd && idx >= idxEnd))
480 idx = page[idx].GetSimilarElement(out page);
485 // Only scan to ending node
486 if (pageEnd != null) {
487 idxPageCurrent = page[0].PageInfo.PageNumber;
488 if (idxPageCurrent > idxPageEnd)
491 if (idxPageCurrent == idxPageEnd && idx >= idxEnd)
495 if ((object) page[idx].LocalName == (object) localName && page[idx].NamespaceUri == namespaceName)
502 // Since nodes are laid out in document order on pages, scan them sequentially
503 // rather than following links.
506 if ((object) page == (object) pageEnd && idx <= idxEnd) {
507 // Only scan to termination point
508 while (idx != idxEnd) {
509 if (page[idx].ElementMatch(localName, namespaceName))
516 // Scan all nodes in the page
517 while (idx < page[0].PageInfo.NodeCount) {
518 if (page[idx].ElementMatch(localName, namespaceName))
524 page = page[0].PageInfo.NextPage;
527 while (page != null);
539 /// Get the next node that:
540 /// 1. Follows the current node in document order (includes descendants, unlike XPath following axis)
541 /// 2. Precedes the ending node in document order (if pageEnd is null, then all following nodes in the document are considered)
542 /// 3. Has the specified XPathNodeType (but Attributes and Namespaces never match)
543 /// If no such node exists, then do not set pageCurrent or idxCurrent and return false.
545 public static bool GetContentFollowing(ref XPathNode[] pageCurrent, ref int idxCurrent, XPathNode[] pageEnd, int idxEnd, XPathNodeType typ) {
546 XPathNode[] page = pageCurrent;
547 int idx = idxCurrent;
548 int mask = XPathNavigator.GetContentKindMask(typ);
549 Debug.Assert(pageCurrent != null && idxCurrent != 0, "Cannot pass null argument(s)");
550 Debug.Assert(typ != XPathNodeType.Text, "Text should be handled by GetTextFollowing in order to take into account collapsed text.");
551 Debug.Assert(page[idx].NodeType != XPathNodeType.Attribute, "Current node should never be an attribute or namespace--caller should handle this case.");
553 // Since nodes are laid out in document order on pages, scan them sequentially
554 // rather than following sibling/child/parent links.
557 if ((object) page == (object) pageEnd && idx <= idxEnd) {
558 // Only scan to termination point
559 while (idx != idxEnd) {
560 if (((1 << (int) page[idx].NodeType) & mask) != 0)
567 // Scan all nodes in the page
568 while (idx < page[0].PageInfo.NodeCount) {
569 if (((1 << (int) page[idx].NodeType) & mask) != 0)
575 page = page[0].PageInfo.NextPage;
578 while (page != null);
583 Debug.Assert(!page[idx].IsAttrNmsp, "GetContentFollowing should never return attributes or namespaces.");
592 /// Scan all nodes that follow the current node in document order, but precede the ending node in document order.
593 /// Return two types of nodes with non-null text:
594 /// 1. Element parents of collapsed text nodes (since it is the element parent that has the collapsed text)
595 /// 2. Non-collapsed text nodes
596 /// If no such node exists, then do not set pageCurrent or idxCurrent and return false.
598 public static bool GetTextFollowing(ref XPathNode[] pageCurrent, ref int idxCurrent, XPathNode[] pageEnd, int idxEnd) {
599 XPathNode[] page = pageCurrent;
600 int idx = idxCurrent;
601 Debug.Assert(pageCurrent != null && idxCurrent != 0, "Cannot pass null argument(s)");
602 Debug.Assert(!page[idx].IsAttrNmsp, "Current node should never be an attribute or namespace--caller should handle this case.");
604 // Since nodes are laid out in document order on pages, scan them sequentially
605 // rather than following sibling/child/parent links.
608 if ((object) page == (object) pageEnd && idx <= idxEnd) {
609 // Only scan to termination point
610 while (idx != idxEnd) {
611 if (page[idx].IsText || (page[idx].NodeType == XPathNodeType.Element && page[idx].HasCollapsedText))
618 // Scan all nodes in the page
619 while (idx < page[0].PageInfo.NodeCount) {
620 if (page[idx].IsText || (page[idx].NodeType == XPathNodeType.Element && page[idx].HasCollapsedText))
626 page = page[0].PageInfo.NextPage;
629 while (page != null);
641 /// Get the next non-virtual (not collapsed text, not namespaces) node that follows the specified node in document order,
642 /// but is not a descendant. If no such node exists, then do not set pageNode or idxNode and return false.
644 public static bool GetNonDescendant(ref XPathNode[] pageNode, ref int idxNode) {
645 XPathNode[] page = pageNode;
648 // Get page, idx at which to end sequential scan of nodes
650 // If the current node has a sibling,
651 if (page[idx].HasSibling) {
652 // Then that is the first non-descendant
654 idxNode = page[idx].GetSibling(out pageNode);
658 // Otherwise, try finding a sibling at the parent level
659 idx = page[idx].GetParent(out page);
667 /// Return the page and index of the first child (attribute or content) of the specified node.
669 private static void GetChild(ref XPathNode[] pageNode, ref int idxNode) {
670 Debug.Assert(pageNode[idxNode].HasAttribute || pageNode[idxNode].HasContentChild, "Caller must check HasAttribute/HasContentChild on parent before calling GetChild.");
671 Debug.Assert(pageNode[idxNode].HasAttribute || !pageNode[idxNode].HasCollapsedText, "Text child is virtualized and therefore is not present in the physical node page.");
673 if (++idxNode >= pageNode.Length) {
674 // Child is first node on next page
675 pageNode = pageNode[0].PageInfo.NextPage;
678 // Else child is next node on this page