File: System\Xml\XPath\Internal\Query.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;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Xml;
using System.Xml.XPath;
using System.Xml.Xsl;
 
namespace MS.Internal.Xml.XPath
{
    // See comments to QueryBuilder.Props
    // Not all of them are used currently
    internal enum QueryProps
    {
        None = 0x00,
        Position = 0x01,
        Count = 0x02,
        Cached = 0x04,
        Reverse = 0x08,
        Merge = 0x10,
    };
 
 
    // Turn off DebuggerDisplayAttribute. in subclasses of Query.
    // Calls to Current in the XPathNavigator.DebuggerDisplayProxy may change state or throw
    [DebuggerDisplay("{ToString()}")]
    internal abstract class Query : ResettableIterator
    {
        public Query() { }
        protected Query(Query other) : base(other) { }
 
        // -- XPathNodeIterator --
        public override bool MoveNext() { return Advance() != null; }
        public override int Count
        {
            get
            {
                // Query can be ordered in reverse order. So we can't assume like base.Count that last node has greatest position.
                if (count == -1)
                {
                    Query clone = (Query)this.Clone();
                    clone.Reset();
                    count = 0;
                    while (clone.MoveNext()) count++;
                }
                return count;
            }
        }
 
        // -------------------- Query ------------------
        public virtual void SetXsltContext(XsltContext context) { }
 
        public abstract object Evaluate(XPathNodeIterator nodeIterator);
        public abstract XPathNavigator? Advance();
 
        public virtual XPathNavigator? MatchNode(XPathNavigator? current)
        {
            throw XPathException.Create(SR.Xp_InvalidPattern);
        }
 
        public virtual double XsltDefaultPriority { get { return 0.5; } }
        public abstract XPathResultType StaticType { get; }
        public virtual QueryProps Properties { get { return QueryProps.Merge; } }
 
        // ----------------- Helper methods -------------
        [return: NotNullIfNotNull(nameof(input))]
        public static Query? Clone(Query? input)
        {
            if (input != null)
            {
                return (Query)input.Clone();
            }
            return null;
        }
 
        [return: NotNullIfNotNull(nameof(input))]
        protected static XPathNodeIterator? Clone(XPathNodeIterator? input)
        {
            if (input != null)
            {
                return input.Clone();
            }
            return null;
        }
 
        [return: NotNullIfNotNull(nameof(input))]
        protected static XPathNavigator? Clone(XPathNavigator? input)
        {
            if (input != null)
            {
                return input.Clone();
            }
            return null;
        }
 
        // -----------------------------------------------------
        // Set of methods to support insertion to sorted buffer.
        // buffer is always sorted here
 
        public static bool Insert(List<XPathNavigator> buffer, XPathNavigator nav)
        {
            int l = 0;
            int r = buffer.Count;
 
            // In most cases nodes are already sorted.
            // This means that nav often will be equal or after then last item in the buffer
            // So let's check this first.
            if (r != 0)
            {
                switch (CompareNodes(buffer[r - 1], nav))
                {
                    case XmlNodeOrder.Same:
                        return false;
                    case XmlNodeOrder.Before:
                        buffer.Add(nav.Clone());
                        return true;
                    default:
                        r--;
                        break;
                }
            }
 
            while (l < r)
            {
                int m = GetMedian(l, r);
                switch (CompareNodes(buffer[m], nav))
                {
                    case XmlNodeOrder.Same:
                        return false;
                    case XmlNodeOrder.Before:
                        l = m + 1;
                        break;
                    default:
                        r = m;
                        break;
                }
            }
            buffer.Insert(l, nav.Clone());
            return true;
        }
 
        private static int GetMedian(int l, int r)
        {
            Debug.Assert(0 <= l && l < r);
            return (int)(((uint)l + (uint)r) >> 1);
        }
 
        public static XmlNodeOrder CompareNodes(XPathNavigator l, XPathNavigator r)
        {
            XmlNodeOrder cmp = l.ComparePosition(r);
            if (cmp == XmlNodeOrder.Unknown)
            {
                XPathNavigator copy = l.Clone();
                copy.MoveToRoot();
                string baseUriL = copy.BaseURI;
                if (!copy.MoveTo(r))
                {
                    copy = r.Clone();
                }
                copy.MoveToRoot();
                string baseUriR = copy.BaseURI;
                int cmpBase = string.CompareOrdinal(baseUriL, baseUriR);
                cmp = (
                    cmpBase < 0 ? XmlNodeOrder.Before :
                    cmpBase > 0 ? XmlNodeOrder.After :
                    /*default*/   XmlNodeOrder.Unknown
                );
            }
            return cmp;
        }
 
        // =================== XPathResultType_Navigator ======================
        // In v.1.0 and v.1.1 XPathResultType.Navigator is defined == to XPathResultType.String
        // This is source for multiple bugs or additional type casts.
        // To fix all of them in one change in v.2 we internally use one more value:
        public const XPathResultType XPathResultType_Navigator = (XPathResultType)4;
        // The biggest challenge in this change is preserve backward compatibility with v.1.1
        // To achieve this in all places where we accept from or report to user XPathResultType.
        // On my best knowledge this happens only in XsltContext.ResolveFunction() / IXsltContextFunction.ReturnType
 
 
        protected static XPathResultType GetXPathType(object value)
        {
            if (value is XPathNodeIterator) return XPathResultType.NodeSet;
            if (value is string) return XPathResultType.String;
            if (value is double) return XPathResultType.Number;
            if (value is bool) return XPathResultType.Boolean;
            Debug.Assert(value is XPathNavigator, "Unknown value type");
            return XPathResultType_Navigator;
        }
    }
}