2003-12-07 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                                 object result = method.Invoke(extension, args);
332                                 IXPathNavigable navigable = result as IXPathNavigable;
333                                 if (navigable != null)
334                                         return navigable.CreateNavigator ();
335
336                                 return result;
337                         } catch {
338                                 Debug.WriteLine ("****** INCORRECT RESOLUTION **********");
339                                 return "";
340                         }
341                 }
342         }
343         
344         class XsltCurrent : XPathFunction {
345                 public XsltCurrent (FunctionArguments args) : base (args)
346                 {
347                         if (args != null)
348                                 throw new XPathException ("current takes 0 args");
349                 }
350                 
351                 public override XPathResultType ReturnType { get { return XPathResultType.NodeSet; }}
352
353                 public override object Evaluate (BaseIterator iter)
354                 {
355                         return new SelfIterator ((iter.NamespaceManager as XsltCompiledContext).Processor.CurrentNode, null);
356                 }
357         }
358         
359         class XsltDocument : XPathFunction {
360                 Expression arg0, arg1;
361                 XPathNavigator doc;
362                 
363                 public XsltDocument (FunctionArguments args, Compiler c) : base (args)
364                 {
365                         if (args == null || (args.Tail != null && args.Tail.Tail != null))
366                                 throw new XPathException ("document takes one or two args");
367                         
368                         arg0 = args.Arg;
369                         if (args.Tail != null)
370                                 arg1 = args.Tail.Arg;
371                         doc = c.Input.Clone ();
372                 }
373                 public override XPathResultType ReturnType { get { return XPathResultType.NodeSet; }}
374                 
375                 public override object Evaluate (BaseIterator iter)
376                 {
377                         string baseUri = null;
378                         if (arg1 != null) {
379                                 XPathNodeIterator it = arg1.EvaluateNodeSet (iter);
380                                 if (it.MoveNext())
381                                         baseUri = it.Current.BaseURI;
382                                 else
383                                         baseUri = VoidBaseUriFlag;
384                         }
385
386                         object o = arg0.Evaluate (iter);
387                         if (o is XPathNodeIterator)
388                                 return GetDocument ((iter.NamespaceManager as XsltCompiledContext), (XPathNodeIterator)o, baseUri);
389                         else
390                                 return GetDocument ((iter.NamespaceManager as XsltCompiledContext), o.ToString (), baseUri);
391                 }
392                 
393                 static string VoidBaseUriFlag = "&^)(*&%*^$&$VOID!BASE!URI!";
394                 
395                 Uri Resolve (string thisUri, string baseUri, XslTransformProcessor p)
396                 {
397                         Debug.WriteLine ("THIS: " + thisUri);
398                         Debug.WriteLine ("BASE: " + baseUri);
399                         XmlResolver r = p.Resolver;
400                         
401                         Uri uriBase = null;
402                         if (! object.ReferenceEquals (baseUri, VoidBaseUriFlag))
403                                 uriBase = r.ResolveUri (null, baseUri);
404                                 
405                         return r.ResolveUri (uriBase, thisUri);
406                 }
407                 
408                 XPathNodeIterator GetDocument (XsltCompiledContext xsltContext, XPathNodeIterator itr, string baseUri)
409                 {
410                         ArrayList list = new ArrayList ();
411                         Hashtable got = new Hashtable ();
412                         
413                         while (itr.MoveNext()) {
414                                 Uri uri = Resolve (itr.Current.Value, baseUri != null ? baseUri : /*itr.Current.BaseURI*/doc.BaseURI, xsltContext.Processor);
415                                 if (!got.ContainsKey (uri)) {
416                                         got.Add (uri, null);
417                                         if (uri.ToString () == "") {
418                                                 XPathNavigator n = doc.Clone ();
419                                                 n.MoveToRoot ();
420                                                 list.Add (n);
421                                         } else
422                                                 list.Add (xsltContext.Processor.GetDocument (uri));
423                                 }
424                         }
425                         
426                         return new EnumeratorIterator (list.GetEnumerator (), xsltContext);
427                 }
428         
429                 XPathNodeIterator GetDocument (XsltCompiledContext xsltContext, string arg0, string baseUri)
430                 {
431                         Uri uri = Resolve (arg0, baseUri != null ? baseUri : doc.BaseURI, xsltContext.Processor);
432                         XPathNavigator n;
433                         if (uri.ToString () == "") {
434                                 n = doc.Clone ();
435                                 n.MoveToRoot ();
436                         } else
437                                 n = xsltContext.Processor.GetDocument (uri);
438                         
439                         return new SelfIterator (n, xsltContext);
440                 }
441         }
442         
443         class XsltElementAvailable : XPathFunction {
444                 Expression arg0;
445                 XmlNamespaceManager nsm;
446                 
447                 public XsltElementAvailable (FunctionArguments args, IStaticXsltContext ctx) : base (args)
448                 {
449                         if (args == null || args.Tail != null)
450                                 throw new XPathException ("element-available takes 1 arg");
451                         
452                         arg0 = args.Arg;
453                         nsm = ctx.GetNsm ();
454                 }
455                 
456                 public override XPathResultType ReturnType { get { return XPathResultType.Boolean; }}
457
458                 public override object Evaluate (BaseIterator iter)
459                 {
460                         QName name = XslNameUtil.FromString (arg0.EvaluateString (iter), nsm);
461
462                         return (
463                                 (name.Namespace == Compiler.XsltNamespace) &&
464                                 (
465                                         //
466                                         // A list of all the instructions (does not include top-level-elements)
467                                         //
468                                         name.Name == "apply-imports" ||
469                                         name.Name == "apply-templates" ||
470                                         name.Name == "call-template" ||
471                                         name.Name == "choose" ||
472                                         name.Name == "comment" ||
473                                         name.Name == "copy" ||
474                                         name.Name == "copy-of" ||
475                                         name.Name == "element" ||
476                                         name.Name == "fallback" ||
477                                         name.Name == "for-each" ||
478                                         name.Name == "message" ||
479                                         name.Name == "number" ||
480                                         name.Name == "processing-instruction" ||
481                                         name.Name == "text" ||
482                                         name.Name == "value-of" ||
483                                         name.Name == "variable"
484                                 )
485                         );
486                 }
487         }
488
489         class XsltFormatNumber : XPathFunction {
490                 Expression arg0, arg1, arg2;
491                 XmlNamespaceManager nsm;
492                 
493                 public XsltFormatNumber (FunctionArguments args, IStaticXsltContext ctx) : base (args)
494                 {
495                         if (args == null || args.Tail == null || (args.Tail.Tail != null && args.Tail.Tail.Tail != null))
496                                 throw new XPathException ("format-number takes 2 or 3 args");
497                         
498                         arg0 = args.Arg;
499                         arg1 = args.Tail.Arg;
500                         if (args.Tail.Tail != null) {
501                                 arg2= args.Tail.Tail.Arg;
502                                 nsm = ctx.GetNsm ();
503                         }
504                 }
505                 public override XPathResultType ReturnType { get { return XPathResultType.String; }}
506                 
507                 public override object Evaluate (BaseIterator iter)
508                 {
509                         double d = arg0.EvaluateNumber (iter);
510                         string s = arg1.EvaluateString (iter);
511                         QName nm = QName.Empty;
512                         
513                         if (arg2 != null)
514                                 nm = XslNameUtil.FromString (arg2.EvaluateString (iter), nsm);
515                         
516                         return (iter.NamespaceManager as XsltCompiledContext).Processor.CompiledStyle
517                                 .LookupDecimalFormat (nm).FormatNumber (d, s);
518                 }
519         }
520         
521         class XsltFunctionAvailable : XPathFunction {
522                 Expression arg0;
523                 XmlNamespaceManager nsm;
524                 
525                 public XsltFunctionAvailable (FunctionArguments args, IStaticXsltContext ctx) : base (args)
526                 {
527                         if (args == null || args.Tail != null)
528                                 throw new XPathException ("element-available takes 1 arg");
529                         
530                         arg0 = args.Arg;
531                         nsm = ctx.GetNsm ();
532                 }
533                 
534                 public override XPathResultType ReturnType { get { return XPathResultType.Boolean; }}
535                 
536                 public override object Evaluate (BaseIterator iter)
537                 {
538                         
539                         string name = arg0.EvaluateString (iter);
540                         int colon = name.IndexOf (':');
541                         // extension function
542                         if (colon > 0)
543                                 return (iter.NamespaceManager as XsltCompiledContext).ResolveFunction (
544                                         XslNameUtil.FromString (name, nsm),
545                                         null) != null;
546                         
547                         return (
548                                 //
549                                 // XPath
550                                 //
551                                 name == "boolean" ||
552                                 name == "ceiling" ||
553                                 name == "concat" ||
554                                 name == "contains" ||
555                                 name == "count" ||
556                                 name == "false" ||
557                                 name == "floor" ||
558                                 name == "id"||
559                                 name == "lang" ||
560                                 name == "last" ||
561                                 name == "local-name" ||
562                                 name == "name" ||
563                                 name == "namespace-uri" ||
564                                 name == "normalize-space" ||
565                                 name == "not" ||
566                                 name == "number" ||
567                                 name == "position" ||
568                                 name == "round" ||
569                                 name == "starts-with" ||
570                                 name == "string" ||
571                                 name == "string-length" ||
572                                 name == "substring" ||
573                                 name == "substring-after" ||
574                                 name == "substring-before" ||
575                                 name == "sum" ||
576                                 name == "translate" ||
577                                 name == "true" ||
578                                 // XSLT
579                                 name == "document" ||
580                                 name == "format-number" ||
581                                 name == "function-available" ||
582                                 name == "generate-id" ||
583                                 name == "key" ||
584                                 name == "current" ||
585                                 name == "unparsed-entity-uri" ||
586                                 name == "element-available" ||
587                                 name == "system-property"
588                         );
589                 }
590         } 
591
592         class XsltGenerateId : XPathFunction {
593                 Expression arg0;
594                 public XsltGenerateId (FunctionArguments args) : base (args)
595                 {
596                         if (args != null) {
597                                 if (args.Tail != null)
598                                         throw new XPathException ("generate-id takes 1 or no args");
599                                 arg0 = args.Arg;
600                         }
601                 }
602                 
603                 public override XPathResultType ReturnType { get { return XPathResultType.String; }}
604                 public override object Evaluate (BaseIterator iter)
605                 {
606                         XPathNavigator n;
607                         if (arg0 != null) {
608                                 XPathNodeIterator itr = arg0.EvaluateNodeSet (iter);
609                                 if (itr.MoveNext ())
610                                         n = itr.Current.Clone ();
611                                 else
612                                         return string.Empty; // empty nodeset == empty string
613                         } else
614                                 n = iter.Current.Clone ();
615                         
616                         StringBuilder sb = new StringBuilder ("Mono"); // Ensure begins with alpha
617                         sb.Append (XmlConvert.EncodeLocalName (n.BaseURI));
618                         sb.Replace ('_', 'm'); // remove underscores from EncodeLocalName
619                         sb.Append (n.NodeType);
620                         sb.Append ('m');
621
622                         do {
623                                 sb.Append (IndexInParent (n));
624                                 sb.Append ('m');
625                         } while (n.MoveToParent ());
626                         
627                         return sb.ToString ();
628                 }
629                 
630                 int IndexInParent (XPathNavigator nav)
631                 {
632                         int n = 0;
633                         while (nav.MoveToPrevious ())
634                                 n++;
635                         
636                         return n;
637                 }
638                 
639         } 
640         
641         class XsltKey : XPathFunction {
642                 Expression arg0, arg1;
643                 XmlNamespaceManager nsm;
644                 
645                 public XsltKey (FunctionArguments args, IStaticXsltContext ctx) : base (args)
646                 {
647                         if (args == null || args.Tail == null)
648                                 throw new XPathException ("key takes 2 args");
649                         arg0 = args.Arg;
650                         arg1 = args.Tail.Arg;
651                         nsm = ctx.GetNsm ();
652                 }
653                 public Expression KeyName { get { return arg0; } }
654                 public Expression Field { get { return arg1; } }
655                 public XmlNamespaceManager NamespaceManager { get { return nsm; } }
656                 public override XPathResultType ReturnType { get { return XPathResultType.NodeSet; }}
657                 
658                 public override object Evaluate (BaseIterator iter)
659                 {
660                         ArrayList result = new ArrayList ();
661                         QName name = XslNameUtil.FromString (arg0.EvaluateString (iter), nsm);
662                         object o = arg1.Evaluate (iter);
663                         XPathNodeIterator it = o as XPathNodeIterator;
664                         
665                         if (it != null) {
666                                 while (it.MoveNext())
667                                         FindKeyMatch ((iter.NamespaceManager as XsltCompiledContext), name, it.Current.Value, result, iter.Current);
668                         } else {
669                                 FindKeyMatch ((iter.NamespaceManager as XsltCompiledContext), name, XPathFunctions.ToString (o), result, iter.Current);
670                         }
671                         
672                         return new EnumeratorIterator (result.GetEnumerator (), (iter.NamespaceManager as XsltCompiledContext));
673                 }
674                 
675                 void FindKeyMatch (XsltCompiledContext xsltContext, QName name, string value, ArrayList result, XPathNavigator context)
676                 {
677                         XPathNavigator searchDoc = context.Clone ();
678                         searchDoc.MoveToRoot ();
679                         XslKey key = xsltContext.Processor.CompiledStyle.Style.FindKey (name);
680                         if (key != null) {
681                                 XPathNodeIterator desc = searchDoc.SelectDescendants (XPathNodeType.All, true);
682
683                                 while (desc.MoveNext ()) {
684                                         if (key.Matches (desc.Current, value))
685                                                 AddResult (result, desc.Current);
686                                         
687                                         if (desc.Current.MoveToFirstAttribute ()) {
688                                                 do {
689                                                         if (key.Matches (desc.Current, value))
690                                                                 AddResult (result, desc.Current);       
691                                                 } while (desc.Current.MoveToNextAttribute ());
692                                                 
693                                                 desc.Current.MoveToParent ();
694                                         }
695                                 }
696                         }
697                 }
698
699                 void AddResult (ArrayList result, XPathNavigator nav)
700                 {
701                         for (int i = 0; i < result.Count; i++) {
702                                 XmlNodeOrder docOrder = nav.ComparePosition (((XPathNavigator)result [i]));
703                                 if (docOrder == XmlNodeOrder.Same)
704                                         return;
705                                 
706                                 if (docOrder == XmlNodeOrder.Before) {
707                                         result.Insert(i, nav.Clone ());
708                                         return;
709                                 }
710                         }
711                         result.Add (nav.Clone ());
712                 }
713         }
714         
715         class XsltSystemProperty : XPathFunction {
716                 Expression arg0;
717                 XmlNamespaceManager nsm;
718                 
719                 public XsltSystemProperty (FunctionArguments args, IStaticXsltContext ctx) : base (args)
720                 {
721                         if (args == null || args.Tail != null)
722                                 throw new XPathException ("system-property takes 1 arg");
723                         
724                         arg0 = args.Arg;
725                         nsm = ctx.GetNsm ();
726                 }
727                 
728                 public override XPathResultType ReturnType { get { return XPathResultType.String; }}
729                 public override object Evaluate (BaseIterator iter)
730                 {
731                         QName name = XslNameUtil.FromString (arg0.EvaluateString (iter), nsm);
732                         
733                         if (name.Namespace == Compiler.XsltNamespace) {
734                                 switch (name.Name) {
735                                         case "version": return "1.0";
736                                         case "vendor": return "Mono";
737                                         case "vendor-url": return "http://www.go-mono.com/";
738                                 }
739                         }
740                         
741                         return "";
742                 }
743         } 
744
745         class XsltUnparsedEntityUri : XPathFunction {
746                 Expression arg0;
747                 
748                 public XsltUnparsedEntityUri (FunctionArguments args) : base (args)
749                 {
750                         if (args == null || args.Tail != null)
751                                 throw new XPathException ("unparsed-entity-uri takes 1 arg");
752                         
753                         arg0 = args.Arg;
754                 }
755                 
756                 public override XPathResultType ReturnType { get { return XPathResultType.String; }}
757                 public override object Evaluate (BaseIterator iter)
758                 {
759                         IHasXmlNode xn = iter.Current as IHasXmlNode;
760                         if (xn == null)
761                                 return String.Empty;
762                         XmlNode n = xn.GetNode ();
763                         XmlDocumentType doctype = n.OwnerDocument.DocumentType;
764                         if (doctype == null)
765                                 return String.Empty;
766                         XmlEntity ent = doctype.Entities.GetNamedItem (arg0.EvaluateString (iter)) as XmlEntity;
767                         if (ent == null)
768                                 return String.Empty;
769                         else
770                                 return ent.BaseURI;
771                 }
772         }
773 }