//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // // Microsoft //------------------------------------------------------------------------------ using System; using System.Diagnostics; using System.Text; using System.Xml; using System.Xml.XPath; using System.Xml.Schema; namespace MS.Internal.Xml.Cache { /// /// Library of XPathNode helper routines. /// internal abstract class XPathNodeHelper { /// /// Return chain of namespace nodes. If specified node has no local namespaces, then 0 will be /// returned. Otherwise, the first node in the chain is guaranteed to be a local namespace (its /// parent is this node). Subsequent nodes may not have the same node as parent, so the caller will /// need to test the parent in order to terminate a search that processes only local namespaces. /// public static int GetLocalNamespaces(XPathNode[] pageElem, int idxElem, out XPathNode[] pageNmsp) { if (pageElem[idxElem].HasNamespaceDecls) { // Only elements have namespace nodes Debug.Assert(pageElem[idxElem].NodeType == XPathNodeType.Element); return pageElem[idxElem].Document.LookupNamespaces(pageElem, idxElem, out pageNmsp); } pageNmsp = null; return 0; } /// /// Return chain of in-scope namespace nodes for nodes of type Element. Nodes in the chain might not /// have this element as their parent. Since the xmlns:xml namespace node is always in scope, this /// method will never return 0 if the specified node is an element. /// public static int GetInScopeNamespaces(XPathNode[] pageElem, int idxElem, out XPathNode[] pageNmsp) { XPathDocument doc; // Only elements have namespace nodes if (pageElem[idxElem].NodeType == XPathNodeType.Element) { doc = pageElem[idxElem].Document; // Walk ancestors, looking for an ancestor that has at least one namespace declaration while (!pageElem[idxElem].HasNamespaceDecls) { idxElem = pageElem[idxElem].GetParent(out pageElem); if (idxElem == 0) { // There are no namespace nodes declared on ancestors, so return xmlns:xml node return doc.GetXmlNamespaceNode(out pageNmsp); } } // Return chain of in-scope namespace nodes return doc.LookupNamespaces(pageElem, idxElem, out pageNmsp); } pageNmsp = null; return 0; } /// /// Return the first attribute of the specified node. If no attribute exist, do not /// set pageNode or idxNode and return false. /// public static bool GetFirstAttribute(ref XPathNode[] pageNode, ref int idxNode) { Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)"); if (pageNode[idxNode].HasAttribute) { GetChild(ref pageNode, ref idxNode); Debug.Assert(pageNode[idxNode].NodeType == XPathNodeType.Attribute); return true; } return false; } /// /// Return the next attribute sibling of the specified node. If the node is not itself an /// attribute, or if there are no siblings, then do not set pageNode or idxNode and return false. /// public static bool GetNextAttribute(ref XPathNode[] pageNode, ref int idxNode) { XPathNode[] page; int idx; Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)"); idx = pageNode[idxNode].GetSibling(out page); if (idx != 0 && page[idx].NodeType == XPathNodeType.Attribute) { pageNode = page; idxNode = idx; return true; } return false; } /// /// Return the first content-typed child of the specified node. If the node has no children, or /// if the node is not content-typed, then do not set pageNode or idxNode and return false. /// public static bool GetContentChild(ref XPathNode[] pageNode, ref int idxNode) { XPathNode[] page = pageNode; int idx = idxNode; Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)"); if (page[idx].HasContentChild) { GetChild(ref page, ref idx); // Skip past attribute children while (page[idx].NodeType == XPathNodeType.Attribute) { idx = page[idx].GetSibling(out page); Debug.Assert(idx != 0); } pageNode = page; idxNode = idx; return true; } return false; } /// /// Return the next content-typed sibling of the specified node. If the node has no siblings, or /// if the node is not content-typed, then do not set pageNode or idxNode and return false. /// public static bool GetContentSibling(ref XPathNode[] pageNode, ref int idxNode) { XPathNode[] page = pageNode; int idx = idxNode; Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)"); if (!page[idx].IsAttrNmsp) { idx = page[idx].GetSibling(out page); if (idx != 0) { pageNode = page; idxNode = idx; return true; } } return false; } /// /// Return the parent of the specified node. If the node has no parent, do not set pageNode /// or idxNode and return false. /// public static bool GetParent(ref XPathNode[] pageNode, ref int idxNode) { XPathNode[] page = pageNode; int idx = idxNode; Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)"); idx = page[idx].GetParent(out page); if (idx != 0) { pageNode = page; idxNode = idx; return true; } return false; } /// /// Return a location integer that can be easily compared with other locations from the same document /// in order to determine the relative document order of two nodes. /// public static int GetLocation(XPathNode[] pageNode, int idxNode) { Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)"); Debug.Assert(idxNode <= UInt16.MaxValue); Debug.Assert(pageNode[0].PageInfo.PageNumber <= Int16.MaxValue); return (pageNode[0].PageInfo.PageNumber << 16) | idxNode; } /// /// Return the first element child of the specified node that has the specified name. If no such child exists, /// then do not set pageNode or idxNode and return false. Assume that the localName has been atomized with respect /// to this document's name table, but not the namespaceName. /// public static bool GetElementChild(ref XPathNode[] pageNode, ref int idxNode, string localName, string namespaceName) { XPathNode[] page = pageNode; int idx = idxNode; Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)"); // Only check children if at least one element child exists if (page[idx].HasElementChild) { GetChild(ref page, ref idx); Debug.Assert(idx != 0); // Find element with specified localName and namespaceName do { if (page[idx].ElementMatch(localName, namespaceName)) { pageNode = page; idxNode = idx; return true; } idx = page[idx].GetSibling(out page); } while (idx != 0); } return false; } /// /// Return a following sibling element of the specified node that has the specified name. If no such /// sibling exists, or if the node is not content-typed, then do not set pageNode or idxNode and /// return false. Assume that the localName has been atomized with respect to this document's name table, /// but not the namespaceName. /// public static bool GetElementSibling(ref XPathNode[] pageNode, ref int idxNode, string localName, string namespaceName) { XPathNode[] page = pageNode; int idx = idxNode; Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)"); // Elements should not be returned as "siblings" of attributes (namespaces don't link to elements, so don't need to check them) if (page[idx].NodeType != XPathNodeType.Attribute) { while (true) { idx = page[idx].GetSibling(out page); if (idx == 0) break; if (page[idx].ElementMatch(localName, namespaceName)) { pageNode = page; idxNode = idx; return true; } } } return false; } /// /// Return the first child of the specified node that has the specified type (must be a content type). If no such /// child exists, then do not set pageNode or idxNode and return false. /// public static bool GetContentChild(ref XPathNode[] pageNode, ref int idxNode, XPathNodeType typ) { XPathNode[] page = pageNode; int idx = idxNode; int mask; Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)"); // Only check children if at least one content-typed child exists if (page[idx].HasContentChild) { mask = XPathNavigator.GetContentKindMask(typ); GetChild(ref page, ref idx); do { if (((1 << (int) page[idx].NodeType) & mask) != 0) { // Never return attributes, as Attribute is not a content type if (typ == XPathNodeType.Attribute) return false; pageNode = page; idxNode = idx; return true; } idx = page[idx].GetSibling(out page); } while (idx != 0); } return false; } /// /// Return a following sibling of the specified node that has the specified type. If no such /// sibling exists, then do not set pageNode or idxNode and return false. /// public static bool GetContentSibling(ref XPathNode[] pageNode, ref int idxNode, XPathNodeType typ) { XPathNode[] page = pageNode; int idx = idxNode; int mask = XPathNavigator.GetContentKindMask(typ); Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)"); if (page[idx].NodeType != XPathNodeType.Attribute) { while (true) { idx = page[idx].GetSibling(out page); if (idx == 0) break; if (((1 << (int) page[idx].NodeType) & mask) != 0) { Debug.Assert(typ != XPathNodeType.Attribute && typ != XPathNodeType.Namespace); pageNode = page; idxNode = idx; return true; } } } return false; } /// /// Return the first preceding sibling of the specified node. If no such sibling exists, then do not set /// pageNode or idxNode and return false. /// public static bool GetPreviousContentSibling(ref XPathNode[] pageNode, ref int idxNode) { XPathNode[] pageParent = pageNode, pagePrec, pageAnc; int idxParent = idxNode, idxPrec, idxAnc; Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)"); Debug.Assert(pageNode[idxNode].NodeType != XPathNodeType.Attribute); // Since nodes are laid out in document order on pages, the algorithm is: // 1. Get parent of current node // 2. If no parent, then there is no previous sibling, so return false // 3. Get node that immediately precedes the current node in document order // 4. If preceding node is parent, then there is no previous sibling, so return false // 5. Walk ancestors of preceding node, until parent of current node is found idxParent = pageParent[idxParent].GetParent(out pageParent); if (idxParent != 0) { idxPrec = idxNode - 1; if (idxPrec == 0) { // Need to get previous page pagePrec = pageNode[0].PageInfo.PreviousPage; idxPrec = pagePrec.Length - 1; } else { // Previous node is on the same page pagePrec = pageNode; } // If parent node is previous node, then no previous sibling if (idxParent == idxPrec && pageParent == pagePrec) return false; // Find child of parent node by walking ancestor chain pageAnc = pagePrec; idxAnc = idxPrec; do { pagePrec = pageAnc; idxPrec = idxAnc; idxAnc = pageAnc[idxAnc].GetParent(out pageAnc); Debug.Assert(idxAnc != 0 && pageAnc != null); } while (idxAnc != idxParent || pageAnc != pageParent); // We found the previous sibling, but if it's an attribute node, then return false if (pagePrec[idxPrec].NodeType != XPathNodeType.Attribute) { pageNode = pagePrec; idxNode = idxPrec; return true; } } return false; } /// /// Return a previous sibling element of the specified node that has the specified name. If no such /// sibling exists, or if the node is not content-typed, then do not set pageNode or idxNode and /// return false. Assume that the localName has been atomized with respect to this document's name table, /// but not the namespaceName. /// public static bool GetPreviousElementSibling(ref XPathNode[] pageNode, ref int idxNode, string localName, string namespaceName) { XPathNode[] page = pageNode; int idx = idxNode; Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)"); if (page[idx].NodeType != XPathNodeType.Attribute) { while (true) { if (!GetPreviousContentSibling(ref page, ref idx)) break; if (page[idx].ElementMatch(localName, namespaceName)) { pageNode = page; idxNode = idx; return true; } } } return false; } /// /// Return a previous sibling of the specified node that has the specified type. If no such /// sibling exists, then do not set pageNode or idxNode and return false. /// public static bool GetPreviousContentSibling(ref XPathNode[] pageNode, ref int idxNode, XPathNodeType typ) { XPathNode[] page = pageNode; int idx = idxNode; int mask = XPathNavigator.GetContentKindMask(typ); Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)"); while (true) { if (!GetPreviousContentSibling(ref page, ref idx)) break; if (((1 << (int) page[idx].NodeType) & mask) != 0) { pageNode = page; idxNode = idx; return true; } } return false; } /// /// Return the attribute of the specified node that has the specified name. If no such attribute exists, /// then do not set pageNode or idxNode and return false. Assume that the localName has been atomized with respect /// to this document's name table, but not the namespaceName. /// public static bool GetAttribute(ref XPathNode[] pageNode, ref int idxNode, string localName, string namespaceName) { XPathNode[] page = pageNode; int idx = idxNode; Debug.Assert(pageNode != null && idxNode != 0, "Cannot pass null argument(s)"); // Find attribute with specified localName and namespaceName if (page[idx].HasAttribute) { GetChild(ref page, ref idx); do { if (page[idx].NameMatch(localName, namespaceName)) { pageNode = page; idxNode = idx; return true; } idx = page[idx].GetSibling(out page); } while (idx != 0 && page[idx].NodeType == XPathNodeType.Attribute); } return false; } /// /// Get the next non-virtual (not collapsed text, not namespaces) node that follows the specified node in document order. /// If no such node exists, then do not set pageNode or idxNode and return false. /// public static bool GetFollowing(ref XPathNode[] pageNode, ref int idxNode) { XPathNode[] page = pageNode; int idx = idxNode; do { // Next non-virtual node is in next slot within the page if (++idx < page[0].PageInfo.NodeCount) { pageNode = page; idxNode = idx; return true; } // Otherwise, start at the beginning of the next page page = page[0].PageInfo.NextPage; idx = 0; } while (page != null); return false; } /// /// Get the next element node that: /// 1. Follows the current node in document order (includes descendants, unlike XPath following axis) /// 2. Precedes the ending node in document order (if pageEnd is null, then all following nodes in the document are considered) /// 3. Has the specified QName /// If no such element exists, then do not set pageCurrent or idxCurrent and return false. /// Assume that the localName has been atomized with respect to this document's name table, but not the namespaceName. /// public static bool GetElementFollowing(ref XPathNode[] pageCurrent, ref int idxCurrent, XPathNode[] pageEnd, int idxEnd, string localName, string namespaceName) { XPathNode[] page = pageCurrent; int idx = idxCurrent; Debug.Assert(pageCurrent != null && idxCurrent != 0, "Cannot pass null argument(s)"); // If current node is an element having a matching name, if (page[idx].NodeType == XPathNodeType.Element && (object) page[idx].LocalName == (object) localName) { // Then follow similar element name pointers int idxPageEnd = 0; int idxPageCurrent; if (pageEnd != null) { idxPageEnd = pageEnd[0].PageInfo.PageNumber; idxPageCurrent = page[0].PageInfo.PageNumber; // If ending node is <= starting node in document order, then scan to end of document if (idxPageCurrent > idxPageEnd || (idxPageCurrent == idxPageEnd && idx >= idxEnd)) pageEnd = null; } while (true) { idx = page[idx].GetSimilarElement(out page); if (idx == 0) break; // Only scan to ending node if (pageEnd != null) { idxPageCurrent = page[0].PageInfo.PageNumber; if (idxPageCurrent > idxPageEnd) break; if (idxPageCurrent == idxPageEnd && idx >= idxEnd) break; } if ((object) page[idx].LocalName == (object) localName && page[idx].NamespaceUri == namespaceName) goto FoundNode; } return false; } // Since nodes are laid out in document order on pages, scan them sequentially // rather than following links. idx++; do { if ((object) page == (object) pageEnd && idx <= idxEnd) { // Only scan to termination point while (idx != idxEnd) { if (page[idx].ElementMatch(localName, namespaceName)) goto FoundNode; idx++; } break; } else { // Scan all nodes in the page while (idx < page[0].PageInfo.NodeCount) { if (page[idx].ElementMatch(localName, namespaceName)) goto FoundNode; idx++; } } page = page[0].PageInfo.NextPage; idx = 1; } while (page != null); return false; FoundNode: // Found match pageCurrent = page; idxCurrent = idx; return true; } /// /// Get the next node that: /// 1. Follows the current node in document order (includes descendants, unlike XPath following axis) /// 2. Precedes the ending node in document order (if pageEnd is null, then all following nodes in the document are considered) /// 3. Has the specified XPathNodeType (but Attributes and Namespaces never match) /// If no such node exists, then do not set pageCurrent or idxCurrent and return false. /// public static bool GetContentFollowing(ref XPathNode[] pageCurrent, ref int idxCurrent, XPathNode[] pageEnd, int idxEnd, XPathNodeType typ) { XPathNode[] page = pageCurrent; int idx = idxCurrent; int mask = XPathNavigator.GetContentKindMask(typ); Debug.Assert(pageCurrent != null && idxCurrent != 0, "Cannot pass null argument(s)"); Debug.Assert(typ != XPathNodeType.Text, "Text should be handled by GetTextFollowing in order to take into account collapsed text."); Debug.Assert(page[idx].NodeType != XPathNodeType.Attribute, "Current node should never be an attribute or namespace--caller should handle this case."); // Since nodes are laid out in document order on pages, scan them sequentially // rather than following sibling/child/parent links. idx++; do { if ((object) page == (object) pageEnd && idx <= idxEnd) { // Only scan to termination point while (idx != idxEnd) { if (((1 << (int) page[idx].NodeType) & mask) != 0) goto FoundNode; idx++; } break; } else { // Scan all nodes in the page while (idx < page[0].PageInfo.NodeCount) { if (((1 << (int) page[idx].NodeType) & mask) != 0) goto FoundNode; idx++; } } page = page[0].PageInfo.NextPage; idx = 1; } while (page != null); return false; FoundNode: Debug.Assert(!page[idx].IsAttrNmsp, "GetContentFollowing should never return attributes or namespaces."); // Found match pageCurrent = page; idxCurrent = idx; return true; } /// /// Scan all nodes that follow the current node in document order, but precede the ending node in document order. /// Return two types of nodes with non-null text: /// 1. Element parents of collapsed text nodes (since it is the element parent that has the collapsed text) /// 2. Non-collapsed text nodes /// If no such node exists, then do not set pageCurrent or idxCurrent and return false. /// public static bool GetTextFollowing(ref XPathNode[] pageCurrent, ref int idxCurrent, XPathNode[] pageEnd, int idxEnd) { XPathNode[] page = pageCurrent; int idx = idxCurrent; Debug.Assert(pageCurrent != null && idxCurrent != 0, "Cannot pass null argument(s)"); Debug.Assert(!page[idx].IsAttrNmsp, "Current node should never be an attribute or namespace--caller should handle this case."); // Since nodes are laid out in document order on pages, scan them sequentially // rather than following sibling/child/parent links. idx++; do { if ((object) page == (object) pageEnd && idx <= idxEnd) { // Only scan to termination point while (idx != idxEnd) { if (page[idx].IsText || (page[idx].NodeType == XPathNodeType.Element && page[idx].HasCollapsedText)) goto FoundNode; idx++; } break; } else { // Scan all nodes in the page while (idx < page[0].PageInfo.NodeCount) { if (page[idx].IsText || (page[idx].NodeType == XPathNodeType.Element && page[idx].HasCollapsedText)) goto FoundNode; idx++; } } page = page[0].PageInfo.NextPage; idx = 1; } while (page != null); return false; FoundNode: // Found match pageCurrent = page; idxCurrent = idx; return true; } /// /// Get the next non-virtual (not collapsed text, not namespaces) node that follows the specified node in document order, /// but is not a descendant. If no such node exists, then do not set pageNode or idxNode and return false. /// public static bool GetNonDescendant(ref XPathNode[] pageNode, ref int idxNode) { XPathNode[] page = pageNode; int idx = idxNode; // Get page, idx at which to end sequential scan of nodes do { // If the current node has a sibling, if (page[idx].HasSibling) { // Then that is the first non-descendant pageNode = page; idxNode = page[idx].GetSibling(out pageNode); return true; } // Otherwise, try finding a sibling at the parent level idx = page[idx].GetParent(out page); } while (idx != 0); return false; } /// /// Return the page and index of the first child (attribute or content) of the specified node. /// private static void GetChild(ref XPathNode[] pageNode, ref int idxNode) { Debug.Assert(pageNode[idxNode].HasAttribute || pageNode[idxNode].HasContentChild, "Caller must check HasAttribute/HasContentChild on parent before calling GetChild."); Debug.Assert(pageNode[idxNode].HasAttribute || !pageNode[idxNode].HasCollapsedText, "Text child is virtualized and therefore is not present in the physical node page."); if (++idxNode >= pageNode.Length) { // Child is first node on next page pageNode = pageNode[0].PageInfo.NextPage; idxNode = 1; } // Else child is next node on this page } } }