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