File: System\Xml\XPath\Internal\CompiledXPathExpr.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;
using System.Diagnostics;
using System.Globalization;
using System.Xml;
using System.Xml.XPath;
using System.Xml.Xsl;
 
namespace MS.Internal.Xml.XPath
{
    internal class CompiledXpathExpr : XPathExpression
    {
        private Query _query;
        private readonly string _expr;
        private bool _needContext;
 
        internal CompiledXpathExpr(Query query, string expression, bool needContext)
        {
            _query = query;
            _expr = expression;
            _needContext = needContext;
        }
 
        internal Query QueryTree
        {
            get
            {
                if (_needContext)
                {
                    throw XPathException.Create(SR.Xp_NoContext);
                }
                return _query;
            }
        }
 
        public override string Expression
        {
            get { return _expr; }
        }
 
        public virtual void CheckErrors()
        {
            Debug.Assert(_query != null, "In case of error in XPath we create ErrorXPathExpression");
        }
 
        public override void AddSort(object expr, IComparer comparer)
        {
            // sort makes sense only when we are dealing with a query that
            // returns a nodeset.
            Query evalExpr;
            string? query = expr as string;
            if (query != null)
            {
                evalExpr = new QueryBuilder().Build(query, out _needContext); // this will throw if expr is invalid
            }
            else
            {
                CompiledXpathExpr? xpathExpr = expr as CompiledXpathExpr;
                if (xpathExpr != null)
                {
                    evalExpr = xpathExpr.QueryTree;
                }
                else
                {
                    throw XPathException.Create(SR.Xp_BadQueryObject);
                }
            }
            SortQuery? sortQuery = _query as SortQuery;
            if (sortQuery == null)
            {
                _query = sortQuery = new SortQuery(_query);
            }
            sortQuery.AddSort(evalExpr, comparer);
        }
 
        public override void AddSort(object expr, XmlSortOrder order, XmlCaseOrder caseOrder, string lang, XmlDataType dataType)
        {
            AddSort(expr, new XPathComparerHelper(order, caseOrder, lang, dataType));
        }
 
        public override XPathExpression Clone()
        {
            return new CompiledXpathExpr(Query.Clone(_query), _expr, _needContext);
        }
 
        public override void SetContext(XmlNamespaceManager nsManager)
        {
            SetContext((IXmlNamespaceResolver)nsManager);
        }
 
        public override void SetContext(IXmlNamespaceResolver? nsResolver)
        {
            XsltContext? xsltContext = nsResolver as XsltContext;
            if (xsltContext == null)
            {
                nsResolver ??= new XmlNamespaceManager(new NameTable());
                xsltContext = new UndefinedXsltContext(nsResolver);
            }
            _query.SetXsltContext(xsltContext);
 
            _needContext = false;
        }
 
        public override XPathResultType ReturnType { get { return _query.StaticType; } }
 
        private sealed class UndefinedXsltContext : XsltContext
        {
            private readonly IXmlNamespaceResolver _nsResolver;
 
            public UndefinedXsltContext(IXmlNamespaceResolver nsResolver) : base(/*dummy*/false)
            {
                _nsResolver = nsResolver;
            }
            //----- Namespace support -----
            public override string DefaultNamespace
            {
                get { return string.Empty; }
            }
            public override string LookupNamespace(string prefix)
            {
                Debug.Assert(prefix != null);
                if (prefix.Length == 0)
                {
                    return string.Empty;
                }
                string? ns = _nsResolver.LookupNamespace(prefix);
                if (ns == null)
                {
                    throw XPathException.Create(SR.XmlUndefinedAlias, prefix);
                }
                return ns;
            }
            //----- XsltContext support -----
            public override IXsltContextVariable ResolveVariable(string prefix, string name)
            {
                throw XPathException.Create(SR.Xp_UndefinedXsltContext);
            }
            public override IXsltContextFunction ResolveFunction(string prefix, string name, XPathResultType[] ArgTypes)
            {
                throw XPathException.Create(SR.Xp_UndefinedXsltContext);
            }
            public override bool Whitespace { get { return false; } }
            public override bool PreserveWhitespace(XPathNavigator node) { return false; }
            public override int CompareDocument(string baseUri, string nextbaseUri)
            {
                return string.CompareOrdinal(baseUri, nextbaseUri);
            }
        }
    }
 
    internal sealed class XPathComparerHelper : IComparer
    {
        private readonly XmlSortOrder _order;
        private readonly XmlCaseOrder _caseOrder;
        private readonly CultureInfo _cinfo;
        private readonly XmlDataType _dataType;
 
        public XPathComparerHelper(XmlSortOrder order, XmlCaseOrder caseOrder, string? lang, XmlDataType dataType)
        {
            if (lang == null)
            {
                _cinfo = CultureInfo.CurrentCulture;
            }
            else
            {
                try
                {
                    _cinfo = new CultureInfo(lang);
                }
                catch (System.ArgumentException)
                {
                    throw;  // Throwing an XsltException would be a breaking change
                }
            }
 
            if (order == XmlSortOrder.Descending)
            {
                if (caseOrder == XmlCaseOrder.LowerFirst)
                {
                    caseOrder = XmlCaseOrder.UpperFirst;
                }
                else if (caseOrder == XmlCaseOrder.UpperFirst)
                {
                    caseOrder = XmlCaseOrder.LowerFirst;
                }
            }
 
            _order = order;
            _caseOrder = caseOrder;
            _dataType = dataType;
        }
 
        public int Compare(object? x, object? y)
        {
            switch (_dataType)
            {
                case XmlDataType.Text:
                    string? s1 = Convert.ToString(x, _cinfo);
                    string? s2 = Convert.ToString(y, _cinfo);
                    int result = _cinfo.CompareInfo.Compare(s1, s2, _caseOrder != XmlCaseOrder.None ? CompareOptions.IgnoreCase : CompareOptions.None);
 
                    if (result != 0 || _caseOrder == XmlCaseOrder.None)
                        return (_order == XmlSortOrder.Ascending) ? result : -result;
 
                    // If we came this far, it means that strings s1 and s2 are
                    // equal to each other when case is ignored. Now it's time to check
                    // and see if they differ in case only and take into account the user
                    // requested case order for sorting purposes.
                    result = _cinfo.CompareInfo.Compare(s1, s2);
                    return (_caseOrder == XmlCaseOrder.LowerFirst) ? result : -result;
 
                case XmlDataType.Number:
                    double r1 = XmlConvert.ToXPathDouble(x);
                    double r2 = XmlConvert.ToXPathDouble(y);
                    result = r1.CompareTo(r2);
                    return (_order == XmlSortOrder.Ascending) ? result : -result;
 
                default:
                    // dataType doesn't support any other value
                    throw new InvalidOperationException(SR.Xml_InvalidOperation);
            }
        } // Compare ()
    } // class XPathComparerHelper
}