2003-12-16 Atsushi Enomoto <ginga@kit.hi-ho.ne.jp>
[mono.git] / mcs / class / System.XML / Mono.Xml.Xsl / XsltCompiledContext.cs
1 //
2 // XsltCompiledContext.cs
3 //
4 // Authors:
5 //      Ben Maurer (bmaurer@users.sourceforge.net)
6 //      
7 // (C) 2003 Ben Maurer
8 //
9
10 using System;
11 using System.CodeDom;
12 using System.Collections;
13 using System.Collections.Specialized;
14 using System.Xml;
15 using System.Xml.Schema;
16 using System.Xml.XPath;
17 using System.Xml.Xsl;
18 using System.Text;
19 using System.IO;
20 using Mono.Xml.Xsl.Functions;
21 using Mono.Xml.Xsl.Operations;
22 using System.Reflection;
23 using BF = System.Reflection.BindingFlags;
24
25 using QName = System.Xml.XmlQualifiedName;
26
27
28 namespace Mono.Xml.Xsl {
29
30         internal class XsltCompiledContext : XsltContext {              
31                 XslTransformProcessor p;
32                         
33                 public XslTransformProcessor Processor { get { return p; }}
34                         
35                 public XsltCompiledContext (XslTransformProcessor p) : base (new NameTable ())
36                 {
37                         this.p = p;
38                 }
39
40                 public override string DefaultNamespace { get { return String.Empty; }}
41
42
43                 public override string LookupNamespace (string prefix)
44                 {
45                         throw new Exception ("we should never get here");
46                 }
47                 
48                 internal override IXsltContextFunction ResolveFunction (XmlQualifiedName name, XPathResultType [] argTypes)
49                 {
50                         IXsltContextFunction func = null;
51
52                         string ns = name.Namespace;
53
54                         if (ns == null) return null;
55
56                         object extension = null;
57                         
58                         if (p.Arguments != null)
59                                 extension = p.Arguments.GetExtensionObject (ns);
60                         
61                         bool isScript = false;
62                         if (extension == null) {
63                                 extension = p.ScriptManager.GetExtensionObject (ns);
64                                 if (extension == null)
65                                         return null;
66
67                                 isScript = true;
68                         }
69                         
70                         
71                         MethodInfo method = FindBestMethod (extension.GetType (), name.Name, argTypes, isScript);
72                         
73                         if (method != null) 
74                                 return new XsltExtensionFunction (extension, method);
75                         return null;
76                 }
77                 
78                 MethodInfo FindBestMethod (Type t, string name, XPathResultType [] argTypes, bool isScript)
79                 {
80                         int free, length;
81                         
82                         MethodInfo [] mi = t.GetMethods ((isScript ? BF.Public | BF.NonPublic : BF.Public) | BF.Instance | BF.Static);
83                         if (mi.Length == 0)
84                                 return null;
85                         
86                         if (argTypes == null)
87                                 return mi [0]; // if we dont have info on the arg types, nothing we can do
88
89
90                         free = 0;
91                         // filter on name + num args
92                         int numArgs = argTypes.Length;
93                         for (int i = 0; i < mi.Length; i ++) {
94                                 if (mi [i].Name == name && mi [i].GetParameters ().Length == numArgs) 
95                                         mi [free++] = mi [i];
96                         }
97                         length = free;
98                         
99                         // No method
100                         if (length == 0)
101                                 return null;
102                         
103                         // Thats it!
104                         if (length == 1)
105                                 return mi [0];
106                         
107                         free = 0;
108                         for (int i = 0; i < length; i ++) {
109                                 bool match = true;
110                                 ParameterInfo [] pi = mi [i].GetParameters ();
111                                 
112                                 for (int par = 0; par < pi.Length; par++) {
113                                         XPathResultType required = argTypes [par];
114                                         if (required == XPathResultType.Any)
115                                                 continue; // dunno what it is
116                                         
117                                         XPathResultType actual = XPFuncImpl.GetXPathType (pi [par].ParameterType);
118                                         if (actual != required && actual != XPathResultType.Any) {
119                                                 match = false;
120                                                 break;
121                                         }
122                                         
123                                         if (actual == XPathResultType.Any) {
124                                                 // try to get a stronger gind
125                                                 if (required != XPathResultType.NodeSet && !(pi [par].ParameterType == typeof (object)))
126                                                 {
127                                                         match = false;
128                                                         break;
129                                                 }
130                                         }
131                                 }
132                                 if (match) return mi [i]; // TODO look for exact match
133                         }
134                         return null;
135                 }
136                         
137                 public override IXsltContextVariable ResolveVariable (string prefix, string name)
138                 {
139                         throw new Exception ("shouldn't get here");
140                 }
141                 
142                 public override IXsltContextFunction ResolveFunction (string prefix, string name, XPathResultType [] ArgTypes)
143                 {
144                         throw new Exception ("shouldn't get here");
145                 }
146                 
147                 internal override System.Xml.Xsl.IXsltContextVariable ResolveVariable(QName q)
148                 {
149                         return p.CompiledStyle.ResolveVariable (q);
150                 }
151
152                 public override int CompareDocument (string baseUri, string nextBaseUri) { throw new NotImplementedException (); }
153
154                 public override bool PreserveWhitespace (XPathNavigator nav) 
155                 {
156                         /*
157                         XPathNavigator tmp = nav.Clone ();
158                         switch (tmp.NodeType) {
159                         case XPathNodeType.Root:
160                                 return false;
161                         case XPathNodeType.Element:
162                                 break;
163                         default:
164                                 tmp.MoveToParent ();
165                                 break;
166                         }
167
168                         for (; tmp.NodeType == XPathNodeType.Element; tmp.MoveToParent ()) {
169                                 object o = p.CompiledStyle.Style.SpaceControls [new XmlQualifiedName (tmp.LocalName, tmp.NamespaceURI)];
170                         */
171                                 object o = p.CompiledStyle.Style.SpaceControls [new XmlQualifiedName (nav.LocalName, nav.NamespaceURI)];
172                                 if (o == null)
173 //                                      continue;
174                                         return true;
175                                 XmlSpace space = (XmlSpace) o;
176                                 switch ((XmlSpace) o) {
177                                 case XmlSpace.Preserve:
178                                         return true;
179                                 case XmlSpace.Default:
180                                         return false;
181                                 // None: continue.
182                                 }
183                         return true; // temporary
184                         /*
185                         }
186                         return true;
187                         */
188                 }
189
190                 public override bool Whitespace { get { return WhitespaceHandling; } }
191
192                 // Below are mimicking XmlNamespaceManager ;-)
193                 public bool IsCData {
194                         get { return scopes [scopeAt].IsCData; }
195                         set { scopes [scopeAt].IsCData = value; }
196                 }
197                 public bool WhitespaceHandling {
198                         get { return scopes [scopeAt].PreserveWhitespace; }
199                         set { scopes [scopeAt].PreserveWhitespace = value; }
200                 }
201
202                 struct XsltContextInfo
203                 {
204                         public bool IsCData;
205                         public bool PreserveWhitespace;
206                 }
207                 
208                 // TODO: set 40 or so. It is set to 2 only for test.
209                 XsltContextInfo [] scopes = new XsltContextInfo [2];
210                 int scopeAt = 0;
211                 
212                 // precondition scopeAt == scopes.Length
213                 void ExtendScope ()
214                 {
215                         XsltContextInfo [] old = scopes;
216                         scopes = new XsltContextInfo [scopeAt * 2 + 1];
217                         if (scopeAt > 0)
218                                 Array.Copy (old, 0, scopes, 0, scopeAt);
219                 }
220
221                 public override bool PopScope ()
222                 {
223                         base.PopScope ();
224
225                         if (scopeAt == -1)
226                                 return false;
227                         scopeAt--;
228                         return true;
229                 }
230
231                 public override void PushScope ()
232                 {
233                         base.PushScope ();
234
235                         scopeAt++;
236                         if (scopeAt == scopes.Length)
237                                 ExtendScope ();
238                 }
239         }
240
241
242 }
243 namespace Mono.Xml.Xsl.Functions {
244
245         internal abstract class XPFuncImpl : IXsltContextFunction {
246                 int minargs, maxargs;
247                 XPathResultType returnType;
248                 XPathResultType [] argTypes;
249
250                 public XPFuncImpl () {}
251                 public XPFuncImpl (int minArgs, int maxArgs, XPathResultType returnType, XPathResultType[] argTypes)
252                 {
253                         this.Init(minArgs, maxArgs, returnType, argTypes);
254                 }
255                 
256                 protected void Init (int minArgs, int maxArgs, XPathResultType returnType, XPathResultType[] argTypes)
257                 {
258                         this.minargs    = minArgs;
259                         this.maxargs    = maxArgs;
260                         this.returnType = returnType;
261                         this.argTypes   = argTypes;
262                 }
263
264                 public int Minargs { get { return this.minargs; }}
265                 public int Maxargs { get { return this.maxargs; }}
266                 public XPathResultType ReturnType { get { return this.returnType; }}
267                 public XPathResultType [] ArgTypes { get { return this.argTypes; }}
268                 public object Invoke (XsltContext xsltContext, object [] args, XPathNavigator docContext)
269                 {
270                         return Invoke ((XsltCompiledContext)xsltContext, args, docContext);
271                 }
272                 
273                 public abstract object Invoke (XsltCompiledContext xsltContext, object [] args, XPathNavigator docContext);
274                 
275                 public static XPathResultType GetXPathType (Type type) {
276                         switch (Type.GetTypeCode(type)) {
277                         case TypeCode.String:
278                                 return XPathResultType.String;
279                         case TypeCode.Boolean:
280                                 return XPathResultType.Boolean;
281                         case TypeCode.Object:
282                                 if (typeof (XPathNavigator).IsAssignableFrom (type) || typeof (IXPathNavigable).IsAssignableFrom (type))
283                                         return XPathResultType.Navigator;
284                                 
285                                 if (typeof (XPathNodeIterator).IsAssignableFrom (type))
286                                         return XPathResultType.NodeSet;
287                                 
288                                 return XPathResultType.Any;
289                         case TypeCode.DateTime :
290                                 throw new Exception ();
291                         default: // Numeric
292                                 return XPathResultType.Number;
293                         } 
294                 }
295         }
296         
297         class XsltExtensionFunction : XPFuncImpl {
298                 private object extension;
299                 private MethodInfo method;
300                 private TypeCode [] typeCodes;
301
302                 public XsltExtensionFunction (object extension, MethodInfo method)
303                 {
304                         this.extension = extension;
305                         this.method = method;
306
307                         ParameterInfo [] parameters = method.GetParameters ();
308                         int minArgs = parameters.Length;
309                         int maxArgs = parameters.Length;
310                         
311                         this.typeCodes = new TypeCode [parameters.Length];
312                         XPathResultType[] argTypes = new XPathResultType [parameters.Length];
313                         
314                         bool canBeOpt = true;
315                         for (int i = parameters.Length - 1; 0 <= i; i--) { // optionals at the end
316                                 typeCodes [i] = Type.GetTypeCode (parameters [i].ParameterType);
317                                 argTypes [i] = GetXPathType (parameters [i].ParameterType);
318                                 if (canBeOpt) {
319                                         if (parameters[i].IsOptional)
320                                                 minArgs --;
321                                         else
322                                                 canBeOpt = false;
323                                 }
324                         }
325                         base.Init (minArgs, maxArgs, GetXPathType (method.ReturnType), argTypes);
326                 }
327
328                 public override object Invoke (XsltCompiledContext xsltContext, object [] args, XPathNavigator docContext)
329                 {
330                         try {
331                                 ParameterInfo [] pis = method.GetParameters ();
332                                 object [] castedArgs = new object [pis.Length];
333                                 for (int i = 0; i < args.Length; i++) {
334                                         Type t = pis [i].ParameterType;
335                                         switch (t.FullName) {
336                                         case "System.Int16":
337                                         case "System.UInt16":
338                                         case "System.Int32":
339                                         case "System.UInt32":
340                                         case "System.Int64":
341                                         case "System.UInt64":
342                                         case "System.Single":
343                                         case "System.Decimal":
344                                                 castedArgs [i] = Convert.ChangeType (args [i], t);
345                                                 break;
346                                         default:
347                                                 castedArgs [i] = args [i];
348                                                 break;
349                                         }
350                                 }
351
352                                 object result = null;
353                                 switch (method.ReturnType.FullName) {
354                                 case "System.Int16":
355                                 case "System.UInt16":
356                                 case "System.Int32":
357                                 case "System.UInt32":
358                                 case "System.Int64":
359                                 case "System.UInt64":
360                                 case "System.Single":
361                                 case "System.Decimal":
362                                         result = (double) method.Invoke (extension, castedArgs);
363                                         break;
364                                 default:
365                                         result = method.Invoke(extension, castedArgs);
366                                         break;
367                                 }
368                                 IXPathNavigable navigable = result as IXPathNavigable;
369                                 if (navigable != null)
370                                         return navigable.CreateNavigator ();
371
372                                 return result;
373                         } catch (Exception ex) {
374 //                              throw new XsltException ("Custom function reported an error.", ex);
375                                 Debug.WriteLine ("****** INCORRECT RESOLUTION **********");
376                                 return "";
377                         }
378                 }
379         }
380         
381         class XsltCurrent : XPathFunction {
382                 public XsltCurrent (FunctionArguments args) : base (args)
383                 {
384                         if (args != null)
385                                 throw new XPathException ("current takes 0 args");
386                 }
387                 
388                 public override XPathResultType ReturnType { get { return XPathResultType.NodeSet; }}
389
390                 public override object Evaluate (BaseIterator iter)
391                 {
392                         return new SelfIterator ((iter.NamespaceManager as XsltCompiledContext).Processor.CurrentNode, null);
393                 }
394         }
395         
396         class XsltDocument : XPathFunction {
397                 Expression arg0, arg1;
398                 XPathNavigator doc;
399                 
400                 public XsltDocument (FunctionArguments args, Compiler c) : base (args)
401                 {
402                         if (args == null || (args.Tail != null && args.Tail.Tail != null))
403                                 throw new XPathException ("document takes one or two args");
404                         
405                         arg0 = args.Arg;
406                         if (args.Tail != null)
407                                 arg1 = args.Tail.Arg;
408                         doc = c.Input.Clone ();
409                 }
410                 public override XPathResultType ReturnType { get { return XPathResultType.NodeSet; }}
411                 
412                 public override object Evaluate (BaseIterator iter)
413                 {
414                         string baseUri = null;
415                         if (arg1 != null) {
416                                 XPathNodeIterator it = arg1.EvaluateNodeSet (iter);
417                                 if (it.MoveNext())
418                                         baseUri = it.Current.BaseURI;
419                                 else
420                                         baseUri = VoidBaseUriFlag;
421                         }
422
423                         object o = arg0.Evaluate (iter);
424                         if (o is XPathNodeIterator)
425                                 return GetDocument ((iter.NamespaceManager as XsltCompiledContext), (XPathNodeIterator)o, baseUri);
426                         else
427                                 return GetDocument ((iter.NamespaceManager as XsltCompiledContext), o.ToString (), baseUri);
428                 }
429                 
430                 static string VoidBaseUriFlag = "&^)(*&%*^$&$VOID!BASE!URI!";
431                 
432                 Uri Resolve (string thisUri, string baseUri, XslTransformProcessor p)
433                 {
434                         Debug.WriteLine ("THIS: " + thisUri);
435                         Debug.WriteLine ("BASE: " + baseUri);
436                         XmlResolver r = p.Resolver;
437                         
438                         Uri uriBase = null;
439                         if (! object.ReferenceEquals (baseUri, VoidBaseUriFlag))
440                                 uriBase = r.ResolveUri (null, baseUri);
441                                 
442                         return r.ResolveUri (uriBase, thisUri);
443                 }
444                 
445                 XPathNodeIterator GetDocument (XsltCompiledContext xsltContext, XPathNodeIterator itr, string baseUri)
446                 {
447                         ArrayList list = new ArrayList ();
448                         Hashtable got = new Hashtable ();
449                         
450                         while (itr.MoveNext()) {
451                                 Uri uri = Resolve (itr.Current.Value, baseUri != null ? baseUri : /*itr.Current.BaseURI*/doc.BaseURI, xsltContext.Processor);
452                                 if (!got.ContainsKey (uri)) {
453                                         got.Add (uri, null);
454                                         if (uri.ToString () == "") {
455                                                 XPathNavigator n = doc.Clone ();
456                                                 n.MoveToRoot ();
457                                                 list.Add (n);
458                                         } else
459                                                 list.Add (xsltContext.Processor.GetDocument (uri));
460                                 }
461                         }
462                         
463                         return new EnumeratorIterator (list.GetEnumerator (), xsltContext);
464                 }
465         
466                 XPathNodeIterator GetDocument (XsltCompiledContext xsltContext, string arg0, string baseUri)
467                 {
468                         Uri uri = Resolve (arg0, baseUri != null ? baseUri : doc.BaseURI, xsltContext.Processor);
469                         XPathNavigator n;
470                         if (uri.ToString () == "") {
471                                 n = doc.Clone ();
472                                 n.MoveToRoot ();
473                         } else
474                                 n = xsltContext.Processor.GetDocument (uri);
475                         
476                         return new SelfIterator (n, xsltContext);
477                 }
478         }
479         
480         class XsltElementAvailable : XPathFunction {
481                 Expression arg0;
482                 XmlNamespaceManager nsm;
483                 
484                 public XsltElementAvailable (FunctionArguments args, IStaticXsltContext ctx) : base (args)
485                 {
486                         if (args == null || args.Tail != null)
487                                 throw new XPathException ("element-available takes 1 arg");
488                         
489                         arg0 = args.Arg;
490                         nsm = ctx.GetNsm ();
491                 }
492                 
493                 public override XPathResultType ReturnType { get { return XPathResultType.Boolean; }}
494
495                 public override object Evaluate (BaseIterator iter)
496                 {
497                         QName name = XslNameUtil.FromString (arg0.EvaluateString (iter), nsm);
498
499                         return (
500                                 (name.Namespace == Compiler.XsltNamespace) &&
501                                 (
502                                         //
503                                         // A list of all the instructions (does not include top-level-elements)
504                                         //
505                                         name.Name == "apply-imports" ||
506                                         name.Name == "apply-templates" ||
507                                         name.Name == "call-template" ||
508                                         name.Name == "choose" ||
509                                         name.Name == "comment" ||
510                                         name.Name == "copy" ||
511                                         name.Name == "copy-of" ||
512                                         name.Name == "element" ||
513                                         name.Name == "fallback" ||
514                                         name.Name == "for-each" ||
515                                         name.Name == "message" ||
516                                         name.Name == "number" ||
517                                         name.Name == "processing-instruction" ||
518                                         name.Name == "text" ||
519                                         name.Name == "value-of" ||
520                                         name.Name == "variable"
521                                 )
522                         );
523                 }
524         }
525
526         class XsltFormatNumber : XPathFunction {
527                 Expression arg0, arg1, arg2;
528                 XmlNamespaceManager nsm;
529                 
530                 public XsltFormatNumber (FunctionArguments args, IStaticXsltContext ctx) : base (args)
531                 {
532                         if (args == null || args.Tail == null || (args.Tail.Tail != null && args.Tail.Tail.Tail != null))
533                                 throw new XPathException ("format-number takes 2 or 3 args");
534                         
535                         arg0 = args.Arg;
536                         arg1 = args.Tail.Arg;
537                         if (args.Tail.Tail != null) {
538                                 arg2= args.Tail.Tail.Arg;
539                                 nsm = ctx.GetNsm ();
540                         }
541                 }
542                 public override XPathResultType ReturnType { get { return XPathResultType.String; }}
543                 
544                 public override object Evaluate (BaseIterator iter)
545                 {
546                         double d = arg0.EvaluateNumber (iter);
547                         string s = arg1.EvaluateString (iter);
548                         QName nm = QName.Empty;
549                         
550                         if (arg2 != null)
551                                 nm = XslNameUtil.FromString (arg2.EvaluateString (iter), nsm);
552                         
553                         return (iter.NamespaceManager as XsltCompiledContext).Processor.CompiledStyle
554                                 .LookupDecimalFormat (nm).FormatNumber (d, s);
555                 }
556         }
557         
558         class XsltFunctionAvailable : XPathFunction {
559                 Expression arg0;
560                 XmlNamespaceManager nsm;
561                 
562                 public XsltFunctionAvailable (FunctionArguments args, IStaticXsltContext ctx) : base (args)
563                 {
564                         if (args == null || args.Tail != null)
565                                 throw new XPathException ("element-available takes 1 arg");
566                         
567                         arg0 = args.Arg;
568                         nsm = ctx.GetNsm ();
569                 }
570                 
571                 public override XPathResultType ReturnType { get { return XPathResultType.Boolean; }}
572                 
573                 public override object Evaluate (BaseIterator iter)
574                 {
575                         
576                         string name = arg0.EvaluateString (iter);
577                         int colon = name.IndexOf (':');
578                         // extension function
579                         if (colon > 0)
580                                 return (iter.NamespaceManager as XsltCompiledContext).ResolveFunction (
581                                         XslNameUtil.FromString (name, nsm),
582                                         null) != null;
583                         
584                         return (
585                                 //
586                                 // XPath
587                                 //
588                                 name == "boolean" ||
589                                 name == "ceiling" ||
590                                 name == "concat" ||
591                                 name == "contains" ||
592                                 name == "count" ||
593                                 name == "false" ||
594                                 name == "floor" ||
595                                 name == "id"||
596                                 name == "lang" ||
597                                 name == "last" ||
598                                 name == "local-name" ||
599                                 name == "name" ||
600                                 name == "namespace-uri" ||
601                                 name == "normalize-space" ||
602                                 name == "not" ||
603                                 name == "number" ||
604                                 name == "position" ||
605                                 name == "round" ||
606                                 name == "starts-with" ||
607                                 name == "string" ||
608                                 name == "string-length" ||
609                                 name == "substring" ||
610                                 name == "substring-after" ||
611                                 name == "substring-before" ||
612                                 name == "sum" ||
613                                 name == "translate" ||
614                                 name == "true" ||
615                                 // XSLT
616                                 name == "document" ||
617                                 name == "format-number" ||
618                                 name == "function-available" ||
619                                 name == "generate-id" ||
620                                 name == "key" ||
621                                 name == "current" ||
622                                 name == "unparsed-entity-uri" ||
623                                 name == "element-available" ||
624                                 name == "system-property"
625                         );
626                 }
627         } 
628
629         class XsltGenerateId : XPathFunction {
630                 Expression arg0;
631                 public XsltGenerateId (FunctionArguments args) : base (args)
632                 {
633                         if (args != null) {
634                                 if (args.Tail != null)
635                                         throw new XPathException ("generate-id takes 1 or no args");
636                                 arg0 = args.Arg;
637                         }
638                 }
639                 
640                 public override XPathResultType ReturnType { get { return XPathResultType.String; }}
641                 public override object Evaluate (BaseIterator iter)
642                 {
643                         XPathNavigator n;
644                         if (arg0 != null) {
645                                 XPathNodeIterator itr = arg0.EvaluateNodeSet (iter);
646                                 if (itr.MoveNext ())
647                                         n = itr.Current.Clone ();
648                                 else
649                                         return string.Empty; // empty nodeset == empty string
650                         } else
651                                 n = iter.Current.Clone ();
652                         
653                         StringBuilder sb = new StringBuilder ("Mono"); // Ensure begins with alpha
654                         sb.Append (XmlConvert.EncodeLocalName (n.BaseURI));
655                         sb.Replace ('_', 'm'); // remove underscores from EncodeLocalName
656                         sb.Append (n.NodeType);
657                         sb.Append ('m');
658
659                         do {
660                                 sb.Append (IndexInParent (n));
661                                 sb.Append ('m');
662                         } while (n.MoveToParent ());
663                         
664                         return sb.ToString ();
665                 }
666                 
667                 int IndexInParent (XPathNavigator nav)
668                 {
669                         int n = 0;
670                         while (nav.MoveToPrevious ())
671                                 n++;
672                         
673                         return n;
674                 }
675                 
676         } 
677         
678         class XsltKey : XPathFunction {
679                 Expression arg0, arg1;
680                 XmlNamespaceManager nsm;
681                 
682                 public XsltKey (FunctionArguments args, IStaticXsltContext ctx) : base (args)
683                 {
684                         if (args == null || args.Tail == null)
685                                 throw new XPathException ("key takes 2 args");
686                         arg0 = args.Arg;
687                         arg1 = args.Tail.Arg;
688                         nsm = ctx.GetNsm ();
689                 }
690                 public Expression KeyName { get { return arg0; } }
691                 public Expression Field { get { return arg1; } }
692                 public XmlNamespaceManager NamespaceManager { get { return nsm; } }
693                 public override XPathResultType ReturnType { get { return XPathResultType.NodeSet; }}
694                 
695                 public override object Evaluate (BaseIterator iter)
696                 {
697                         ArrayList result = new ArrayList ();
698                         QName name = XslNameUtil.FromString (arg0.EvaluateString (iter), nsm);
699                         object o = arg1.Evaluate (iter);
700                         XPathNodeIterator it = o as XPathNodeIterator;
701                         
702                         if (it != null) {
703                                 while (it.MoveNext())
704                                         FindKeyMatch ((iter.NamespaceManager as XsltCompiledContext), name, it.Current.Value, result, iter.Current);
705                         } else {
706                                 FindKeyMatch ((iter.NamespaceManager as XsltCompiledContext), name, XPathFunctions.ToString (o), result, iter.Current);
707                         }
708                         
709                         return new EnumeratorIterator (result.GetEnumerator (), (iter.NamespaceManager as XsltCompiledContext));
710                 }
711                 
712                 void FindKeyMatch (XsltCompiledContext xsltContext, QName name, string value, ArrayList result, XPathNavigator context)
713                 {
714                         XPathNavigator searchDoc = context.Clone ();
715                         searchDoc.MoveToRoot ();
716                         XslKey key = xsltContext.Processor.CompiledStyle.Style.FindKey (name);
717                         if (key != null) {
718                                 XPathNodeIterator desc = searchDoc.SelectDescendants (XPathNodeType.All, true);
719
720                                 while (desc.MoveNext ()) {
721                                         if (key.Matches (desc.Current, value))
722                                                 AddResult (result, desc.Current);
723                                         
724                                         if (desc.Current.MoveToFirstAttribute ()) {
725                                                 do {
726                                                         if (key.Matches (desc.Current, value))
727                                                                 AddResult (result, desc.Current);       
728                                                 } while (desc.Current.MoveToNextAttribute ());
729                                                 
730                                                 desc.Current.MoveToParent ();
731                                         }
732                                 }
733                         }
734                 }
735
736                 void AddResult (ArrayList result, XPathNavigator nav)
737                 {
738                         for (int i = 0; i < result.Count; i++) {
739                                 XmlNodeOrder docOrder = nav.ComparePosition (((XPathNavigator)result [i]));
740                                 if (docOrder == XmlNodeOrder.Same)
741                                         return;
742                                 
743                                 if (docOrder == XmlNodeOrder.Before) {
744                                         result.Insert(i, nav.Clone ());
745                                         return;
746                                 }
747                         }
748                         result.Add (nav.Clone ());
749                 }
750         }
751         
752         class XsltSystemProperty : XPathFunction {
753                 Expression arg0;
754                 XmlNamespaceManager nsm;
755                 
756                 public XsltSystemProperty (FunctionArguments args, IStaticXsltContext ctx) : base (args)
757                 {
758                         if (args == null || args.Tail != null)
759                                 throw new XPathException ("system-property takes 1 arg");
760                         
761                         arg0 = args.Arg;
762                         nsm = ctx.GetNsm ();
763                 }
764                 
765                 public override XPathResultType ReturnType { get { return XPathResultType.String; }}
766                 public override object Evaluate (BaseIterator iter)
767                 {
768                         QName name = XslNameUtil.FromString (arg0.EvaluateString (iter), nsm);
769                         
770                         if (name.Namespace == Compiler.XsltNamespace) {
771                                 switch (name.Name) {
772                                         case "version": return "1.0";
773                                         case "vendor": return "Mono";
774                                         case "vendor-url": return "http://www.go-mono.com/";
775                                 }
776                         }
777                         
778                         return "";
779                 }
780         } 
781
782         class XsltUnparsedEntityUri : XPathFunction {
783                 Expression arg0;
784                 
785                 public XsltUnparsedEntityUri (FunctionArguments args) : base (args)
786                 {
787                         if (args == null || args.Tail != null)
788                                 throw new XPathException ("unparsed-entity-uri takes 1 arg");
789                         
790                         arg0 = args.Arg;
791                 }
792                 
793                 public override XPathResultType ReturnType { get { return XPathResultType.String; }}
794                 public override object Evaluate (BaseIterator iter)
795                 {
796                         IHasXmlNode xn = iter.Current as IHasXmlNode;
797                         if (xn == null)
798                                 return String.Empty;
799                         XmlNode n = xn.GetNode ();
800                         XmlDocumentType doctype = n.OwnerDocument.DocumentType;
801                         if (doctype == null)
802                                 return String.Empty;
803                         XmlEntity ent = doctype.Entities.GetNamedItem (arg0.EvaluateString (iter)) as XmlEntity;
804                         if (ent == null)
805                                 return String.Empty;
806                         else
807                                 return ent.BaseURI;
808                 }
809         }
810 }