File: System\Xml\Xsl\XsltOld\XsltCompileContext.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.Collections;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.Versioning;
using System.Security;
using System.Xml.XPath;
using System.Xml.Xsl.Runtime;
using MS.Internal.Xml.XPath;
 
namespace System.Xml.Xsl.XsltOld
{
    internal sealed class XsltCompileContext : XsltContext
    {
        private InputScopeManager? _manager;
        private Processor? _processor;
 
        // storage for the functions
        private static readonly Hashtable s_FunctionTable = CreateFunctionTable();
        private static readonly IXsltContextFunction s_FuncNodeSet = new FuncNodeSet();
        private const string f_NodeSet = "node-set";
 
        internal XsltCompileContext(InputScopeManager manager, Processor processor) : base(/*dummy*/false)
        {
            _manager = manager;
            _processor = processor;
        }
 
        internal XsltCompileContext() : base(/*dummy*/ false) { }
 
        internal void Recycle()
        {
            _manager = null;
            _processor = null;
        }
 
        internal void Reinitialize(InputScopeManager manager, Processor processor)
        {
            _manager = manager;
            _processor = processor;
        }
 
        public override int CompareDocument(string baseUri, string nextbaseUri)
        {
            return string.Compare(baseUri, nextbaseUri, StringComparison.Ordinal);
        }
 
        // Namespace support
        public override string DefaultNamespace
        {
            get { return string.Empty; }
        }
 
        public override string LookupNamespace(string prefix)
        {
            return _manager!.ResolveXPathNamespace(prefix);
        }
 
        // --------------------------- XsltContext -------------------
        //                Resolving variables and functions
 
        public override IXsltContextVariable ResolveVariable(string prefix, string name)
        {
            string namespaceURI = this.LookupNamespace(prefix);
            XmlQualifiedName qname = new XmlQualifiedName(name, namespaceURI);
            IXsltContextVariable? variable = _manager!.VariableScope.ResolveVariable(qname);
            if (variable == null)
            {
                throw XsltException.Create(SR.Xslt_InvalidVariable, qname.ToString());
            }
            return variable;
        }
 
        internal object EvaluateVariable(VariableAction variable)
        {
            object result = _processor!.GetVariableValue(variable);
            if (result == null && !variable.IsGlobal)
            {
                // This was uninitialized local variable. May be we have sutable global var too?
                VariableAction? global = _manager!.VariableScope.ResolveGlobalVariable(variable.Name!);
                if (global != null)
                {
                    result = _processor.GetVariableValue(global);
                }
            }
            if (result == null)
            {
                throw XsltException.Create(SR.Xslt_InvalidVariable, variable.Name!.ToString());
            }
            return result;
        }
 
        // Whitespace stripping support
        public override bool Whitespace
        {
            get { return _processor!.Stylesheet.Whitespace; }
        }
 
        public override bool PreserveWhitespace(XPathNavigator node)
        {
            node = node.Clone();
            node.MoveToParent();
            return _processor!.Stylesheet.PreserveWhiteSpace(_processor, node);
        }
 
        private static MethodInfo? FindBestMethod(MethodInfo[] methods, bool ignoreCase, bool publicOnly, string name, XPathResultType[]? argTypes)
        {
            int length = methods.Length;
            int free = 0;
            // restrict search to methods with the same name and requiested protection attribute
            for (int i = 0; i < length; i++)
            {
                if (string.Equals(name, methods[i].Name, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal))
                {
                    if (!publicOnly || methods[i].GetBaseDefinition().IsPublic)
                    {
                        methods[free++] = methods[i];
                    }
                }
            }
            length = free;
            if (length == 0)
            {
                // this is the only place we returning null in this function
                return null;
            }
            if (argTypes == null)
            {
                // without arg types we can't do more detailed search
                return methods[0];
            }
            // restrict search by number of parameters
            free = 0;
            for (int i = 0; i < length; i++)
            {
                if (methods[i].GetParameters().Length == argTypes.Length)
                {
                    methods[free++] = methods[i];
                }
            }
            length = free;
            if (length <= 1)
            {
                // 0 -- not method found. We have to return non-null and let it fail with correct exception on call.
                // 1 -- no reason to continue search anyway.
                return methods[0];
            }
            // restrict search by parameters type
            free = 0;
            for (int i = 0; i < length; i++)
            {
                bool match = true;
                ParameterInfo[] parameters = methods[i].GetParameters();
                for (int par = 0; par < parameters.Length; par++)
                {
                    XPathResultType required = argTypes[par];
                    if (required == XPathResultType.Any)
                    {
                        continue;                        // Any means we don't know type and can't discriminate by it
                    }
                    XPathResultType actual = GetXPathType(parameters[par].ParameterType);
                    if (
                        actual != required &&
                        actual != XPathResultType.Any   // actual arg is object and we can pass everithing here.
                    )
                    {
                        match = false;
                        break;
                    }
                }
                if (match)
                {
                    methods[free++] = methods[i];
                }
            }
            return methods[0];
        }
 
        private const BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static;
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:RequiresUnreferencedCode",
            Justification = XsltArgumentList.ExtensionObjectSuppresion)]
        private FuncExtension? GetExtensionMethod(string ns, string name, XPathResultType[]? argTypes, out object? extension)
        {
            FuncExtension? result = null;
            extension = _processor!.GetScriptObject(ns);
            if (extension != null)
            {
                MethodInfo? method = FindBestMethod(extension.GetType().GetMethods(bindingFlags), /*ignoreCase:*/true, /*publicOnly:*/false, name, argTypes);
                if (method != null)
                {
                    result = new FuncExtension(extension, method);
                }
                return result;
            }
 
            extension = _processor.GetExtensionObject(ns);
            if (extension != null)
            {
                MethodInfo? method = FindBestMethod(extension.GetType().GetMethods(bindingFlags), /*ignoreCase:*/false, /*publicOnly:*/true, name, argTypes);
                if (method != null)
                {
                    result = new FuncExtension(extension, method);
                }
                return result;
            }
 
            return null;
        }
 
        public override IXsltContextFunction ResolveFunction(string prefix, string name, XPathResultType[] argTypes)
        {
            IXsltContextFunction? func;
            if (prefix.Length == 0)
            {
                func = s_FunctionTable[name] as IXsltContextFunction;
            }
            else
            {
                string ns = this.LookupNamespace(prefix);
                if (ns == XmlReservedNs.NsMsxsl && name == f_NodeSet)
                {
                    func = s_FuncNodeSet;
                }
                else
                {
                    object? extension;
                    func = GetExtensionMethod(ns, name, argTypes, out extension);
                    if (extension == null)
                    {
                        throw XsltException.Create(SR.Xslt_ScriptInvalidPrefix, prefix);  // BugBug: It's better to say that method 'name' not found
                    }
                }
            }
            if (func == null)
            {
                throw XsltException.Create(SR.Xslt_UnknownXsltFunction, name);
            }
            if (argTypes.Length < func.Minargs || func.Maxargs < argTypes.Length)
            {
                throw XsltException.Create(SR.Xslt_WrongNumberArgs, name, argTypes.Length.ToString(CultureInfo.InvariantCulture));
            }
            return func;
        }
 
        //
        // Xslt Function Extensions to XPath
        //
        private Uri ComposeUri(string thisUri, string baseUri)
        {
            Debug.Assert(thisUri != null && baseUri != null);
            XmlResolver resolver = _processor!.Resolver;
            Uri? uriBase = null;
            if (baseUri.Length != 0)
            {
                uriBase = resolver.ResolveUri(null, baseUri);
            }
            return resolver.ResolveUri(uriBase, thisUri);
        }
 
        private XPathNodeIterator Document(object arg0, string? baseUri)
        {
            XPathNodeIterator? it = arg0 as XPathNodeIterator;
            if (it != null)
            {
                ArrayList list = new ArrayList();
                Hashtable documents = new Hashtable();
                while (it.MoveNext())
                {
                    Uri uri = ComposeUri(it.Current!.Value, baseUri ?? it.Current.BaseURI);
                    if (!documents.ContainsKey(uri))
                    {
                        documents.Add(uri, null);
                        list.Add(_processor!.GetNavigator(uri));
                    }
                }
                return new XPathArrayIterator(list);
            }
            else
            {
                return new XPathSingletonIterator(
                    _processor!.GetNavigator(
                        ComposeUri(XmlConvert.ToXPathString(arg0)!, baseUri ?? _manager!.Navigator.BaseURI)
                    )
                );
            }
        }
 
        private Hashtable BuildKeyTable(Key key, XPathNavigator root)
        {
            Hashtable keyTable = new Hashtable();
 
            string matchStr = _processor!.GetQueryExpression(key.MatchKey);
            Query matchExpr = _processor.GetCompiledQuery(key.MatchKey);
            Query useExpr = _processor.GetCompiledQuery(key.UseKey);
 
            XPathNodeIterator sel = root.SelectDescendants(XPathNodeType.All, /*matchSelf:*/ false);
 
            while (sel.MoveNext())
            {
                XPathNavigator node = sel.Current!;
                EvaluateKey(node, matchExpr, matchStr, useExpr, keyTable);
                if (node.MoveToFirstAttribute())
                {
                    do
                    {
                        EvaluateKey(node, matchExpr, matchStr, useExpr, keyTable);
                    } while (node.MoveToNextAttribute());
                    node.MoveToParent();
                }
            }
            return keyTable;
        }
 
        private static void AddKeyValue(Hashtable keyTable, string key, XPathNavigator value, bool checkDuplicates)
        {
            ArrayList? list = (ArrayList?)keyTable[key];
            if (list == null)
            {
                list = new ArrayList();
                keyTable.Add(key, list);
            }
            else
            {
                Debug.Assert(
                    value.ComparePosition((XPathNavigator?)list[list.Count - 1]) != XmlNodeOrder.Before,
                    "The way we traversing nodes should garantees node-order"
                );
                if (checkDuplicates)
                {
                    // it's possible that this value already was associated with current node
                    // but if this happened the node is last in the list of values.
                    if (value.ComparePosition((XPathNavigator?)list[list.Count - 1]) == XmlNodeOrder.Same)
                    {
                        return;
                    }
                }
                else
                {
                    Debug.Assert(
                        value.ComparePosition((XPathNavigator?)list[list.Count - 1]) != XmlNodeOrder.Same,
                        "checkDuplicates == false : We can't have duplicates"
                    );
                }
            }
            list.Add(value.Clone());
        }
 
        private static void EvaluateKey(XPathNavigator? node, Query matchExpr, string matchStr, Query useExpr, Hashtable keyTable)
        {
            try
            {
                if (matchExpr.MatchNode(node) == null)
                {
                    return;
                }
            }
            catch (XPathException)
            {
                throw XsltException.Create(SR.Xslt_InvalidPattern, matchStr);
            }
            object result = useExpr.Evaluate(new XPathSingletonIterator(node!, /*moved:*/true));
            XPathNodeIterator? it = result as XPathNodeIterator;
            if (it != null)
            {
                bool checkDuplicates = false;
                while (it.MoveNext())
                {
                    AddKeyValue(keyTable, /*key:*/it.Current!.Value!, /*value:*/node!, checkDuplicates);
                    checkDuplicates = true;
                }
            }
            else
            {
                string key = XmlConvert.ToXPathString(result)!;
                AddKeyValue(keyTable, key, /*value:*/node!, /*checkDuplicates:*/ false);
            }
        }
 
        private DecimalFormat ResolveFormatName(string? formatName)
        {
            string ns = string.Empty, local = string.Empty;
            if (formatName != null)
            {
                string prefix;
                PrefixQName.ParseQualifiedName(formatName, out prefix, out local);
                ns = LookupNamespace(prefix);
            }
            DecimalFormat? formatInfo = _processor!.RootAction!.GetDecimalFormat(new XmlQualifiedName(local, ns));
            if (formatInfo == null)
            {
                if (formatName != null)
                {
                    throw XsltException.Create(SR.Xslt_NoDecimalFormat, formatName);
                }
                formatInfo = new DecimalFormat(new NumberFormatInfo(), '#', '0', ';');
            }
            return formatInfo;
        }
 
        // see http://www.w3.org/TR/xslt#function-element-available
        private bool ElementAvailable(string qname)
        {
            string name, prefix;
            PrefixQName.ParseQualifiedName(qname, out prefix, out name);
            string ns = _manager!.ResolveXmlNamespace(prefix);
            // msxsl:script - is not an "instruction" so we return false for it.
            if (ns == XmlReservedNs.NsXslt)
            {
                return (
                    name == "apply-imports" ||
                    name == "apply-templates" ||
                    name == "attribute" ||
                    name == "call-template" ||
                    name == "choose" ||
                    name == "comment" ||
                    name == "copy" ||
                    name == "copy-of" ||
                    name == "element" ||
                    name == "fallback" ||
                    name == "for-each" ||
                    name == "if" ||
                    name == "message" ||
                    name == "number" ||
                    name == "processing-instruction" ||
                    name == "text" ||
                    name == "value-of" ||
                    name == "variable"
                );
            }
            return false;
        }
 
        // see: http://www.w3.org/TR/xslt#function-function-available
        private bool FunctionAvailable(string qname)
        {
            string name, prefix;
            PrefixQName.ParseQualifiedName(qname, out prefix, out name);
            string ns = LookupNamespace(prefix);
 
            if (ns == XmlReservedNs.NsMsxsl)
            {
                return name == f_NodeSet;
            }
            else if (ns.Length == 0)
            {
                switch (name)
                {
                    case "last":
                    case "position":
                    case "name":
                    case "namespace-uri":
                    case "local-name":
                    case "count":
                    case "id":
                    case "string":
                    case "concat":
                    case "starts-with":
                    case "contains":
                    case "substring-before":
                    case "substring-after":
                    case "substring":
                    case "string-length":
                    case "normalize-space":
                    case "translate":
                    case "boolean":
                    case "not":
                    case "true":
                    case "false":
                    case "lang":
                    case "number":
                    case "sum":
                    case "floor":
                    case "ceiling":
                    case "round":
                        return true;
 
                    // XSLT functions:
                    default:
                        return s_FunctionTable[name] != null && name != "unparsed-entity-uri";
                }
            }
            else
            {
                // Is this script or extension function?
                return GetExtensionMethod(ns, name, /*argTypes*/null, out _) != null;
            }
        }
 
        private XPathNodeIterator Current()
        {
            XPathNavigator? nav = _processor!.Current;
            if (nav != null)
            {
                return new XPathSingletonIterator(nav.Clone());
            }
            return XPathEmptyIterator.Instance;
        }
 
        private string SystemProperty(string qname)
        {
            string result = string.Empty;
 
            string prefix;
            string local;
            PrefixQName.ParseQualifiedName(qname, out prefix, out local);
 
            // verify the prefix corresponds to the Xslt namespace
            string urn = LookupNamespace(prefix);
 
            if (urn == XmlReservedNs.NsXslt)
            {
                if (local == "version")
                {
                    result = "1";
                }
                else if (local == "vendor")
                {
                    result = "Microsoft";
                }
                else if (local == "vendor-url")
                {
                    result = "http://www.microsoft.com";
                }
            }
            else
            {
                if (urn == null && prefix != null)
                {
                    // if prefix exist it has to be mapped to namespace.
                    // Can it be "" here ?
                    throw XsltException.Create(SR.Xslt_InvalidPrefix, prefix);
                }
                return string.Empty;
            }
 
            return result;
        }
 
        public static XPathResultType GetXPathType(Type type)
        {
            switch (Type.GetTypeCode(type))
            {
                case TypeCode.String:
                    return XPathResultType.String;
                case TypeCode.Boolean:
                    return XPathResultType.Boolean;
                case TypeCode.Object:
                    if (typeof(XPathNavigator).IsAssignableFrom(type) || typeof(IXPathNavigable).IsAssignableFrom(type))
                    {
                        return XPathResultType.Navigator;
                    }
                    if (typeof(XPathNodeIterator).IsAssignableFrom(type))
                    {
                        return XPathResultType.NodeSet;
                    }
                    // sdub: It be better to check that type is really object and otherwise return XPathResultType.Error
                    return XPathResultType.Any;
                case TypeCode.DateTime:
                    return XPathResultType.Error;
 
                default: /* all numeric types */
                    return XPathResultType.Number;
            }
        }
 
        // ---------------- Xslt Function Implementations -------------------
        //
        private static Hashtable CreateFunctionTable()
        {
            Hashtable ft = new Hashtable(10);
            {
                ft["current"] = new FuncCurrent();
                ft["unparsed-entity-uri"] = new FuncUnEntityUri();
                ft["generate-id"] = new FuncGenerateId();
                ft["system-property"] = new FuncSystemProp();
                ft["element-available"] = new FuncElementAvailable();
                ft["function-available"] = new FuncFunctionAvailable();
                ft["document"] = new FuncDocument();
                ft["key"] = new FuncKey();
                ft["format-number"] = new FuncFormatNumber();
            }
            return ft;
        }
 
        // + IXsltContextFunction
        //   + XsltFunctionImpl             func. name,       min/max args,      return type                args types
        //       FuncCurrent            "current"              0   0         XPathResultType.NodeSet   { }
        //       FuncUnEntityUri        "unparsed-entity-uri"  1   1         XPathResultType.String    { XPathResultType.String  }
        //       FuncGenerateId         "generate-id"          0   1         XPathResultType.String    { XPathResultType.NodeSet }
        //       FuncSystemProp         "system-property"      1   1         XPathResultType.String    { XPathResultType.String  }
        //       FuncElementAvailable   "element-available"    1   1         XPathResultType.Boolean   { XPathResultType.String  }
        //       FuncFunctionAvailable  "function-available"   1   1         XPathResultType.Boolean   { XPathResultType.String  }
        //       FuncDocument           "document"             1   2         XPathResultType.NodeSet   { XPathResultType.Any    , XPathResultType.NodeSet }
        //       FuncKey                "key"                  2   2         XPathResultType.NodeSet   { XPathResultType.String , XPathResultType.Any     }
        //       FuncFormatNumber       "format-number"        2   3         XPathResultType.String    { XPathResultType.Number , XPathResultType.String, XPathResultType.String }
        //       FuncNodeSet            "msxsl:node-set"       1   1         XPathResultType.NodeSet   { XPathResultType.Navigator }
        //       FuncExtension
        //
        private abstract class XsltFunctionImpl : IXsltContextFunction
        {
            private int _minargs;
            private int _maxargs;
            private XPathResultType _returnType;
            private XPathResultType[] _argTypes = null!; // Used by derived classes which initialize it
 
            public XsltFunctionImpl() { }
 
            public XsltFunctionImpl(int minArgs, int maxArgs, XPathResultType returnType, XPathResultType[] argTypes)
            {
                Init(minArgs, maxArgs, returnType, argTypes);
            }
 
            [MemberNotNull(nameof(_argTypes))]
            protected void Init(int minArgs, int maxArgs, XPathResultType returnType, XPathResultType[] argTypes)
            {
                _minargs = minArgs;
                _maxargs = maxArgs;
                _returnType = returnType;
                _argTypes = argTypes;
            }
 
            public int Minargs { get { return _minargs; } }
            public int Maxargs { get { return _maxargs; } }
            public XPathResultType ReturnType { get { return _returnType; } }
            public XPathResultType[] ArgTypes { get { return _argTypes; } }
            public abstract object Invoke(XsltContext xsltContext, object[] args, XPathNavigator docContext);
 
            // static helper methods:
            public static XPathNodeIterator ToIterator(object argument)
            {
                XPathNodeIterator? it = argument as XPathNodeIterator;
                if (it == null)
                {
                    throw XsltException.Create(SR.Xslt_NoNodeSetConversion);
                }
                return it;
            }
 
            public static XPathNavigator ToNavigator(object argument)
            {
                XPathNavigator? nav = argument as XPathNavigator;
                if (nav == null)
                {
                    throw XsltException.Create(SR.Xslt_NoNavigatorConversion);
                }
                return nav;
            }
 
            private static string IteratorToString(XPathNodeIterator it)
            {
                Debug.Assert(it != null);
                if (it.MoveNext())
                {
                    return it.Current!.Value;
                }
                return string.Empty;
            }
 
            [return: NotNullIfNotNull(nameof(argument))]
            public static string? ToString(object argument)
            {
                XPathNodeIterator? it = argument as XPathNodeIterator;
                if (it != null)
                {
                    return IteratorToString(it);
                }
                else
                {
                    return XmlConvert.ToXPathString(argument)!;
                }
            }
 
            public static bool ToBoolean(object argument)
            {
                XPathNodeIterator? it = argument as XPathNodeIterator;
                if (it != null)
                {
                    return Convert.ToBoolean(IteratorToString(it), CultureInfo.InvariantCulture);
                }
                XPathNavigator? nav = argument as XPathNavigator;
                if (nav != null)
                {
                    return Convert.ToBoolean(nav.ToString(), CultureInfo.InvariantCulture);
                }
                return Convert.ToBoolean(argument, CultureInfo.InvariantCulture);
            }
 
            public static double ToNumber(object argument)
            {
                XPathNodeIterator? it = argument as XPathNodeIterator;
                if (it != null)
                {
                    return XmlConvert.ToXPathDouble(IteratorToString(it));
                }
                XPathNavigator? nav = argument as XPathNavigator;
                if (nav != null)
                {
                    return XmlConvert.ToXPathDouble(nav.ToString());
                }
                return XmlConvert.ToXPathDouble(argument);
            }
 
            private static object ToNumeric(object argument, Type type)
            {
                return Convert.ChangeType(ToNumber(argument), type, CultureInfo.InvariantCulture);
            }
 
            public static object ConvertToXPathType(object val, XPathResultType xt, Type type)
            {
                switch (xt)
                {
                    case XPathResultType.String:
                        // Unfortunately XPathResultType.String == XPathResultType.Navigator (This is wrong but cant be changed in Everett)
                        // Fortunetely we have typeCode hare so let's discriminate by typeCode
                        if (type == typeof(string))
                        {
                            return ToString(val);
                        }
                        else
                        {
                            return ToNavigator(val);
                        }
                    case XPathResultType.Number: return ToNumeric(val, type);
                    case XPathResultType.Boolean: return ToBoolean(val);
                    case XPathResultType.NodeSet: return ToIterator(val);
                    //                case XPathResultType.Navigator : return ToNavigator(val);
                    case XPathResultType.Any:
                    case XPathResultType.Error:
                        return val;
                    default:
                        Debug.Fail("unexpected XPath type");
                        return val;
                }
            }
        }
 
        private sealed class FuncCurrent : XsltFunctionImpl
        {
            public FuncCurrent() : base(0, 0, XPathResultType.NodeSet, Array.Empty<XPathResultType>()) { }
            public override object Invoke(XsltContext xsltContext, object[] args, XPathNavigator docContext)
            {
                return ((XsltCompileContext)xsltContext).Current();
            }
        }
 
        private sealed class FuncUnEntityUri : XsltFunctionImpl
        {
            public FuncUnEntityUri() : base(1, 1, XPathResultType.String, new XPathResultType[] { XPathResultType.String }) { }
            public override object Invoke(XsltContext xsltContext, object[] args, XPathNavigator docContext)
            {
                throw XsltException.Create(SR.Xslt_UnsuppFunction, "unparsed-entity-uri");
            }
        }
 
        private sealed class FuncGenerateId : XsltFunctionImpl
        {
            public FuncGenerateId() : base(0, 1, XPathResultType.String, new XPathResultType[] { XPathResultType.NodeSet }) { }
            public override object Invoke(XsltContext xsltContext, object[] args, XPathNavigator docContext)
            {
                if (args.Length > 0)
                {
                    XPathNodeIterator it = ToIterator(args[0]);
                    if (it.MoveNext())
                    {
                        return it.Current!.UniqueId;
                    }
                    else
                    {
                        // if empty nodeset, return empty string, otherwise return generated id
                        return string.Empty;
                    }
                }
                else
                {
                    return docContext.UniqueId;
                }
            }
        }
 
        private sealed class FuncSystemProp : XsltFunctionImpl
        {
            public FuncSystemProp() : base(1, 1, XPathResultType.String, new XPathResultType[] { XPathResultType.String }) { }
            public override object Invoke(XsltContext xsltContext, object[] args, XPathNavigator docContext)
            {
                return ((XsltCompileContext)xsltContext).SystemProperty(ToString(args[0]));
            }
        }
 
        // see http://www.w3.org/TR/xslt#function-element-available
        private sealed class FuncElementAvailable : XsltFunctionImpl
        {
            public FuncElementAvailable() : base(1, 1, XPathResultType.Boolean, new XPathResultType[] { XPathResultType.String }) { }
            public override object Invoke(XsltContext xsltContext, object[] args, XPathNavigator docContext)
            {
                return ((XsltCompileContext)xsltContext).ElementAvailable(ToString(args[0]));
            }
        }
 
        // see: http://www.w3.org/TR/xslt#function-function-available
        private sealed class FuncFunctionAvailable : XsltFunctionImpl
        {
            public FuncFunctionAvailable() : base(1, 1, XPathResultType.Boolean, new XPathResultType[] { XPathResultType.String }) { }
            public override object Invoke(XsltContext xsltContext, object[] args, XPathNavigator docContext)
            {
                return ((XsltCompileContext)xsltContext).FunctionAvailable(ToString(args[0]));
            }
        }
 
        private sealed class FuncDocument : XsltFunctionImpl
        {
            public FuncDocument() : base(1, 2, XPathResultType.NodeSet, new XPathResultType[] { XPathResultType.Any, XPathResultType.NodeSet }) { }
 
            // SxS: This method uses resource names read from source document and does not expose any resources to the caller.
            // It's OK to suppress the SxS warning.
            public override object Invoke(XsltContext xsltContext, object[] args, XPathNavigator docContext)
            {
                string? baseUri = null;
                if (args.Length == 2)
                {
                    XPathNodeIterator it = ToIterator(args[1]);
                    if (it.MoveNext())
                    {
                        baseUri = it.Current!.BaseURI;
                    }
                    else
                    {
                        // http://www.w3.org/1999/11/REC-xslt-19991116-errata (E14):
                        // It is an error if the second argument node-set is empty and the URI reference is relative; the XSLT processor may signal the error;
                        // if it does not signal an error, it must recover by returning an empty node-set.
                        baseUri = string.Empty; // call to Document will fail if args[0] is reletive.
                    }
                }
                try
                {
                    return ((XsltCompileContext)xsltContext).Document(args[0], baseUri);
                }
                catch (Exception e)
                {
                    if (!XmlException.IsCatchableException(e))
                    {
                        throw;
                    }
                    return XPathEmptyIterator.Instance;
                }
            }
        }
 
        private sealed class FuncKey : XsltFunctionImpl
        {
            public FuncKey() : base(2, 2, XPathResultType.NodeSet, new XPathResultType[] { XPathResultType.String, XPathResultType.Any }) { }
            public override object Invoke(XsltContext xsltContext, object[] args, XPathNavigator docContext)
            {
                XsltCompileContext xsltCompileContext = (XsltCompileContext)xsltContext;
 
                string local, prefix;
                PrefixQName.ParseQualifiedName(ToString(args[0]), out prefix, out local);
                string? ns = xsltContext.LookupNamespace(prefix);
                XmlQualifiedName keyName = new XmlQualifiedName(local, ns);
 
                XPathNavigator root = docContext.Clone();
                root.MoveToRoot();
 
                ArrayList? resultCollection = null;
 
                foreach (Key key in xsltCompileContext._processor!.KeyList!)
                {
                    if (key.Name == keyName)
                    {
                        Hashtable? keyTable = key.GetKeys(root);
                        if (keyTable == null)
                        {
                            keyTable = xsltCompileContext.BuildKeyTable(key, root);
                            key.AddKey(root, keyTable);
                        }
 
                        XPathNodeIterator? it = args[1] as XPathNodeIterator;
                        if (it != null)
                        {
                            it = it.Clone();
                            while (it.MoveNext())
                            {
                                resultCollection = AddToList(resultCollection, (ArrayList?)keyTable[it.Current!.Value]);
                            }
                        }
                        else
                        {
                            resultCollection = AddToList(resultCollection, (ArrayList?)keyTable[ToString(args[1])]);
                        }
                    }
                }
                if (resultCollection == null)
                {
                    return XPathEmptyIterator.Instance;
                }
                else if (resultCollection[0] is XPathNavigator)
                {
                    return new XPathArrayIterator(resultCollection);
                }
                else
                {
                    return new XPathMultyIterator(resultCollection);
                }
            }
 
            private static ArrayList? AddToList(ArrayList? resultCollection, ArrayList? newList)
            {
                if (newList == null)
                {
                    return resultCollection;
                }
                if (resultCollection == null)
                {
                    return newList;
                }
                Debug.Assert(resultCollection.Count != 0);
                Debug.Assert(newList.Count != 0);
                if (!(resultCollection[0] is ArrayList))
                {
                    // Transform resultCollection from ArrayList(XPathNavigator) to ArrayList(ArrayList(XPathNavigator))
                    Debug.Assert(resultCollection[0] is XPathNavigator);
                    ArrayList firstList = resultCollection;
                    resultCollection = new ArrayList();
                    resultCollection.Add(firstList);
                }
                resultCollection.Add(newList);
                return resultCollection;
            }
        }
 
        private sealed class FuncFormatNumber : XsltFunctionImpl
        {
            public FuncFormatNumber() : base(2, 3, XPathResultType.String, new XPathResultType[] { XPathResultType.Number, XPathResultType.String, XPathResultType.String }) { }
            public override object Invoke(XsltContext xsltContext, object[] args, XPathNavigator docContext)
            {
                DecimalFormat formatInfo = ((XsltCompileContext)xsltContext).ResolveFormatName(args.Length == 3 ? ToString(args[2]) : null);
                return DecimalFormatter.Format(ToNumber(args[0]), ToString(args[1]), formatInfo);
            }
        }
 
        private sealed class FuncNodeSet : XsltFunctionImpl
        {
            public FuncNodeSet() : base(1, 1, XPathResultType.NodeSet, new XPathResultType[] { XPathResultType.Navigator }) { }
            public override object Invoke(XsltContext xsltContext, object[] args, XPathNavigator docContext)
            {
                return new XPathSingletonIterator(ToNavigator(args[0]));
            }
        }
 
        private sealed class FuncExtension : XsltFunctionImpl
        {
            private readonly object _extension;
            private readonly MethodInfo _method;
            private readonly Type[] _types;
 
            public FuncExtension(object extension, MethodInfo method)
            {
                Debug.Assert(extension != null);
                Debug.Assert(method != null);
                _extension = extension;
                _method = method;
                XPathResultType returnType = GetXPathType(method.ReturnType);
 
                ParameterInfo[] parameters = method.GetParameters();
                int minArgs = parameters.Length;
                int maxArgs = parameters.Length;
                _types = new Type[parameters.Length];
                XPathResultType[] argTypes = new XPathResultType[parameters.Length];
                bool optionalParams = true; // we allow only last params be optional. Set false on the first non optional.
                for (int i = parameters.Length - 1; 0 <= i; i--)
                {            // Revers order is essential: counting optional parameters
                    _types[i] = parameters[i].ParameterType;
                    argTypes[i] = GetXPathType(parameters[i].ParameterType);
                    if (optionalParams)
                    {
                        if (parameters[i].IsOptional)
                        {
                            minArgs--;
                        }
                        else
                        {
                            optionalParams = false;
                        }
                    }
                }
                base.Init(minArgs, maxArgs, returnType, argTypes);
            }
 
            public override object Invoke(XsltContext xsltContext, object[] args, XPathNavigator docContext)
            {
                Debug.Assert(args.Length <= this.Minargs, "We are checking this on resolve time");
                for (int i = args.Length - 1; 0 <= i; i--)
                {
                    args[i] = ConvertToXPathType(args[i], this.ArgTypes[i], _types[i]);
                }
                return _method.Invoke(_extension, args)!;
            }
        }
    }
}