Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Data.SqlXml / System / Xml / Xsl / XsltOld / XsltCompileContext.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="XsltCompileContext.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">Microsoft</owner>
6 //------------------------------------------------------------------------------
7
8 namespace System.Xml.Xsl.XsltOld {
9     using Res = System.Xml.Utils.Res;
10     using System.Diagnostics;
11     using System.IO;
12     using System.Globalization;
13     using System.Collections;
14     using System.Xml.XPath;
15     using System.Xml.Xsl.Runtime;
16     using MS.Internal.Xml.XPath;
17     using System.Reflection;
18     using System.Security;
19     using System.Runtime.Versioning;
20
21     internal class XsltCompileContext : XsltContext {
22         private InputScopeManager manager;
23         private Processor         processor;
24
25         // storage for the functions
26         private static Hashtable            s_FunctionTable = CreateFunctionTable();
27         private static IXsltContextFunction s_FuncNodeSet   = new FuncNodeSet();
28         const          string               f_NodeSet       = "node-set";
29
30         internal XsltCompileContext(InputScopeManager manager, Processor processor) : base(/*dummy*/false) {
31             this.manager   = manager;
32             this.processor = processor;
33         }
34
35         internal XsltCompileContext() : base(/*dummy*/ false ) {}
36
37         internal void Recycle() {
38             manager = null;
39             processor = null;
40         }
41
42         internal void Reinitialize(InputScopeManager manager, Processor processor) {
43             this.manager = manager;
44             this.processor = processor;
45         }
46
47         public override int CompareDocument(string baseUri, string nextbaseUri) {
48             return String.Compare(baseUri, nextbaseUri, StringComparison.Ordinal);
49         }
50
51         // Namespace support
52         public override string DefaultNamespace {
53             get { return string.Empty; }
54         }
55
56         public override string LookupNamespace(string prefix) {
57             return this.manager.ResolveXPathNamespace(prefix);
58         }
59
60         // --------------------------- XsltContext -------------------
61         //                Resolving variables and functions
62
63         public override IXsltContextVariable ResolveVariable(string prefix, string name) {
64             string namespaceURI = this.LookupNamespace(prefix);
65             XmlQualifiedName qname = new XmlQualifiedName(name, namespaceURI);
66             IXsltContextVariable variable = this.manager.VariableScope.ResolveVariable(qname);
67             if (variable == null) {
68                 throw XsltException.Create(Res.Xslt_InvalidVariable, qname.ToString());
69             }
70             return variable;
71         }
72
73         internal object EvaluateVariable(VariableAction variable) {
74             Object result = this.processor.GetVariableValue(variable);
75             if (result == null && ! variable.IsGlobal) {
76                 // This was uninitialized local variable. May be we have sutable global var too?
77                 VariableAction global = this.manager.VariableScope.ResolveGlobalVariable(variable.Name);
78                 if (global != null) {
79                     result = this.processor.GetVariableValue(global);
80                 }
81             }
82             if (result == null) {
83                 throw XsltException.Create(Res.Xslt_InvalidVariable, variable.Name.ToString());
84             }
85             return result;
86         }
87
88         // Whitespace stripping support
89         public override bool Whitespace {
90             get { return this.processor.Stylesheet.Whitespace; }
91         }
92
93         public override bool PreserveWhitespace(XPathNavigator node) {
94             node = node.Clone();
95             node.MoveToParent();
96             return this.processor.Stylesheet.PreserveWhiteSpace(this.processor, node);
97         }
98
99         private MethodInfo FindBestMethod(MethodInfo[] methods, bool ignoreCase, bool publicOnly, string name, XPathResultType[] argTypes) {
100             int length = methods.Length;
101             int free   = 0;
102             // restrict search to methods with the same name and requiested protection attribute
103             for(int i = 0; i < length; i ++) {
104                 if(string.Compare(name, methods[i].Name, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal) == 0) {
105                     if(! publicOnly || methods[i].GetBaseDefinition().IsPublic) {
106                         methods[free ++] = methods[i];
107                     }
108                 }
109             }
110             length = free;
111             if(length == 0) {
112                 // this is the only place we returning null in this function
113                 return null;
114             }
115             if(argTypes == null) {
116                 // without arg types we can't do more detailed search
117                 return methods[0];
118             }
119             // restrict search by number of parameters
120             free = 0;
121             for(int i = 0; i < length; i ++) {
122                 if(methods[i].GetParameters().Length == argTypes.Length) {
123                     methods[free ++] = methods[i];
124                 }
125             }
126             length = free;
127             if(length <= 1) {
128                 // 0 -- not method found. We have to return non-null and let it fail with corect exception on call.
129                 // 1 -- no reason to continue search anyway.
130                 return methods[0];
131             }
132             // restrict search by parameters type
133             free = 0;
134             for(int i = 0; i < length; i ++) {
135                 bool match = true;
136                 ParameterInfo[] parameters = methods[i].GetParameters();
137                 for(int par = 0; par < parameters.Length; par ++) {
138                     XPathResultType required = argTypes[par];
139                     if(required == XPathResultType.Any) {
140                         continue;                        // Any means we don't know type and can't discriminate by it
141                     }
142                     XPathResultType actual = GetXPathType(parameters[par].ParameterType);
143                     if(
144                         actual != required &&
145                         actual != XPathResultType.Any   // actual arg is object and we can pass everithing here.
146                     ) {
147                         match = false;
148                         break;
149                     }
150                 }
151                 if(match) {
152                     methods[free ++] = methods[i];
153                 }
154             }
155             length = free;
156             return methods[0];
157         }
158
159         private const BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static;
160         private IXsltContextFunction GetExtentionMethod(string ns, string name, XPathResultType[] argTypes, out object extension) {
161             FuncExtension result = null;
162             extension = this.processor.GetScriptObject(ns);
163             if (extension != null) {
164                 MethodInfo method = FindBestMethod(extension.GetType().GetMethods(bindingFlags), /*ignoreCase:*/true, /*publicOnly:*/false, name, argTypes);
165                 if (method != null) {
166                     result = new FuncExtension(extension, method, /*permissions*/null);
167                 }
168                 return result;
169             }
170
171             extension = this.processor.GetExtensionObject(ns);
172             if(extension != null) {
173                 MethodInfo method = FindBestMethod(extension.GetType().GetMethods(bindingFlags), /*ignoreCase:*/false, /*publicOnly:*/true, name, argTypes);
174                 if (method != null) {
175                     result = new FuncExtension(extension, method, this.processor.permissions);
176                 }
177                 return result;
178             }
179
180             return null;
181         }
182
183         public override IXsltContextFunction ResolveFunction(string prefix, string name, XPathResultType[] argTypes) {
184             IXsltContextFunction func = null;
185             if(prefix.Length == 0) {
186                 func = s_FunctionTable[name] as IXsltContextFunction;
187             } else {
188                 string ns = this.LookupNamespace(prefix);
189                 if (ns == XmlReservedNs.NsMsxsl && name == f_NodeSet) {
190                     func = s_FuncNodeSet;
191                 } else {
192                     object     extension;
193                     func = GetExtentionMethod(ns, name, argTypes, out extension);
194                     if(extension == null) {
195                         throw XsltException.Create(Res.Xslt_ScriptInvalidPrefix, prefix);  // BugBug: It's better to say that method 'name' not found
196                     }
197                 }
198             }
199             if (func == null) {
200                 throw XsltException.Create(Res.Xslt_UnknownXsltFunction, name);
201             }
202             if (argTypes.Length < func.Minargs || func.Maxargs < argTypes.Length) {
203                throw XsltException.Create(Res.Xslt_WrongNumberArgs, name, argTypes.Length.ToString(CultureInfo.InvariantCulture));
204             }
205             return func;
206         }
207
208         //
209         // Xslt Function Extensions to XPath
210         //
211         [ResourceConsumption(ResourceScope.Machine)]
212         [ResourceExposure(ResourceScope.Machine)]
213         private Uri ComposeUri(string thisUri, string baseUri) {
214             Debug.Assert(thisUri != null && baseUri != null);
215             XmlResolver resolver = processor.Resolver;
216             Uri uriBase = null;
217             if (baseUri.Length != 0) {
218                 uriBase = resolver.ResolveUri(null, baseUri);
219             }
220             return resolver.ResolveUri(uriBase, thisUri);
221         }
222
223         [ResourceConsumption(ResourceScope.Machine)]
224         [ResourceExposure(ResourceScope.Machine)]
225         private XPathNodeIterator Document(object arg0, string baseUri) {
226             if (this.processor.permissions != null) {
227                 this.processor.permissions.PermitOnly();
228             }
229             XPathNodeIterator it = arg0 as XPathNodeIterator;
230             if (it != null) {
231                 ArrayList list = new ArrayList();
232                 Hashtable documents = new Hashtable();
233                 while (it.MoveNext()) {
234                     Uri uri = ComposeUri(it.Current.Value, baseUri ?? it.Current.BaseURI);
235                     if (! documents.ContainsKey(uri)) {
236                         documents.Add(uri, null);
237                         list.Add(this.processor.GetNavigator(uri));
238                     }
239                 }
240                 return new XPathArrayIterator(list);
241             } else {
242                 return new XPathSingletonIterator(
243                     this.processor.GetNavigator(
244                         ComposeUri(XmlConvert.ToXPathString(arg0), baseUri ?? this.manager.Navigator.BaseURI)
245                     )
246                 );
247             }
248         }
249
250         private Hashtable BuildKeyTable(Key key, XPathNavigator root) {
251             Hashtable keyTable = new Hashtable();
252
253             string matchStr = this.processor.GetQueryExpression(key.MatchKey);
254             Query matchExpr = this.processor.GetCompiledQuery(key.MatchKey);
255             Query useExpr   = this.processor.GetCompiledQuery(key.UseKey);
256
257             XPathNodeIterator  sel = root.SelectDescendants(XPathNodeType.All, /*matchSelf:*/ false);
258
259             while(sel.MoveNext()) {
260                 XPathNavigator node = sel.Current;
261                 EvaluateKey(node, matchExpr, matchStr, useExpr, keyTable);
262                 if (node.MoveToFirstAttribute()) {
263                     do {
264                         EvaluateKey(node, matchExpr, matchStr, useExpr, keyTable);
265                     } while (node.MoveToNextAttribute());
266                     node.MoveToParent();
267                 }
268             }
269             return keyTable;
270         }
271
272         private static void AddKeyValue(Hashtable keyTable, String key, XPathNavigator value, bool checkDuplicates) {
273             ArrayList list = (ArrayList)keyTable[key];
274             if (list == null) {
275                 list = new ArrayList();
276                 keyTable.Add(key, list);
277             } else {
278                 Debug.Assert(
279                     value.ComparePosition((XPathNavigator) list[list.Count - 1]) != XmlNodeOrder.Before,
280                     "The way we traversing nodes should garantees node-order"
281                 );
282                 if (checkDuplicates) {
283                     // it's posible that this value already was ----osiated with current node
284                     // but if this happened the node is last in the list of values.
285                     if (value.ComparePosition((XPathNavigator) list[list.Count - 1]) == XmlNodeOrder.Same) {
286                         return;
287                     }
288                 } else {
289                     Debug.Assert(
290                         value.ComparePosition((XPathNavigator) list[list.Count - 1]) != XmlNodeOrder.Same,
291                         "checkDuplicates == false : We can't have duplicates"
292                     );
293                 }
294             }
295             list.Add(value.Clone());
296         }
297
298         private static void EvaluateKey(XPathNavigator node, Query matchExpr, string matchStr, Query useExpr, Hashtable keyTable) {
299             try {
300                 if (matchExpr.MatchNode(node) == null) {
301                     return;
302                 }
303             } catch(XPathException) {
304                 throw XsltException.Create(Res.Xslt_InvalidPattern, matchStr);
305             }
306             object result = useExpr.Evaluate(new XPathSingletonIterator(node, /*moved:*/true));
307             XPathNodeIterator it = result as XPathNodeIterator;
308             if (it != null) {
309                 bool checkDuplicates = false;
310                 while (it.MoveNext()) {
311                     AddKeyValue(keyTable, /*key:*/it.Current.Value, /*value:*/node, checkDuplicates);
312                     checkDuplicates = true;
313                 }
314             } else {
315                 String key = XmlConvert.ToXPathString(result);
316                 AddKeyValue(keyTable, key, /*value:*/node, /*checkDuplicates:*/ false);
317             }
318         }
319
320         private DecimalFormat ResolveFormatName(string formatName) {
321             string ns = string.Empty, local = string.Empty;
322             if (formatName != null) {
323                 string prefix;
324                 PrefixQName.ParseQualifiedName(formatName, out prefix, out local);
325                 ns = LookupNamespace(prefix);
326             }
327             DecimalFormat formatInfo = this.processor.RootAction.GetDecimalFormat(new XmlQualifiedName(local, ns));
328             if (formatInfo == null) {
329                 if (formatName != null) {
330                     throw XsltException.Create(Res.Xslt_NoDecimalFormat, formatName);
331                 }
332                 formatInfo = new DecimalFormat(new NumberFormatInfo(), '#', '0', ';');
333             }
334             return formatInfo;
335         }
336
337         // see http://www.w3.org/TR/xslt#function-element-available
338         private bool ElementAvailable(string qname) {
339             string name, prefix;
340             PrefixQName.ParseQualifiedName(qname, out prefix, out name);
341             string ns = this.manager.ResolveXmlNamespace(prefix);
342             // msxsl:script - is not an "instruction" so we return false for it.
343             if (ns == XmlReservedNs.NsXslt) {
344                 return (
345                     name == "apply-imports" ||
346                     name == "apply-templates" ||
347                     name == "attribute" ||
348                     name == "call-template" ||
349                     name == "choose" ||
350                     name == "comment" ||
351                     name == "copy" ||
352                     name == "copy-of" ||
353                     name == "element" ||
354                     name == "fallback" ||
355                     name == "for-each" ||
356                     name == "if" ||
357                     name == "message" ||
358                     name == "number" ||
359                     name == "processing-instruction" ||
360                     name == "text" ||
361                     name == "value-of" ||
362                     name == "variable"
363                 );
364             }
365             return false;
366         }
367
368         // see: http://www.w3.org/TR/xslt#function-function-available
369         private bool FunctionAvailable(string qname) {
370             string name, prefix;
371             PrefixQName.ParseQualifiedName(qname, out prefix, out name);
372             string ns = LookupNamespace(prefix);
373
374             if (ns == XmlReservedNs.NsMsxsl) {
375                 return name == f_NodeSet;
376             } else if(ns.Length == 0) {
377                 return (
378                     // It'll be better to get this information from XPath
379                     name == "last"              ||
380                     name == "position"          ||
381                     name == "name"              ||
382                     name == "namespace-uri"     ||
383                     name == "local-name"        ||
384                     name == "count"             ||
385                     name == "id"                ||
386                     name == "string"            ||
387                     name == "concat"            ||
388                     name == "starts-with"       ||
389                     name == "contains"          ||
390                     name == "substring-before"  ||
391                     name == "substring-after"   ||
392                     name == "substring"         ||
393                     name == "string-length"     ||
394                     name == "normalize-space"   ||
395                     name == "translate"         ||
396                     name == "boolean"           ||
397                     name == "not"               ||
398                     name == "true"              ||
399                     name == "false"             ||
400                     name == "lang"              ||
401                     name == "number"            ||
402                     name == "sum"               ||
403                     name == "floor"             ||
404                     name == "ceiling"           ||
405                     name == "round"             ||
406                     // XSLT functions:
407                     (s_FunctionTable[name] != null && name != "unparsed-entity-uri")
408                 );
409             } else {
410                 // Is this script or extention function?
411                 object extension;
412                 return GetExtentionMethod(ns, name, /*argTypes*/null, out extension) != null;
413             }
414         }
415
416         private XPathNodeIterator Current() {
417             XPathNavigator nav = this.processor.Current;
418             if (nav != null) {
419                 return new XPathSingletonIterator(nav.Clone());
420             }
421             return XPathEmptyIterator.Instance;
422         }
423
424         private String SystemProperty(string qname) {
425             String result = string.Empty;
426
427             string prefix;
428             string local;
429             PrefixQName.ParseQualifiedName(qname, out prefix, out local);
430
431             // verify the prefix corresponds to the Xslt namespace
432             string urn = LookupNamespace(prefix);
433
434             if (urn == XmlReservedNs.NsXslt) {
435                 if(local == "version") {
436                     result = "1";
437                 } else if(local == "vendor") {
438                     result = "Microsoft";
439                 } else if(local == "vendor-url") {
440                     result = "http://www.microsoft.com";
441                 }
442             } else {
443                 if(urn == null && prefix != null) {
444                 // if prefix exist it has to be mapped to namespace.
445                 // Can it be "" here ?
446                     throw XsltException.Create(Res.Xslt_InvalidPrefix, prefix);
447                 }
448                 return string.Empty;
449             }
450
451             return result;
452         }
453
454         public static XPathResultType GetXPathType(Type type) {
455             switch(Type.GetTypeCode(type)) {
456             case TypeCode.String :
457                 return XPathResultType.String;
458             case TypeCode.Boolean :
459                 return XPathResultType.Boolean;
460             case TypeCode.Object :
461                 if (
462                     typeof(XPathNavigator ).IsAssignableFrom(type) ||
463                     typeof(IXPathNavigable).IsAssignableFrom(type)
464                 ) {
465                     return XPathResultType.Navigator;
466                 }
467                 if (typeof(XPathNodeIterator).IsAssignableFrom(type)) {
468                     return XPathResultType.NodeSet;
469                 }
470                 // Microsoft: It be better to check that type is realy object and otherwise return XPathResultType.Error
471                 return XPathResultType.Any;
472             case TypeCode.DateTime :
473                 return XPathResultType.Error;
474             default: /* all numeric types */
475                 return XPathResultType.Number;
476             }
477         }
478
479         // ---------------- Xslt Function Implementations -------------------
480         //
481         static Hashtable CreateFunctionTable() {
482             Hashtable ft = new Hashtable(10); {
483                 ft["current"            ] = new FuncCurrent          ();
484                 ft["unparsed-entity-uri"] = new FuncUnEntityUri      ();
485                 ft["generate-id"        ] = new FuncGenerateId       ();
486                 ft["system-property"    ] = new FuncSystemProp       ();
487                 ft["element-available"  ] = new FuncElementAvailable ();
488                 ft["function-available" ] = new FuncFunctionAvailable();
489                 ft["document"           ] = new FuncDocument         ();
490                 ft["key"                ] = new FuncKey              ();
491                 ft["format-number"      ] = new FuncFormatNumber     ();
492             }
493             return ft;
494         }
495
496         // + IXsltContextFunction
497         //   + XsltFunctionImpl             func. name,       min/max args,      return type                args types
498         //       FuncCurrent            "current"              0   0         XPathResultType.NodeSet   { }
499         //       FuncUnEntityUri        "unparsed-entity-uri"  1   1         XPathResultType.String    { XPathResultType.String  }
500         //       FuncGenerateId         "generate-id"          0   1         XPathResultType.String    { XPathResultType.NodeSet }
501         //       FuncSystemProp         "system-property"      1   1         XPathResultType.String    { XPathResultType.String  }
502         //       FuncElementAvailable   "element-available"    1   1         XPathResultType.Boolean   { XPathResultType.String  }
503         //       FuncFunctionAvailable  "function-available"   1   1         XPathResultType.Boolean   { XPathResultType.String  }
504         //       FuncDocument           "document"             1   2         XPathResultType.NodeSet   { XPathResultType.Any    , XPathResultType.NodeSet }
505         //       FuncKey                "key"                  2   2         XPathResultType.NodeSet   { XPathResultType.String , XPathResultType.Any     }
506         //       FuncFormatNumber       "format-number"        2   3         XPathResultType.String    { XPathResultType.Number , XPathResultType.String, XPathResultType.String }
507         //       FuncNodeSet            "msxsl:node-set"       1   1         XPathResultType.NodeSet   { XPathResultType.Navigator }
508         //       FuncExtension
509         //
510         private abstract class XsltFunctionImpl : IXsltContextFunction {
511             private int               minargs;
512             private int               maxargs;
513             private XPathResultType   returnType;
514             private XPathResultType[] argTypes;
515
516             public XsltFunctionImpl() {}
517             public XsltFunctionImpl(int minArgs, int maxArgs, XPathResultType returnType, XPathResultType[] argTypes) {
518                 this.Init(minArgs, maxArgs, returnType, argTypes);
519             }
520             protected void Init(int minArgs, int maxArgs, XPathResultType returnType, XPathResultType[] argTypes) {
521                 this.minargs    = minArgs;
522                 this.maxargs    = maxArgs;
523                 this.returnType = returnType;
524                 this.argTypes   = argTypes;
525             }
526
527             public int               Minargs       { get { return this.minargs;    } }
528             public int               Maxargs       { get { return this.maxargs;    } }
529             public XPathResultType   ReturnType    { get { return this.returnType; } }
530             public XPathResultType[] ArgTypes      { get { return this.argTypes;   } }
531             public abstract object Invoke(XsltContext xsltContext, object[] args, XPathNavigator docContext);
532
533         // static helper methods:
534             public static XPathNodeIterator ToIterator(object argument) {
535                 XPathNodeIterator it = argument as XPathNodeIterator;
536                 if(it == null) {
537                     throw XsltException.Create(Res.Xslt_NoNodeSetConversion);
538                 }
539                 return it;
540             }
541
542             public static XPathNavigator ToNavigator(object argument) {
543                 XPathNavigator nav = argument as XPathNavigator;
544                 if(nav == null) {
545                     throw XsltException.Create(Res.Xslt_NoNavigatorConversion);
546                 }
547                 return nav;
548             }
549
550             private static string IteratorToString(XPathNodeIterator it) {
551                 Debug.Assert(it != null);
552                 if(it.MoveNext()) {
553                     return it.Current.Value;
554                 }
555                 return string.Empty;
556             }
557
558             public static string ToString(object argument) {
559                 XPathNodeIterator it = argument as XPathNodeIterator;
560                 if(it != null) {
561                     return IteratorToString(it);
562                 } else {
563                     return XmlConvert.ToXPathString(argument);
564                 }
565             }
566
567             public static bool ToBoolean(object argument) {
568                 XPathNodeIterator it = argument as XPathNodeIterator;
569                 if(it != null) {
570                     return Convert.ToBoolean(IteratorToString(it), CultureInfo.InvariantCulture);
571                 }
572                 XPathNavigator nav = argument as XPathNavigator;
573                 if (nav != null) {
574                     return Convert.ToBoolean(nav.ToString(), CultureInfo.InvariantCulture);
575                 }
576                 return Convert.ToBoolean(argument, CultureInfo.InvariantCulture);
577             }
578
579             public static double ToNumber(object argument) {
580                 XPathNodeIterator it = argument as XPathNodeIterator;
581                 if (it != null) {
582                     return XmlConvert.ToXPathDouble(IteratorToString(it));
583                 }
584                 XPathNavigator nav = argument as XPathNavigator;
585                 if (nav != null) {
586                     return XmlConvert.ToXPathDouble(nav.ToString());
587                 }
588                 return XmlConvert.ToXPathDouble(argument);
589             }
590
591             private static object ToNumeric(object argument, TypeCode typeCode) {
592                 return Convert.ChangeType(ToNumber(argument), typeCode, CultureInfo.InvariantCulture);
593             }
594
595             public static object ConvertToXPathType(object val, XPathResultType xt, TypeCode typeCode) {
596                 switch(xt) {
597                 case XPathResultType.String    :
598                     // Unfortunetely XPathResultType.String == XPathResultType.Navigator (This is wrong but cant be changed in Everett)
599                     // Fortunetely we have typeCode hare so let's discriminate by typeCode
600                     if (typeCode == TypeCode.String) {
601                         return ToString(val);
602                     } else {
603                         return ToNavigator(val);
604                     }
605                 case XPathResultType.Number    : return ToNumeric(val, typeCode);
606                 case XPathResultType.Boolean   : return ToBoolean(val);
607                 case XPathResultType.NodeSet   : return ToIterator(val);
608 //                case XPathResultType.Navigator : return ToNavigator(val);
609                 case XPathResultType.Any       :
610                 case XPathResultType.Error     :
611                     return val;
612                 default :
613                     Debug.Assert(false, "unexpected XPath type");
614                     return val;
615                 }
616             }
617         }
618
619         private class FuncCurrent : XsltFunctionImpl {
620             public FuncCurrent() : base(0, 0, XPathResultType.NodeSet, new XPathResultType[] {}) {}
621             public override object Invoke(XsltContext xsltContext, object[] args, XPathNavigator docContext) {
622                 return ((XsltCompileContext)xsltContext).Current();
623             }
624         }
625
626         private class FuncUnEntityUri : XsltFunctionImpl {
627             public FuncUnEntityUri() : base(1, 1, XPathResultType.String , new XPathResultType[] { XPathResultType.String  }) {}
628             public override object Invoke(XsltContext xsltContext, object[] args, XPathNavigator docContext) {
629                 throw XsltException.Create(Res.Xslt_UnsuppFunction, "unparsed-entity-uri");
630             }
631         }
632
633         private class FuncGenerateId : XsltFunctionImpl {
634             public FuncGenerateId() : base(0, 1, XPathResultType.String , new XPathResultType[] { XPathResultType.NodeSet }) {}
635             public override object Invoke(XsltContext xsltContext, object[] args, XPathNavigator docContext) {
636                 if(args.Length > 0) {
637                     XPathNodeIterator it = ToIterator(args[0]);
638                     if(it.MoveNext()) {
639                         return it.Current.UniqueId;
640                     } else {
641                         // if empty nodeset, return empty string, otherwise return generated id
642                         return string.Empty;
643                     }
644                 } else {
645                     return docContext.UniqueId;
646                 }
647             }
648         }
649
650         private class FuncSystemProp : XsltFunctionImpl {
651             public FuncSystemProp() : base(1, 1, XPathResultType.String , new XPathResultType[] { XPathResultType.String  }) {}
652             public override object Invoke(XsltContext xsltContext, object[] args, XPathNavigator docContext) {
653                 return ((XsltCompileContext)xsltContext).SystemProperty(ToString(args[0]));
654             }
655         }
656
657         // see http://www.w3.org/TR/xslt#function-element-available
658         private class FuncElementAvailable : XsltFunctionImpl {
659             public FuncElementAvailable() : base(1, 1, XPathResultType.Boolean, new XPathResultType[] { XPathResultType.String  }) {}
660             public override object Invoke(XsltContext xsltContext, object[] args, XPathNavigator docContext) {
661                 return ((XsltCompileContext)xsltContext).ElementAvailable(ToString(args[0]));
662             }
663         }
664
665         // see: http://www.w3.org/TR/xslt#function-function-available
666         private class FuncFunctionAvailable : XsltFunctionImpl {
667             public FuncFunctionAvailable() : base(1, 1, XPathResultType.Boolean, new XPathResultType[] { XPathResultType.String  }) {}
668             public override object Invoke(XsltContext xsltContext, object[] args, XPathNavigator docContext) {
669                 return ((XsltCompileContext)xsltContext).FunctionAvailable(ToString(args[0]));
670             }
671         }
672
673         private class FuncDocument : XsltFunctionImpl {
674             public FuncDocument() : base(1, 2, XPathResultType.NodeSet, new XPathResultType[] { XPathResultType.Any    , XPathResultType.NodeSet}) {}
675             
676             // SxS: This method uses resource names read from source document and does not expose any resources to the caller.
677             // It's OK to suppress the SxS warning.
678             [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
679             [ResourceExposure(ResourceScope.None)]
680             public override object Invoke(XsltContext xsltContext, object[] args, XPathNavigator docContext) {
681                 string baseUri = null;
682                 if (args.Length == 2) {
683                     XPathNodeIterator it = ToIterator(args[1]);
684                     if (it.MoveNext()){
685                         baseUri = it.Current.BaseURI;
686                     } else {
687                         // http://www.w3.org/1999/11/REC-xslt-19991116-errata (E14):
688                         // 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;
689                         // if it does not signal an error, it must recover by returning an empty node-set.
690                         baseUri = string.Empty; // call to Document will fail if args[0] is reletive.
691                     }
692                 }
693                 try {
694                     return ((XsltCompileContext)xsltContext).Document(args[0], baseUri);
695                 }
696                 catch (Exception e) {
697                     if (!XmlException.IsCatchableException(e)) {
698                         throw;
699                     }
700                     return XPathEmptyIterator.Instance;
701                 }
702             }
703         }
704
705         private class FuncKey : XsltFunctionImpl {
706             public FuncKey() : base(2, 2, XPathResultType.NodeSet, new XPathResultType[] { XPathResultType.String , XPathResultType.Any     }) {}
707             public override object Invoke(XsltContext xsltContext, object[] args, XPathNavigator docContext) {
708                 XsltCompileContext xsltCompileContext = (XsltCompileContext) xsltContext;
709
710                 string local, prefix;
711                 PrefixQName.ParseQualifiedName(ToString(args[0]), out prefix, out local);
712                 string ns = xsltContext.LookupNamespace(prefix);
713                 XmlQualifiedName keyName = new XmlQualifiedName(local, ns);
714
715                 XPathNavigator root = docContext.Clone();
716                 root.MoveToRoot();
717
718                 ArrayList resultCollection = null;
719
720                 foreach (Key key in xsltCompileContext.processor.KeyList) {
721                     if (key.Name == keyName) {
722                         Hashtable keyTable = key.GetKeys(root);
723                         if (keyTable == null) {
724                             keyTable = xsltCompileContext.BuildKeyTable(key, root);
725                             key.AddKey(root, keyTable);
726                         }
727
728                         XPathNodeIterator it = args[1] as XPathNodeIterator;
729                         if (it != null) {
730                             it = it.Clone();
731                             while (it.MoveNext()) {
732                                 resultCollection = AddToList(resultCollection, (ArrayList) keyTable[it.Current.Value]);
733                             }
734                         } else {
735                             resultCollection = AddToList(resultCollection, (ArrayList) keyTable[ToString(args[1])]);
736                         }
737                     }
738                 }
739                 if (resultCollection == null) {
740                     return XPathEmptyIterator.Instance;
741                 } else if (resultCollection[0] is XPathNavigator) {
742                     return new XPathArrayIterator(resultCollection);
743                 } else {
744                     return new XPathMultyIterator(resultCollection);
745                 }
746             }
747
748             static ArrayList AddToList(ArrayList resultCollection, ArrayList newList) {
749                 if (newList == null) {
750                     return resultCollection;
751                 }
752                 if (resultCollection == null) {
753                     return newList;
754                 }
755                 Debug.Assert(resultCollection.Count != 0);
756                 Debug.Assert(newList.Count != 0);
757                 if (! (resultCollection[0] is ArrayList)) {
758                     // Transform resultCollection from ArrayList(XPathNavigator) to ArrayList(ArrayList(XPathNavigator))
759                     Debug.Assert(resultCollection[0] is XPathNavigator);
760                     ArrayList firstList = resultCollection;
761                     resultCollection = new ArrayList();
762                     resultCollection.Add(firstList);
763                 }
764                 resultCollection.Add(newList);
765                 return resultCollection;
766             }
767         }
768
769         private class FuncFormatNumber : XsltFunctionImpl {
770             public FuncFormatNumber() : base(2, 3, XPathResultType.String , new XPathResultType[] { XPathResultType.Number , XPathResultType.String, XPathResultType.String }) {}
771             public override object Invoke(XsltContext xsltContext, object[] args, XPathNavigator docContext) {
772                 DecimalFormat formatInfo = ((XsltCompileContext)xsltContext).ResolveFormatName(args.Length == 3 ? ToString(args[2]) : null);
773                 return DecimalFormatter.Format(ToNumber(args[0]), ToString(args[1]), formatInfo);
774             }
775         }
776
777         private class FuncNodeSet : XsltFunctionImpl {
778             public FuncNodeSet() : base(1, 1, XPathResultType.NodeSet, new XPathResultType[] { XPathResultType.Navigator }) {}
779             public override object Invoke(XsltContext xsltContext, object[] args, XPathNavigator docContext) {
780                 return new XPathSingletonIterator(ToNavigator(args[0]));
781             }
782         }
783
784         private class FuncExtension : XsltFunctionImpl {
785             private object            extension;
786             private MethodInfo        method;
787             private TypeCode[]        typeCodes;
788             private PermissionSet     permissions;
789
790             public FuncExtension(object extension, MethodInfo method, PermissionSet permissions) {
791                 Debug.Assert(extension != null);
792                 Debug.Assert(method    != null);
793                 this.extension   = extension;
794                 this.method      = method;
795                 this.permissions = permissions;
796                 XPathResultType returnType = GetXPathType(method.ReturnType);
797
798                 ParameterInfo[] parameters = method.GetParameters();
799                 int minArgs = parameters.Length;
800                 int maxArgs = parameters.Length;
801                 this.typeCodes = new TypeCode[parameters.Length];
802                 XPathResultType[] argTypes = new XPathResultType[parameters.Length];
803                 bool optionalParams = true; // we allow only last params be optional. Set false on the first non optional.
804                 for (int i = parameters.Length - 1; 0 <= i; i --) {            // Revers order is essential: counting optional parameters
805                     typeCodes[i] = Type.GetTypeCode(parameters[i].ParameterType);
806                     argTypes[i] = GetXPathType(parameters[i].ParameterType);
807                     if(optionalParams) {
808                         if(parameters[i].IsOptional) {
809                             minArgs --;
810                         } else {
811                             optionalParams = false;
812                         }
813                     }
814                 }
815                 base.Init(minArgs, maxArgs, returnType, argTypes);
816             }
817
818             public override object Invoke(XsltContext xsltContext, object[] args, XPathNavigator docContext) {
819                 Debug.Assert(args.Length <= this.Minargs, "We cheking this on resolve time");
820                 for(int i = args.Length -1; 0 <= i; i --) {
821                     args[i] = ConvertToXPathType(args[i], this.ArgTypes[i], this.typeCodes[i]);
822                 }
823                 if (this.permissions != null) {
824                     this.permissions.PermitOnly();
825                 }
826                 return method.Invoke(extension, args);
827             }
828         }
829     }
830 }