| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682 |
- //------------------------------------------------------------------------------
- // <copyright file="XPathNodeHelper.cs" company="Microsoft">
- // Copyright (c) Microsoft Corporation. All rights reserved.
- // </copyright>
- // <owner current="true" primary="true">Microsoft</owner>
- //------------------------------------------------------------------------------
- using System;
- using System.Diagnostics;
- using System.Text;
- using System.Xml;
- using System.Xml.XPath;
- using System.Xml.Schema;
- namespace MS.Internal.Xml.Cache {
- /// <summary>
- /// Library of XPathNode helper routines.
- /// </summary>
- internal abstract class XPathNodeHelper {
- /// <summary>
- /// 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.
- /// </summary>
- 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;
- }
- /// <summary>
- /// 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.
- /// </summary>
- 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;
- }
- /// <summary>
- /// Return the first attribute of the specified node. If no attribute exist, do not
- /// set pageNode or idxNode and return false.
- /// </summary>
- 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;
- }
- /// <summary>
- /// 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.
- /// </summary>
- 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;
- }
- /// <summary>
- /// 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.
- /// </summary>
- 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;
- }
- /// <summary>
- /// 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.
- /// </summary>
- 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;
- }
- /// <summary>
- /// Return the parent of the specified node. If the node has no parent, do not set pageNode
- /// or idxNode and return false.
- /// </summary>
- 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;
- }
- /// <summary>
- /// 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.
- /// </summary>
- 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;
- }
- /// <summary>
- /// 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.
- /// </summary>
- 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;
- }
- /// <summary>
- /// 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.
- /// </summary>
- 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;
- }
- /// <summary>
- /// 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.
- /// </summary>
- 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;
- }
- /// <summary>
- /// 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.
- /// </summary>
- 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;
- }
- /// <summary>
- /// Return the first preceding sibling of the specified node. If no such sibling exists, then do not set
- /// pageNode or idxNode and return false.
- /// </summary>
- 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;
- }
- /// <summary>
- /// 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.
- /// </summary>
- 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;
- }
- /// <summary>
- /// 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.
- /// </summary>
- 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;
- }
- /// <summary>
- /// 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.
- /// </summary>
- 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;
- }
- /// <summary>
- /// 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.
- /// </summary>
- 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;
- }
- /// <summary>
- /// 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.
- /// </summary>
- 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;
- }
- /// <summary>
- /// 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.
- /// </summary>
- 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;
- }
- /// <summary>
- /// 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.
- /// </summary>
- 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;
- }
- /// <summary>
- /// 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.
- /// </summary>
- 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;
- }
- /// <summary>
- /// Return the page and index of the first child (attribute or content) of the specified node.
- /// </summary>
- 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
- }
- }
- }
|