File: System\Xml\Xsl\Runtime\TreeIterators.cs
Web Access
Project: src\src\libraries\System.Private.Xml\src\System.Private.Xml.csproj (System.Private.Xml)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.ComponentModel;
using System.Diagnostics;
using System.Xml.XPath;
 
namespace System.Xml.Xsl.Runtime
{
    /// <summary>
    /// Iterate over all descendant content nodes according to XPath descendant axis rules.
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public struct DescendantIterator
    {
        private XmlNavigatorFilter _filter;
        private XPathNavigator? _navCurrent, _navEnd;
        private bool _hasFirst;
 
        /// <summary>
        /// Initialize the DescendantIterator (no possibility of duplicates).
        /// </summary>
        public void Create(XPathNavigator input, XmlNavigatorFilter filter, bool orSelf)
        {
            // Save input node as current node
            _navCurrent = XmlQueryRuntime.SyncToNavigator(_navCurrent, input);
            _filter = filter;
 
            // Position navEnd to the node at which the descendant scan should terminate
            if (input.NodeType == XPathNodeType.Root)
            {
                _navEnd = null;
            }
            else
            {
                _navEnd = XmlQueryRuntime.SyncToNavigator(_navEnd, input);
                _navEnd.MoveToNonDescendant();
            }
 
            // If self node matches, then return it first
            _hasFirst = (orSelf && !_filter.IsFiltered(_navCurrent));
        }
 
        /// <summary>
        /// Position this iterator to the next descendant node.  Return false if there are no more descendant nodes.
        /// Return true if the Current property is set to the next node in the iteration.
        /// </summary>
        public bool MoveNext()
        {
            if (_hasFirst)
            {
                _hasFirst = false;
                return true;
            }
            return (_filter.MoveToFollowing(_navCurrent!, _navEnd));
        }
 
        /// <summary>
        /// Return the current result navigator.  This is only defined after MoveNext() has returned true.
        /// </summary>
        public XPathNavigator Current
        {
            get { return _navCurrent!; }
        }
    }
 
 
    /// <summary>
    /// Iterate over all descendant content nodes according to XPath descendant axis rules.  Eliminate duplicates by not
    /// querying over nodes that are contained in the subtree of the previous node.
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public struct DescendantMergeIterator
    {
        private XmlNavigatorFilter _filter;
        private XPathNavigator _navCurrent, _navRoot, _navEnd;
        private IteratorState _state;
        private bool _orSelf;
 
        private enum IteratorState
        {
            NoPrevious = 0,
            NeedCurrent,
            NeedDescendant,
        }
 
        /// <summary>
        /// Initialize the DescendantIterator (merge multiple sets of descendant nodes in document order and remove duplicates).
        /// </summary>
        public void Create(XmlNavigatorFilter filter, bool orSelf)
        {
            _filter = filter;
            _state = IteratorState.NoPrevious;
            _orSelf = orSelf;
        }
 
        /// <summary>
        /// Position this iterator to the next descendant node.  Return IteratorResult.NoMoreNodes if there are no more
        /// descendant nodes.  Return IteratorResult.NeedInputNode if the next input node needs to be fetched.
        /// Return IteratorResult.HaveCurrent if the Current property is set to the next node in the iteration.
        /// </summary>
        public IteratorResult MoveNext(XPathNavigator input)
        {
            if (_state != IteratorState.NeedDescendant)
            {
                if (input == null)
                    return IteratorResult.NoMoreNodes;
 
                // Descendants of the input node will be duplicates if the input node is in the subtree
                // of the previous root.
                if (_state != IteratorState.NoPrevious && _navRoot.IsDescendant(input))
                    return IteratorResult.NeedInputNode;
 
                // Save input node as current node and end of input's tree in navEnd
                _navCurrent = XmlQueryRuntime.SyncToNavigator(_navCurrent, input);
                _navRoot = XmlQueryRuntime.SyncToNavigator(_navRoot, input);
                _navEnd = XmlQueryRuntime.SyncToNavigator(_navEnd, input);
                _navEnd.MoveToNonDescendant();
 
                _state = IteratorState.NeedDescendant;
 
                // If self node matches, then return it
                if (_orSelf && !_filter.IsFiltered(input))
                    return IteratorResult.HaveCurrentNode;
            }
 
            if (_filter.MoveToFollowing(_navCurrent, _navEnd))
                return IteratorResult.HaveCurrentNode;
 
            // No more descendants, so transition to NeedCurrent state and get the next input node
            _state = IteratorState.NeedCurrent;
            return IteratorResult.NeedInputNode;
        }
 
        /// <summary>
        /// Return the current result navigator.  This is only defined after MoveNext() has returned true or IteratorResult.HaveCurrentNode.
        /// </summary>
        public XPathNavigator Current
        {
            get { return _navCurrent; }
        }
    }
 
 
    /// <summary>
    /// Iterate over matching parent node according to XPath parent axis rules.
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public struct ParentIterator
    {
        private XPathNavigator _navCurrent;
        private bool _haveCurrent;
 
        /// <summary>
        /// Initialize the ParentIterator.
        /// </summary>
        public void Create(XPathNavigator context, XmlNavigatorFilter filter)
        {
            // Save context node as current node
            _navCurrent = XmlQueryRuntime.SyncToNavigator(_navCurrent, context);
 
            // Attempt to find a matching parent node
            _haveCurrent = (_navCurrent.MoveToParent()) && (!filter.IsFiltered(_navCurrent));
        }
 
        /// <summary>
        /// Return true if a matching parent node exists and set Current property.  Otherwise, return false
        /// (Current property is undefined).
        /// </summary>
        public bool MoveNext()
        {
            if (_haveCurrent)
            {
                _haveCurrent = false;
                return true;
            }
 
            // Iteration is complete
            return false;
        }
 
        /// <summary>
        /// Return the current result navigator.  This is only defined after MoveNext() has returned true.
        /// </summary>
        public XPathNavigator Current
        {
            get { return _navCurrent; }
        }
    }
 
 
    /// <summary>
    /// Iterate over all ancestor nodes according to XPath ancestor axis rules, returning nodes in reverse
    /// document order.
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public struct AncestorIterator
    {
        private XmlNavigatorFilter _filter;
        private XPathNavigator _navCurrent;
        private bool _haveCurrent;
 
        /// <summary>
        /// Initialize the AncestorIterator.
        /// </summary>
        public void Create(XPathNavigator context, XmlNavigatorFilter filter, bool orSelf)
        {
            _filter = filter;
 
            // Save context node as current node
            _navCurrent = XmlQueryRuntime.SyncToNavigator(_navCurrent, context);
 
            // If self node matches, then next call to MoveNext() should return it
            // Otherwise, MoveNext() will fetch next ancestor
            _haveCurrent = (orSelf && !_filter.IsFiltered(_navCurrent));
        }
 
        /// <summary>
        /// Position the iterator on the next matching ancestor node.  Return true if such a node exists and
        /// set Current property.  Otherwise, return false (Current property is undefined).
        /// </summary>
        public bool MoveNext()
        {
            if (_haveCurrent)
            {
                _haveCurrent = false;
                return true;
            }
 
            while (_navCurrent.MoveToParent())
            {
                if (!_filter.IsFiltered(_navCurrent))
                    return true;
            }
 
            // Iteration is complete
            return false;
        }
 
        /// <summary>
        /// Return the current result navigator.  This is only defined after MoveNext() has returned true.
        /// </summary>
        public XPathNavigator Current
        {
            get { return _navCurrent; }
        }
    }
 
 
    /// <summary>
    /// Iterate over all ancestor nodes according to XPath ancestor axis rules, but return the nodes in document order.
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public struct AncestorDocOrderIterator
    {
        private XmlNavigatorStack _stack;
        private XPathNavigator _navCurrent;
 
        /// <summary>
        /// Initialize the AncestorDocOrderIterator (return ancestor nodes in document order, no possibility of duplicates).
        /// </summary>
        public void Create(XPathNavigator context, XmlNavigatorFilter filter, bool orSelf)
        {
            AncestorIterator wrapped = default;
            wrapped.Create(context, filter, orSelf);
            _stack.Reset();
 
            // Fetch all ancestor nodes in reverse document order and push them onto the stack
            while (wrapped.MoveNext())
                _stack.Push(wrapped.Current.Clone());
        }
 
        /// <summary>
        /// Return true if the Current property is set to the next Ancestor node in document order.
        /// </summary>
        public bool MoveNext()
        {
            if (_stack.IsEmpty)
                return false;
 
            _navCurrent = _stack.Pop();
            return true;
        }
 
        /// <summary>
        /// Return the current result navigator.  This is only defined after MoveNext() has returned true.
        /// </summary>
        public XPathNavigator Current
        {
            get { return _navCurrent; }
        }
    }
 
 
    /// <summary>
    /// Iterate over all following nodes according to XPath following axis rules.  These rules specify that
    /// descendants are not included, even though they follow the starting node in document order.  For the
    /// "true" following axis, see FollowingIterator.
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public struct XPathFollowingIterator
    {
        private XmlNavigatorFilter _filter;
        private XPathNavigator _navCurrent;
        private bool _needFirst;
 
        /// <summary>
        /// Initialize the XPathFollowingIterator (no possibility of duplicates).
        /// </summary>
        public void Create(XPathNavigator input, XmlNavigatorFilter filter)
        {
            // Save input node as current node
            _navCurrent = XmlQueryRuntime.SyncToNavigator(_navCurrent, input);
            _filter = filter;
            _needFirst = true;
        }
 
        /// <summary>
        /// Position this iterator to the next following node.  Return false if there are no more following nodes.
        /// Return true if the Current property is set to the next node in the iteration.
        /// </summary>
        public bool MoveNext()
        {
            if (_needFirst)
            {
                if (!MoveFirst(_filter, _navCurrent))
                    return false;
 
                _needFirst = false;
                return true;
            }
 
            return _filter.MoveToFollowing(_navCurrent, null);
        }
 
        /// <summary>
        /// Return the current result navigator.  This is only defined after MoveNext() has returned true.
        /// </summary>
        public XPathNavigator Current
        {
            get { return _navCurrent; }
        }
 
        /// <summary>
        /// Position "nav" to the matching node which follows it in document order but is not a descendant node.
        /// Return false if this is no such matching node.
        /// </summary>
        internal static bool MoveFirst(XmlNavigatorFilter filter, XPathNavigator nav)
        {
            // Attributes and namespace nodes include descendants of their owner element in the set of following nodes
            if (nav.NodeType == XPathNodeType.Attribute || nav.NodeType == XPathNodeType.Namespace)
            {
                if (!nav.MoveToParent())
                {
                    // Floating attribute or namespace node that has no following nodes
                    return false;
                }
 
                if (!filter.MoveToFollowing(nav, null))
                {
                    // No matching following nodes
                    return false;
                }
            }
            else
            {
                // XPath spec doesn't include descendants of the input node in the following axis
                if (!nav.MoveToNonDescendant())
                    // No following nodes
                    return false;
 
                // If the sibling does not match the node-test, find the next following node that does
                if (filter.IsFiltered(nav))
                {
                    if (!filter.MoveToFollowing(nav, null))
                    {
                        // No matching following nodes
                        return false;
                    }
                }
            }
 
            // Success
            return true;
        }
    }
 
 
    /// <summary>
    /// Iterate over all following nodes according to XPath following axis rules.  Merge multiple sets of following nodes
    /// in document order and remove duplicates.
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public struct XPathFollowingMergeIterator
    {
        private XmlNavigatorFilter _filter;
        private IteratorState _state;
        private XPathNavigator _navCurrent, _navNext;
 
        private enum IteratorState
        {
            NeedCandidateCurrent = 0,
            HaveCandidateCurrent,
            HaveCurrentNeedNext,
            HaveCurrentHaveNext,
            HaveCurrentNoNext,
        };
 
        /// <summary>
        /// Initialize the XPathFollowingMergeIterator (merge multiple sets of following nodes in document order and remove duplicates).
        /// </summary>
        public void Create(XmlNavigatorFilter filter)
        {
            _filter = filter;
            _state = IteratorState.NeedCandidateCurrent;
        }
 
        /// <summary>
        /// Position this iterator to the next following node.  Prune by finding the first input node in
        /// document order that has no other input nodes in its subtree.  All other input nodes should be
        /// discarded.  Return IteratorResult.NeedInputNode if the next input node needs to be fetched
        /// first.  Return IteratorResult.HaveCurrent if the Current property is set to the next node in the
        /// iteration.
        /// </summary>
        public IteratorResult MoveNext(XPathNavigator input)
        {
            switch (_state)
            {
                case IteratorState.NeedCandidateCurrent:
                    // If there are no more input nodes, then iteration is complete
                    if (input == null)
                        return IteratorResult.NoMoreNodes;
 
                    // Save input node as current node
                    _navCurrent = XmlQueryRuntime.SyncToNavigator(_navCurrent, input);
 
                    // Still must check next input node to see if is a descendant of this one
                    _state = IteratorState.HaveCandidateCurrent;
                    return IteratorResult.NeedInputNode;
 
                case IteratorState.HaveCandidateCurrent:
                    // If there are no more input nodes,
                    if (input == null)
                    {
                        // Then candidate node has been selected, and there are no further input nodes
                        _state = IteratorState.HaveCurrentNoNext;
                        return MoveFirst();
                    }
 
                    // If input node is in the subtree of the candidate node, then use the input node instead
                    if (_navCurrent.IsDescendant(input))
                        goto case IteratorState.NeedCandidateCurrent;
 
                    // Found node on which to perform following scan.  Now skip past all input nodes in the same document.
                    _state = IteratorState.HaveCurrentNeedNext;
                    goto case IteratorState.HaveCurrentNeedNext;
 
                case IteratorState.HaveCurrentNeedNext:
                    // If there are no more input nodes,
                    if (input == null)
                    {
                        // Then candidate node has been selected, and there are no further input nodes
                        _state = IteratorState.HaveCurrentNoNext;
                        return MoveFirst();
                    }
 
                    // Skip input node unless it's in a different document than the node on which the following scan was performed
                    if (_navCurrent.ComparePosition(input) != XmlNodeOrder.Unknown)
                        return IteratorResult.NeedInputNode;
 
                    // Next node is in a different document, so save it
                    _navNext = XmlQueryRuntime.SyncToNavigator(_navNext, input);
                    _state = IteratorState.HaveCurrentHaveNext;
                    return MoveFirst();
            }
 
            if (!_filter.MoveToFollowing(_navCurrent, null))
                return MoveFailed();
 
            return IteratorResult.HaveCurrentNode;
        }
 
        /// <summary>
        /// Return the current result navigator.  This is only defined after MoveNext() has returned true or IteratorResult.HaveCurrentNode.
        /// </summary>
        public XPathNavigator Current
        {
            get { return _navCurrent; }
        }
 
        /// <summary>
        /// Called when an attempt to move to a following node failed.  If a Next node exists, then make that the new
        /// candidate current node.  Otherwise, iteration is complete.
        /// </summary>
        private IteratorResult MoveFailed()
        {
            XPathNavigator navTemp;
            Debug.Assert(_state == IteratorState.HaveCurrentHaveNext || _state == IteratorState.HaveCurrentNoNext);
 
            if (_state == IteratorState.HaveCurrentNoNext)
            {
                // No more nodes, so iteration is complete
                _state = IteratorState.NeedCandidateCurrent;
                return IteratorResult.NoMoreNodes;
            }
 
            // Make next node the new candidate node
            _state = IteratorState.HaveCandidateCurrent;
 
            // Swap navigators in order to sometimes avoid creating clones
            navTemp = _navCurrent;
            _navCurrent = _navNext;
            _navNext = navTemp;
 
            return IteratorResult.NeedInputNode;
        }
 
        /// <summary>
        /// Position this.navCurrent to the node which follows it in document order but is not a descendant node.
        /// </summary>
        private IteratorResult MoveFirst()
        {
            Debug.Assert(_state == IteratorState.HaveCurrentHaveNext || _state == IteratorState.HaveCurrentNoNext);
 
            if (!XPathFollowingIterator.MoveFirst(_filter, _navCurrent))
                return MoveFailed();
 
            return IteratorResult.HaveCurrentNode;
        }
    }
 
 
    /// <summary>
    /// Iterate over all content-typed nodes which precede the starting node in document order.  Return nodes
    /// in reverse document order.
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public struct PrecedingIterator
    {
        private XmlNavigatorStack _stack;
        private XPathNavigator _navCurrent;
 
        /// <summary>
        /// Initialize the PrecedingIterator (no possibility of duplicates).
        /// </summary>
        public void Create(XPathNavigator context, XmlNavigatorFilter filter)
        {
            // Start at root, which is always first node in the document
            _navCurrent = XmlQueryRuntime.SyncToNavigator(_navCurrent, context);
            _navCurrent.MoveToRoot();
            _stack.Reset();
 
            // If root node is not the ending node,
            if (!_navCurrent.IsSamePosition(context))
            {
                // Push root onto the stack if it is not filtered
                if (!filter.IsFiltered(_navCurrent))
                    _stack.Push(_navCurrent.Clone());
 
                // Push all matching nodes onto stack
                while (filter.MoveToFollowing(_navCurrent, context))
                    _stack.Push(_navCurrent.Clone());
            }
        }
 
        /// <summary>
        /// Return true if the Current property is set to the next Preceding node in reverse document order.
        /// </summary>
        public bool MoveNext()
        {
            if (_stack.IsEmpty)
                return false;
 
            _navCurrent = _stack.Pop();
            return true;
        }
 
        /// <summary>
        /// Return the current result navigator.  This is only defined after MoveNext() has returned true.
        /// </summary>
        public XPathNavigator Current
        {
            get { return _navCurrent; }
        }
    }
 
 
    /// <summary>
    /// Iterate over all preceding nodes according to XPath preceding axis rules, returning nodes in reverse
    /// document order.  These rules specify that ancestors are not included, even though they precede the
    /// starting node in document order.  For the "true" preceding axis, see PrecedingIterator.
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public struct XPathPrecedingIterator
    {
        private XmlNavigatorStack _stack;
        private XPathNavigator _navCurrent;
 
        /// <summary>
        /// Initialize the XPathPrecedingIterator (no possibility of duplicates).
        /// </summary>
        public void Create(XPathNavigator context, XmlNavigatorFilter filter)
        {
            XPathPrecedingDocOrderIterator wrapped = default;
            wrapped.Create(context, filter);
            _stack.Reset();
 
            // Fetch all preceding nodes in document order and push them onto the stack
            while (wrapped.MoveNext())
                _stack.Push(wrapped.Current.Clone());
        }
 
        /// <summary>
        /// Return true if the Current property is set to the next Preceding node in reverse document order.
        /// </summary>
        public bool MoveNext()
        {
            if (_stack.IsEmpty)
                return false;
 
            _navCurrent = _stack.Pop();
            return true;
        }
 
        /// <summary>
        /// Return the current result navigator.  This is only defined after MoveNext() has returned true.
        /// </summary>
        public XPathNavigator Current
        {
            get { return _navCurrent; }
        }
    }
 
 
    /// <summary>
    /// Iterate over all preceding nodes according to XPath preceding axis rules, returning nodes in document order.
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public struct XPathPrecedingDocOrderIterator
    {
        private XmlNavigatorFilter _filter;
        private XPathNavigator _navCurrent;
        private XmlNavigatorStack _navStack;
 
        /// <summary>
        /// Initialize the XPathPrecedingDocOrderIterator (return preceding nodes in document order, no possibility of duplicates).
        /// </summary>
        public void Create(XPathNavigator input, XmlNavigatorFilter filter)
        {
            // Save input node as current node
            _navCurrent = XmlQueryRuntime.SyncToNavigator(_navCurrent, input);
            _filter = filter;
            PushAncestors();
        }
 
        /// <summary>
        /// Position this iterator to the next preceding node.  Return false if there are no more preceding nodes.
        /// Return true if the Current property is set to the next node in the iteration.
        /// </summary>
        public bool MoveNext()
        {
            if (!_navStack.IsEmpty)
            {
                while (true)
                {
                    // Move to the next matching node that is before the top node on the stack in document order
                    if (_filter.MoveToFollowing(_navCurrent, _navStack.Peek()))
                        // Found match
                        return true;
 
                    // Do not include ancestor nodes as part of the preceding axis
                    _navCurrent.MoveTo(_navStack.Pop());
 
                    // No more preceding matches possible
                    if (_navStack.IsEmpty)
                        break;
                }
            }
 
            return false;
        }
 
        /// <summary>
        /// Return the current result navigator.  This is only defined after MoveNext() has returned true or
        /// IteratorResult.HaveCurrentNode.
        /// </summary>
        public XPathNavigator Current
        {
            get { return _navCurrent; }
        }
 
        /// <summary>
        /// Push all ancestors of this.navCurrent onto a stack.  The set of preceding nodes should not contain any of these
        /// ancestors.
        /// </summary>
        private void PushAncestors()
        {
            _navStack.Reset();
            do
            {
                _navStack.Push(_navCurrent.Clone());
            }
            while (_navCurrent.MoveToParent());
 
            // Pop the root of the tree, since MoveToFollowing calls will never return it
            _navStack.Pop();
        }
    }
 
 
    /// <summary>
    /// Iterate over all preceding nodes according to XPath preceding axis rules, except that nodes are always
    /// returned in document order.  Merge multiple sets of preceding nodes in document order and remove duplicates.
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public struct XPathPrecedingMergeIterator
    {
        private XmlNavigatorFilter _filter;
        private IteratorState _state;
        private XPathNavigator _navCurrent, _navNext;
        private XmlNavigatorStack _navStack;
 
        private enum IteratorState
        {
            NeedCandidateCurrent = 0,
            HaveCandidateCurrent,
            HaveCurrentHaveNext,
            HaveCurrentNoNext,
        }
 
        /// <summary>
        /// Initialize the XPathPrecedingMergeIterator (merge multiple sets of preceding nodes in document order and remove duplicates).
        /// </summary>
        public void Create(XmlNavigatorFilter filter)
        {
            _filter = filter;
            _state = IteratorState.NeedCandidateCurrent;
        }
 
        /// <summary>
        /// Position this iterator to the next preceding node in document order.  Discard all input nodes
        /// that are followed by another input node in the same document.  This leaves one node per document from
        /// which the complete set of preceding nodes can be derived without possibility of duplicates.
        /// Return IteratorResult.NeedInputNode if the next input node needs to be fetched first.  Return
        /// IteratorResult.HaveCurrent if the Current property is set to the next node in the iteration.
        /// </summary>
        public IteratorResult MoveNext(XPathNavigator input)
        {
            switch (_state)
            {
                case IteratorState.NeedCandidateCurrent:
                    // If there are no more input nodes, then iteration is complete
                    if (input == null)
                        return IteratorResult.NoMoreNodes;
 
                    // Save input node as current node
                    _navCurrent = XmlQueryRuntime.SyncToNavigator(_navCurrent, input);
 
                    // Scan for additional input nodes within the same document (since they are after navCurrent in docorder)
                    _state = IteratorState.HaveCandidateCurrent;
                    return IteratorResult.NeedInputNode;
 
                case IteratorState.HaveCandidateCurrent:
                    // If there are no more input nodes,
                    if (input == null)
                    {
                        // Then candidate node has been selected, and there are no further input nodes
                        _state = IteratorState.HaveCurrentNoNext;
                    }
                    else
                    {
                        // If the input node is in the same document as the current node,
                        if (_navCurrent.ComparePosition(input) != XmlNodeOrder.Unknown)
                        {
                            // Then update the current node and get the next input node
                            _navCurrent = XmlQueryRuntime.SyncToNavigator(_navCurrent, input);
                            return IteratorResult.NeedInputNode;
                        }
 
                        // Save the input node as navNext
                        _navNext = XmlQueryRuntime.SyncToNavigator(_navNext, input);
                        _state = IteratorState.HaveCurrentHaveNext;
                    }
                    PushAncestors();
                    break;
            }
 
            if (!_navStack.IsEmpty)
            {
                while (true)
                {
                    // Move to the next matching node that is before the top node on the stack in document order
                    if (_filter.MoveToFollowing(_navCurrent, _navStack.Peek()))
                        // Found match
                        return IteratorResult.HaveCurrentNode;
 
                    // Do not include ancestor nodes as part of the preceding axis
                    _navCurrent.MoveTo(_navStack.Pop());
 
                    // No more preceding matches possible
                    if (_navStack.IsEmpty)
                        break;
                }
            }
 
            if (_state == IteratorState.HaveCurrentNoNext)
            {
                // No more nodes, so iteration is complete
                _state = IteratorState.NeedCandidateCurrent;
                return IteratorResult.NoMoreNodes;
            }
 
            // Make next node the current node and start trying to find input node greatest in docorder
            _navCurrent = XmlQueryRuntime.SyncToNavigator(_navCurrent, _navNext);
            _state = IteratorState.HaveCandidateCurrent;
            return IteratorResult.HaveCurrentNode;
        }
 
        /// <summary>
        /// Return the current result navigator.  This is only defined after MoveNext() has returned true or
        /// IteratorResult.HaveCurrentNode.
        /// </summary>
        public XPathNavigator Current
        {
            get { return _navCurrent; }
        }
 
        /// <summary>
        /// Push all ancestors of this.navCurrent onto a stack.  The set of preceding nodes should not contain any of these
        /// ancestors.
        /// </summary>
        private void PushAncestors()
        {
            Debug.Assert(_state == IteratorState.HaveCurrentHaveNext || _state == IteratorState.HaveCurrentNoNext);
 
            _navStack.Reset();
            do
            {
                _navStack.Push(_navCurrent.Clone());
            }
            while (_navCurrent.MoveToParent());
 
            // Pop the root of the tree, since MoveToFollowing calls will never return it
            _navStack.Pop();
        }
    }
 
 
    /// <summary>
    /// Iterate over these nodes in document order (filtering out those that do not match the filter test):
    ///   1. Starting node
    ///   2. All content-typed nodes which follow the starting node until the ending node is reached
    ///   3. Ending node
    ///
    /// If the starting node is the same node as the ending node, iterate over the singleton node.
    /// If the starting node is after the ending node, or is in a different document, iterate to the
    /// end of the document.
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public struct NodeRangeIterator
    {
        private XmlNavigatorFilter _filter;
        private XPathNavigator _navCurrent, _navEnd;
        private IteratorState _state;
 
        private enum IteratorState
        {
            HaveCurrent,
            NeedCurrent,
            HaveCurrentNoNext,
            NoNext,
        }
 
        /// <summary>
        /// Initialize the NodeRangeIterator (no possibility of duplicates).
        /// </summary>
        public void Create(XPathNavigator start, XmlNavigatorFilter filter, XPathNavigator end)
        {
            // Save start node as current node and save ending node
            _navCurrent = XmlQueryRuntime.SyncToNavigator(_navCurrent, start);
            _navEnd = XmlQueryRuntime.SyncToNavigator(_navEnd, end);
            _filter = filter;
 
            if (start.IsSamePosition(end))
            {
                // Start is end, so only return node if it is not filtered
                _state = !filter.IsFiltered(start) ? IteratorState.HaveCurrentNoNext : IteratorState.NoNext;
            }
            else
            {
                // Return nodes until end is reached
                _state = !filter.IsFiltered(start) ? IteratorState.HaveCurrent : IteratorState.NeedCurrent;
            }
        }
 
        /// <summary>
        /// Position this iterator to the next following node.  Return false if there are no more following nodes,
        /// or if the end node has been reached.  Return true if the Current property is set to the next node in
        /// the iteration.
        /// </summary>
        public bool MoveNext()
        {
            switch (_state)
            {
                case IteratorState.HaveCurrent:
                    _state = IteratorState.NeedCurrent;
                    return true;
 
                case IteratorState.NeedCurrent:
                    // Move to next following node which matches
                    if (!_filter.MoveToFollowing(_navCurrent, _navEnd))
                    {
                        // No more nodes unless ending node matches
                        if (_filter.IsFiltered(_navEnd))
                        {
                            _state = IteratorState.NoNext;
                            return false;
                        }
 
                        _navCurrent.MoveTo(_navEnd);
                        _state = IteratorState.NoNext;
                    }
                    return true;
 
                case IteratorState.HaveCurrentNoNext:
                    _state = IteratorState.NoNext;
                    return true;
            }
 
            Debug.Assert(_state == IteratorState.NoNext, $"Illegal state: {_state}");
            return false;
        }
 
        /// <summary>
        /// Return the current result navigator.  This is only defined after MoveNext() has returned true.
        /// </summary>
        public XPathNavigator Current
        {
            get { return _navCurrent; }
        }
    }
}