lots o optimizations
[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)
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 || p.Arguments == null) return null;
55
56                         object extension = p.Arguments.GetExtensionObject (ns);
57                                 
58                         if (extension == null)
59                                 return null;                    
60                         
61                         MethodInfo method = FindBestMethod (extension.GetType (), name.Name, argTypes);
62                         
63                         if (method != null) 
64                                 return new XsltExtensionFunction (extension, method);
65                         return null;
66                 }
67                 
68                 MethodInfo FindBestMethod (Type t, string name, XPathResultType [] argTypes)
69                 {
70                         int free, length;
71                         
72                         MethodInfo [] mi = t.GetMethods (BF.Public | BF.Instance | BF.Static);
73                         if (mi.Length == 0)
74                                 return null;
75                         
76                         if (argTypes == null)
77                                 return mi [0]; // if we dont have info on the arg types, nothing we can do
78
79
80                         free = 0;
81                         // filter on name + num args
82                         int numArgs = argTypes.Length;
83                         for (int i = 0; i < mi.Length; i ++) {
84                                 if (mi [i].Name == name && mi [i].GetParameters ().Length == numArgs) 
85                                         mi [free++] = mi [i];
86                         }
87                         length = free;
88                         
89                         // No method
90                         if (length == 0)
91                                 return null;
92                         
93                         // Thats it!
94                         if (length == 1)
95                                 return mi [0];
96                         
97                         free = 0;
98                         for (int i = 0; i < length; i ++) {
99                                 bool match = true;
100                                 ParameterInfo [] pi = mi [i].GetParameters ();
101                                 
102                                 for (int par = 0; par < pi.Length; par++) {
103                                         XPathResultType required = argTypes [par];
104                                         if (required == XPathResultType.Any)
105                                                 continue; // dunno what it is
106                                         
107                                         XPathResultType actual = XPFuncImpl.GetXPathType (pi [par].ParameterType);
108                                         if (actual != required && actual != XPathResultType.Any) {
109                                                 match = false;
110                                                 break;
111                                         }
112                                         
113                                         if (actual == XPathResultType.Any) {
114                                                 // try to get a stronger gind
115                                                 if (required != XPathResultType.NodeSet && !(pi [par].ParameterType == typeof (object)))
116                                                 {
117                                                         match = false;
118                                                         break;
119                                                 }
120                                         }
121                                 }
122                                 if (match) return mi [i]; // TODO look for exact match
123                         }
124                         return null;
125                 }
126                         
127                 public override IXsltContextVariable ResolveVariable (string prefix, string name)
128                 {
129                         throw new Exception ("shouldn't get here");
130                 }
131                 
132                 public override IXsltContextFunction ResolveFunction (string prefix, string name, XPathResultType [] ArgTypes)
133                 {
134                         throw new Exception ("shouldn't get here");
135                 }
136                 
137                 internal override System.Xml.Xsl.IXsltContextVariable ResolveVariable(QName q)
138                 {
139                         return p.CompiledStyle.ResolveVariable (q);
140                 }
141
142                 public override int CompareDocument (string baseUri, string nextBaseUri) { throw new NotImplementedException (); }
143                 public override bool PreserveWhitespace (XPathNavigator nav) { throw new NotImplementedException (); }
144                 public override bool Whitespace { get { throw new NotImplementedException (); }}
145         }
146
147
148 }
149 namespace Mono.Xml.Xsl.Functions {
150
151         internal abstract class XPFuncImpl : IXsltContextFunction {
152                 int minargs, maxargs;
153                 XPathResultType returnType;
154                 XPathResultType [] argTypes;
155
156                 public XPFuncImpl () {}
157                 public XPFuncImpl (int minArgs, int maxArgs, XPathResultType returnType, XPathResultType[] argTypes)
158                 {
159                         this.Init(minArgs, maxArgs, returnType, argTypes);
160                 }
161                 
162                 protected void Init (int minArgs, int maxArgs, XPathResultType returnType, XPathResultType[] argTypes)
163                 {
164                         this.minargs    = minArgs;
165                         this.maxargs    = maxArgs;
166                         this.returnType = returnType;
167                         this.argTypes   = argTypes;
168                 }
169
170                 public int Minargs { get { return this.minargs; }}
171                 public int Maxargs { get { return this.maxargs; }}
172                 public XPathResultType ReturnType { get { return this.returnType; }}
173                 public XPathResultType [] ArgTypes { get { return this.argTypes; }}
174                 public object Invoke (XsltContext xsltContext, object [] args, XPathNavigator docContext)
175                 {
176                         return Invoke ((XsltCompiledContext)xsltContext, args, docContext);
177                 }
178                 
179                 public abstract object Invoke (XsltCompiledContext xsltContext, object [] args, XPathNavigator docContext);
180                 
181                 public static XPathResultType GetXPathType (Type type) {
182                         switch (Type.GetTypeCode(type)) {
183                         case TypeCode.String:
184                                 return XPathResultType.String;
185                         case TypeCode.Boolean:
186                                 return XPathResultType.Boolean;
187                         case TypeCode.Object:
188                                 if (typeof (XPathNavigator).IsAssignableFrom (type) || typeof (IXPathNavigable).IsAssignableFrom (type))
189                                         return XPathResultType.Navigator;
190                                 
191                                 if (typeof (XPathNodeIterator).IsAssignableFrom (type))
192                                         return XPathResultType.NodeSet;
193                                 
194                                 return XPathResultType.Any;
195                         case TypeCode.DateTime :
196                                 throw new Exception ();
197                         default: // Numeric
198                                 return XPathResultType.Number;
199                         } 
200                 }
201         }
202         
203         class XsltExtensionFunction : XPFuncImpl {
204                 private object extension;
205                 private MethodInfo method;
206                 private TypeCode [] typeCodes;
207
208                 public XsltExtensionFunction (object extension, MethodInfo method)
209                 {
210                         this.extension = extension;
211                         this.method = method;
212
213                         ParameterInfo [] parameters = method.GetParameters ();
214                         int minArgs = parameters.Length;
215                         int maxArgs = parameters.Length;
216                         
217                         this.typeCodes = new TypeCode [parameters.Length];
218                         XPathResultType[] argTypes = new XPathResultType [parameters.Length];
219                         
220                         bool canBeOpt = true;
221                         for (int i = parameters.Length - 1; 0 <= i; i--) { // optionals at the end
222                                 typeCodes [i] = Type.GetTypeCode (parameters [i].ParameterType);
223                                 argTypes [i] = GetXPathType (parameters [i].ParameterType);
224                                 if (canBeOpt) {
225                                         if (parameters[i].IsOptional)
226                                                 minArgs --;
227                                         else
228                                                 canBeOpt = false;
229                                 }
230                         }
231                         base.Init (minArgs, maxArgs, GetXPathType (method.ReturnType), argTypes);
232                 }
233
234                 public override object Invoke (XsltCompiledContext xsltContext, object [] args, XPathNavigator docContext)
235                 {
236                         try {
237                                 object result = method.Invoke(extension, args);
238                                 IXPathNavigable navigable = result as IXPathNavigable;
239                                 if (navigable != null)
240                                         return navigable.CreateNavigator ();
241
242                                 return result;
243                         } catch {
244                                 Debug.WriteLine ("****** INCORRECT RESOLUTION **********");
245                                 return "";
246                         }
247                 }
248         }
249         
250         class XsltCurrent : XPathFunction {
251                 public XsltCurrent (FunctionArguments args) : base (args)
252                 {
253                         if (args != null)
254                                 throw new XPathException ("current takes 0 args");
255                 }
256                 
257                 public override XPathResultType ReturnType { get { return XPathResultType.NodeSet; }}
258
259                 public override object Evaluate (BaseIterator iter)
260                 {
261                         return new SelfIterator ((iter.NamespaceManager as XsltCompiledContext).Processor.CurrentNode, null);
262                 }
263         }
264         
265         class XsltDocument : XPathFunction {
266                 Expression arg0, arg1;
267                 XPathNavigator doc;
268                 
269                 public XsltDocument (FunctionArguments args, Compiler c) : base (args)
270                 {
271                         if (args == null || (args.Tail != null && args.Tail.Tail != null))
272                                 throw new XPathException ("document takes one or two args");
273                         
274                         arg0 = args.Arg;
275                         if (args.Tail != null)
276                                 arg1 = args.Tail.Arg;
277                         doc = c.Input.Clone ();
278                 }
279                 public override XPathResultType ReturnType { get { return XPathResultType.NodeSet; }}
280                 
281                 public override object Evaluate (BaseIterator iter)
282                 {
283                         string baseUri = null;
284                         if (arg1 != null) {
285                                 XPathNodeIterator it = arg1.EvaluateNodeSet (iter);
286                                 if (it.MoveNext())
287                                         baseUri = it.Current.BaseURI;
288                                 else
289                                         baseUri = VoidBaseUriFlag;
290                         }
291
292                         object o = arg0.Evaluate (iter);
293                         if (o is XPathNodeIterator)
294                                 return GetDocument ((iter.NamespaceManager as XsltCompiledContext), (XPathNodeIterator)o, baseUri);
295                         else
296                                 return GetDocument ((iter.NamespaceManager as XsltCompiledContext), o.ToString (), baseUri);
297                 }
298                 
299                 static string VoidBaseUriFlag = "&^)(*&%*^$&$VOID!BASE!URI!";
300                 
301                 Uri Resolve (string thisUri, string baseUri, XslTransformProcessor p)
302                 {
303                         Debug.WriteLine ("THIS: " + thisUri);
304                         Debug.WriteLine ("BASE: " + baseUri);
305                         XmlResolver r = p.Resolver;
306                         
307                         Uri uriBase = null;
308                         if (! object.ReferenceEquals (baseUri, VoidBaseUriFlag))
309                                 uriBase = r.ResolveUri (null, baseUri);
310                                 
311                         return r.ResolveUri (uriBase, thisUri);
312                 }
313                 
314                 XPathNodeIterator GetDocument (XsltCompiledContext xsltContext, XPathNodeIterator itr, string baseUri)
315                 {
316                         ArrayList list = new ArrayList ();
317                         Hashtable got = new Hashtable ();
318                         
319                         while (itr.MoveNext()) {
320                                 Uri uri = Resolve (itr.Current.Value, baseUri != null ? baseUri : itr.Current.BaseURI, xsltContext.Processor);
321                                 if (!got.ContainsKey (uri)) {
322                                         got.Add (uri, null);
323                                         if (uri.ToString () == "") {
324                                                 XPathNavigator n = doc.Clone ();
325                                                 n.MoveToRoot ();
326                                                 list.Add (n);
327                                         } else
328                                                 list.Add (xsltContext.Processor.GetDocument (uri));
329                                 }
330                         }
331                         
332                         return new EnumeratorIterator (list.GetEnumerator (), xsltContext);
333                 }
334         
335                 XPathNodeIterator GetDocument (XsltCompiledContext xsltContext, string arg0, string baseUri)
336                 {
337                         Uri uri = Resolve (arg0, baseUri != null ? baseUri : doc.BaseURI, xsltContext.Processor);
338                         XPathNavigator n;
339                         if (uri.ToString () == "") {
340                                 n = doc.Clone ();
341                                 n.MoveToRoot ();
342                         } else
343                                 n = xsltContext.Processor.GetDocument (uri);
344                         
345                         return new SelfIterator (n, null);
346                 }
347         }
348         
349         class XsltElementAvailable : XPathFunction {
350                 Expression arg0;
351                 XmlNamespaceManager nsm;
352                 
353                 public XsltElementAvailable (FunctionArguments args, IStaticXsltContext ctx) : base (args)
354                 {
355                         if (args == null || args.Tail != null)
356                                 throw new XPathException ("element-available takes 1 arg");
357                         
358                         arg0 = args.Arg;
359                         nsm = ctx.GetNsm ();
360                 }
361                 
362                 public override XPathResultType ReturnType { get { return XPathResultType.Boolean; }}
363
364                 public override object Evaluate (BaseIterator iter)
365                 {
366                         QName name = XslNameUtil.FromString (arg0.EvaluateString (iter), nsm);
367
368                         return (
369                                 (name.Namespace == Compiler.XsltNamespace) &&
370                                 (
371                                         //
372                                         // A list of all the instructions (does not include top-level-elements)
373                                         //
374                                         name.Name == "apply-imports" ||
375                                         name.Name == "apply-templates" ||
376                                         name.Name == "call-template" ||
377                                         name.Name == "choose" ||
378                                         name.Name == "comment" ||
379                                         name.Name == "copy" ||
380                                         name.Name == "copy-of" ||
381                                         name.Name == "element" ||
382                                         name.Name == "fallback" ||
383                                         name.Name == "for-each" ||
384                                         name.Name == "message" ||
385                                         name.Name == "number" ||
386                                         name.Name == "processing-instruction" ||
387                                         name.Name == "text" ||
388                                         name.Name == "value-of" ||
389                                         name.Name == "variable"
390                                 )
391                         );
392                 }
393         }
394
395         class XsltFormatNumber : XPathFunction {
396                 Expression arg0, arg1, arg2;
397                 XmlNamespaceManager nsm;
398                 
399                 public XsltFormatNumber (FunctionArguments args, IStaticXsltContext ctx) : base (args)
400                 {
401                         if (args == null || args.Tail == null || (args.Tail.Tail != null && args.Tail.Tail.Tail != null))
402                                 throw new XPathException ("format-number takes 2 or 3 args");
403                         
404                         arg0 = args.Arg;
405                         arg1 = args.Tail.Arg;
406                         if (args.Tail.Tail != null) {
407                                 arg2= args.Tail.Tail.Arg;
408                                 nsm = ctx.GetNsm ();
409                         }
410                 }
411                 public override XPathResultType ReturnType { get { return XPathResultType.String; }}
412                 
413                 public override object Evaluate (BaseIterator iter)
414                 {
415                         double d = arg0.EvaluateNumber (iter);
416                         string s = arg1.EvaluateString (iter);
417                         QName nm = QName.Empty;
418                         
419                         if (arg2 != null)
420                                 nm = XslNameUtil.FromString (arg2.EvaluateString (iter), nsm);
421                         
422                         return (iter.NamespaceManager as XsltCompiledContext).Processor.CompiledStyle
423                                 .LookupDecimalFormat (nm).FormatNumber (d, s);
424                 }
425         }
426         
427         class XsltFunctionAvailable : XPathFunction {
428                 Expression arg0;
429                 XmlNamespaceManager nsm;
430                 
431                 public XsltFunctionAvailable (FunctionArguments args, IStaticXsltContext ctx) : base (args)
432                 {
433                         if (args == null || args.Tail != null)
434                                 throw new XPathException ("element-available takes 1 arg");
435                         
436                         arg0 = args.Arg;
437                         nsm = ctx.GetNsm ();
438                 }
439                 
440                 public override XPathResultType ReturnType { get { return XPathResultType.Boolean; }}
441                 
442                 public override object Evaluate (BaseIterator iter)
443                 {
444                         
445                         string name = arg0.EvaluateString (iter);
446                         int colon = name.IndexOf (':');
447                         // extension function
448                         if (colon > 0)
449                                 return (iter.NamespaceManager as XsltCompiledContext).ResolveFunction (
450                                         XslNameUtil.FromString (name, nsm),
451                                         null) != null;
452                         
453                         return (
454                                 //
455                                 // XPath
456                                 //
457                                 name == "boolean" ||
458                                 name == "ceiling" ||
459                                 name == "concat" ||
460                                 name == "contains" ||
461                                 name == "count" ||
462                                 name == "false" ||
463                                 name == "floor" ||
464                                 name == "id"||
465                                 name == "lang" ||
466                                 name == "last" ||
467                                 name == "local-name" ||
468                                 name == "name" ||
469                                 name == "namespace-uri" ||
470                                 name == "normalize-space" ||
471                                 name == "not" ||
472                                 name == "number" ||
473                                 name == "position" ||
474                                 name == "round" ||
475                                 name == "starts-with" ||
476                                 name == "string" ||
477                                 name == "string-length" ||
478                                 name == "substring" ||
479                                 name == "substring-after" ||
480                                 name == "substring-before" ||
481                                 name == "sum" ||
482                                 name == "translate" ||
483                                 name == "true" ||
484                                 // XSLT
485                                 name == "document" ||
486                                 name == "format-number" ||
487                                 name == "function-available" ||
488                                 name == "generate-id" ||
489                                 name == "key" ||
490                                 name == "current" ||
491                                 name == "unparsed-entity-uri" ||
492                                 name == "element-available" ||
493                                 name == "system-property"
494                         );
495                 }
496         } 
497
498         class XsltGenerateId : XPathFunction {
499                 Expression arg0;
500                 public XsltGenerateId (FunctionArguments args) : base (args)
501                 {
502                         if (args != null) {
503                                 if (args.Tail != null)
504                                         throw new XPathException ("generate-id takes 1 or no args");
505                                 arg0 = args.Arg;
506                         }
507                 }
508                 
509                 public override XPathResultType ReturnType { get { return XPathResultType.NodeSet; }}
510                 public override object Evaluate (BaseIterator iter)
511                 {
512                         XPathNavigator n;
513                         if (arg0 != null) {
514                                 XPathNodeIterator itr = arg0.EvaluateNodeSet (iter);
515                                 if (itr.MoveNext ())
516                                         n = itr.Current.Clone ();
517                                 else
518                                         return string.Empty; // empty nodeset == empty string
519                         } else
520                                 n = iter.Current.Clone ();
521                         
522                         StringBuilder sb = new StringBuilder ("Mono"); // Ensure begins with alpha
523                         sb.Append (XmlConvert.EncodeLocalName (n.BaseURI));
524                         sb.Replace ('_', 'm'); // remove underscores from EncodeLocalName
525                         
526                         do {
527                                 sb.Append (IndexInParent (n));
528                                 sb.Append ('m');
529                         } while (n.MoveToParent ());
530                         
531                         return sb.ToString ();
532                 }
533                 
534                 int IndexInParent (XPathNavigator nav)
535                 {
536                         nav = nav.Clone();
537                         
538                         int n = 0;
539                         while (nav.MoveToPrevious ())
540                                 n++;
541                         
542                         return n;
543                 }
544                 
545         } 
546         
547         class XsltKey : XPathFunction {
548                 Expression arg0, arg1;
549                 XmlNamespaceManager nsm;
550                 
551                 public XsltKey (FunctionArguments args, IStaticXsltContext ctx) : base (args)
552                 {
553                         if (args == null || args.Tail == null)
554                                 throw new XPathException ("key takes 2 args");
555                         
556                         arg0 = args.Arg;
557                         arg1 = args.Tail.Arg;
558                         nsm = ctx.GetNsm ();
559                 }
560                 public override XPathResultType ReturnType { get { return XPathResultType.NodeSet; }}
561                 
562                 public override object Evaluate (BaseIterator iter)
563                 {
564                         ArrayList result = new ArrayList ();
565                         QName name = XslNameUtil.FromString (arg0.EvaluateString (iter), nsm);
566                         object o = arg1.Evaluate (iter);
567                         XPathNodeIterator it = o as XPathNodeIterator;
568                         
569                         if (it != null) {
570                                 while (it.MoveNext())
571                                         FindKeyMatch ((iter.NamespaceManager as XsltCompiledContext), name, it.Current.Value, result, iter.Current);
572                         } else {
573                                 FindKeyMatch ((iter.NamespaceManager as XsltCompiledContext), name, XPathFunctions.ToString (o), result, iter.Current);
574                         }
575                         
576                         return new EnumeratorIterator (result.GetEnumerator (), (iter.NamespaceManager as XsltCompiledContext));
577                 }
578                 
579                 void FindKeyMatch (XsltCompiledContext xsltContext, QName name, string value, ArrayList result, XPathNavigator context)
580                 {
581                         XPathNavigator searchDoc = context.Clone ();
582                         searchDoc.MoveToRoot ();
583                         foreach (XslKey key in xsltContext.Processor.CompiledStyle.Keys) {
584                                 if (key.Name == name) {
585                                         XPathNodeIterator desc = searchDoc.SelectDescendants (XPathNodeType.All, true);
586
587                                         while (desc.MoveNext ()) {
588                                                 if (key.Matches (desc.Current, value))
589                                                         AddResult (result, desc.Current);
590                                                 
591                                                 if (desc.Current.MoveToFirstAttribute ()) {
592                                                         do {
593                                                                 if (key.Matches (desc.Current, value))
594                                                                         AddResult (result, desc.Current);       
595                                                         } while (desc.Current.MoveToNext ());
596                                                         
597                                                         desc.Current.MoveToParent ();
598                                                 }
599                                         }
600                                 }
601                         }
602                 }
603
604                 void AddResult (ArrayList result, XPathNavigator nav)
605                 {
606                         for (int i = 0; i < result.Count; i++) {
607                                 XmlNodeOrder docOrder = nav.ComparePosition (((XPathNavigator)result [i]));
608                                 if (docOrder == XmlNodeOrder.Same)
609                                         return;
610                                 
611                                 if (docOrder == XmlNodeOrder.Before) {
612                                         result.Insert(i, nav.Clone ());
613                                         return;
614                                 }
615                         }
616                         result.Add (nav.Clone ());
617                 }
618         }
619         
620         class XsltSystemProperty : XPathFunction {
621                 Expression arg0;
622                 XmlNamespaceManager nsm;
623                 
624                 public XsltSystemProperty (FunctionArguments args, IStaticXsltContext ctx) : base (args)
625                 {
626                         if (args == null || args.Tail != null)
627                                 throw new XPathException ("system-property takes 1 arg");
628                         
629                         arg0 = args.Arg;
630                         nsm = ctx.GetNsm ();
631                 }
632                 
633                 public override XPathResultType ReturnType { get { return XPathResultType.String; }}
634                 public override object Evaluate (BaseIterator iter)
635                 {
636                         QName name = XslNameUtil.FromString (arg0.EvaluateString (iter), nsm);
637                         
638                         if (name.Namespace == Compiler.XsltNamespace) {
639                                 switch (name.Name) {
640                                         case "version": return "1.0";
641                                         case "vendor": return "Mono";
642                                         case "vendor-url": return "http://www.go-mono.com/";
643                                 }
644                         }
645                         
646                         return "";
647                 }
648         } 
649
650         class XsltUnparsedEntityUri : XPathFunction {
651                 Expression arg0;
652                 
653                 public XsltUnparsedEntityUri (FunctionArguments args) : base (args)
654                 {
655                         if (args == null || args.Tail != null)
656                                 throw new XPathException ("unparsed-entity-uri takes 1 arg");
657                         
658                         arg0 = args.Arg;
659                 }
660                 
661                 public override XPathResultType ReturnType { get { return XPathResultType.String; }}
662                 public override object Evaluate (BaseIterator iter)
663                 {
664                         throw new NotImplementedException ();
665                 }
666         }
667 }