2004-03-22 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / class / System.XML / Mono.Xml.Xsl / XslFunctions.cs
1 //
2 // XsltCompiledContext.cs
3 //
4 // Authors:
5 //      Ben Maurer (bmaurer@users.sourceforge.net)
6 //      Atsushi Enomoto (atsushi@ximian.com)
7 // (C) 2003 Ben Maurer
8 // (C) 2004 Atsushi Enomoto
9 //
10 using System;
11 using System.Collections;
12 using System.Reflection;
13 using System.Text;
14 using System.Xml;
15 using System.Xml.XPath;
16 using System.Xml.Xsl;
17 using Mono.Xml.Xsl;
18
19 using QName = System.Xml.XmlQualifiedName;
20
21 namespace Mono.Xml.Xsl
22 {
23         internal abstract class XPFuncImpl : IXsltContextFunction 
24         {
25                 int minargs, maxargs;
26                 XPathResultType returnType;
27                 XPathResultType [] argTypes;
28
29                 public XPFuncImpl () {}
30                 public XPFuncImpl (int minArgs, int maxArgs, XPathResultType returnType, XPathResultType[] argTypes)
31                 {
32                         this.Init(minArgs, maxArgs, returnType, argTypes);
33                 }
34                 
35                 protected void Init (int minArgs, int maxArgs, XPathResultType returnType, XPathResultType[] argTypes)
36                 {
37                         this.minargs    = minArgs;
38                         this.maxargs    = maxArgs;
39                         this.returnType = returnType;
40                         this.argTypes   = argTypes;
41                 }
42
43                 public int Minargs { get { return this.minargs; }}
44                 public int Maxargs { get { return this.maxargs; }}
45                 public XPathResultType ReturnType { get { return this.returnType; }}
46                 public XPathResultType [] ArgTypes { get { return this.argTypes; }}
47                 public object Invoke (XsltContext xsltContext, object [] args, XPathNavigator docContext)
48                 {
49                         return Invoke ((XsltCompiledContext)xsltContext, args, docContext);
50                 }
51                 
52                 public abstract object Invoke (XsltCompiledContext xsltContext, object [] args, XPathNavigator docContext);
53                 
54                 public static XPathResultType GetXPathType (Type type, XPathNavigator node) {
55                         switch (Type.GetTypeCode(type)) {
56                         case TypeCode.String:
57                                 return XPathResultType.String;
58                         case TypeCode.Boolean:
59                                 return XPathResultType.Boolean;
60                         case TypeCode.Object:
61                                 if (typeof (XPathNavigator).IsAssignableFrom (type) || typeof (IXPathNavigable).IsAssignableFrom (type))
62                                         return XPathResultType.Navigator;
63                                 
64                                 if (typeof (XPathNodeIterator).IsAssignableFrom (type))
65                                         return XPathResultType.NodeSet;
66                                 
67                                 return XPathResultType.Any;
68                         case TypeCode.DateTime :
69                                 throw new XsltException ("Invalid type DateTime was specified.", null, node);
70                         default: // Numeric
71                                 return XPathResultType.Number;
72                         } 
73                 }
74         }
75         
76         class XsltExtensionFunction : XPFuncImpl 
77         {
78                 private object extension;
79                 private MethodInfo method;
80                 private TypeCode [] typeCodes;
81
82                 public XsltExtensionFunction (object extension, MethodInfo method, XPathNavigator currentNode)
83                 {
84                         this.extension = extension;
85                         this.method = method;
86
87                         ParameterInfo [] parameters = method.GetParameters ();
88                         int minArgs = parameters.Length;
89                         int maxArgs = parameters.Length;
90                         
91                         this.typeCodes = new TypeCode [parameters.Length];
92                         XPathResultType[] argTypes = new XPathResultType [parameters.Length];
93                         
94                         bool canBeOpt = true;
95                         for (int i = parameters.Length - 1; 0 <= i; i--) { // optionals at the end
96                                 typeCodes [i] = Type.GetTypeCode (parameters [i].ParameterType);
97                                 argTypes [i] = GetXPathType (parameters [i].ParameterType, currentNode);
98                                 if (canBeOpt) {
99                                         if (parameters[i].IsOptional)
100                                                 minArgs --;
101                                         else
102                                                 canBeOpt = false;
103                                 }
104                         }
105                         base.Init (minArgs, maxArgs, GetXPathType (method.ReturnType, currentNode), argTypes);
106                 }
107
108                 public override object Invoke (XsltCompiledContext xsltContext, object [] args, XPathNavigator docContext)
109                 {
110                         try {
111                                 ParameterInfo [] pis = method.GetParameters ();
112                                 object [] castedArgs = new object [pis.Length];
113                                 for (int i = 0; i < args.Length; i++) {
114                                         Type t = pis [i].ParameterType;
115                                         switch (t.FullName) {
116                                         case "System.Int16":
117                                         case "System.UInt16":
118                                         case "System.Int32":
119                                         case "System.UInt32":
120                                         case "System.Int64":
121                                         case "System.UInt64":
122                                         case "System.Single":
123                                         case "System.Decimal":
124                                                 castedArgs [i] = Convert.ChangeType (args [i], t);
125                                                 break;
126                                         default:
127                                                 castedArgs [i] = args [i];
128                                                 break;
129                                         }
130                                 }
131
132                                 object result = null;
133                                 switch (method.ReturnType.FullName) {
134                                 case "System.Int16":
135                                 case "System.UInt16":
136                                 case "System.Int32":
137                                 case "System.UInt32":
138                                 case "System.Int64":
139                                 case "System.UInt64":
140                                 case "System.Single":
141                                 case "System.Decimal":
142                                         result = (double) method.Invoke (extension, castedArgs);
143                                         break;
144                                 default:
145                                         result = method.Invoke(extension, castedArgs);
146                                         break;
147                                 }
148                                 IXPathNavigable navigable = result as IXPathNavigable;
149                                 if (navigable != null)
150                                         return navigable.CreateNavigator ();
151
152                                 return result;
153                         } catch (Exception ex) {
154                                 throw new XsltException ("Custom function reported an error.", ex);
155 //                              Debug.WriteLine ("****** INCORRECT RESOLUTION **********");
156                         }
157                 }
158         }
159         
160         class XsltCurrent : XPathFunction 
161         {
162                 public XsltCurrent (FunctionArguments args) : base (args)
163                 {
164                         if (args != null)
165                                 throw new XPathException ("current takes 0 args");
166                 }
167                 
168                 public override XPathResultType ReturnType { get { return XPathResultType.NodeSet; }}
169
170                 public override object Evaluate (BaseIterator iter)
171                 {
172                         return new SelfIterator ((iter.NamespaceManager as XsltCompiledContext).Processor.CurrentNode, null);
173                 }
174         }
175         
176         class XsltDocument : XPathFunction 
177         {
178                 Expression arg0, arg1;
179                 XPathNavigator doc;
180                 
181                 public XsltDocument (FunctionArguments args, Compiler c) : base (args)
182                 {
183                         if (args == null || (args.Tail != null && args.Tail.Tail != null))
184                                 throw new XPathException ("document takes one or two args");
185                         
186                         arg0 = args.Arg;
187                         if (args.Tail != null)
188                                 arg1 = args.Tail.Arg;
189                         doc = c.Input.Clone ();
190                 }
191                 public override XPathResultType ReturnType { get { return XPathResultType.NodeSet; }}
192                 
193                 public override object Evaluate (BaseIterator iter)
194                 {
195                         string baseUri = null;
196                         if (arg1 != null) {
197                                 XPathNodeIterator it = arg1.EvaluateNodeSet (iter);
198                                 if (it.MoveNext())
199                                         baseUri = it.Current.BaseURI;
200                                 else
201                                         baseUri = VoidBaseUriFlag;
202                         }
203
204                         object o = arg0.Evaluate (iter);
205                         if (o is XPathNodeIterator)
206                                 return GetDocument ((iter.NamespaceManager as XsltCompiledContext), (XPathNodeIterator)o, baseUri);
207                         else
208                                 return GetDocument ((iter.NamespaceManager as XsltCompiledContext), o.ToString (), baseUri);
209                 }
210                 
211                 static string VoidBaseUriFlag = "&^)(*&%*^$&$VOID!BASE!URI!";
212                 
213                 Uri Resolve (string thisUri, string baseUri, XslTransformProcessor p)
214                 {
215 //                      Debug.WriteLine ("THIS: " + thisUri);
216 //                      Debug.WriteLine ("BASE: " + baseUri);
217                         XmlResolver r = p.Resolver;
218                         
219                         Uri uriBase = null;
220                         if (! object.ReferenceEquals (baseUri, VoidBaseUriFlag))
221                                 uriBase = r.ResolveUri (null, baseUri);
222                                 
223                         return r.ResolveUri (uriBase, thisUri);
224                 }
225                 
226                 XPathNodeIterator GetDocument (XsltCompiledContext xsltContext, XPathNodeIterator itr, string baseUri)
227                 {
228                         ArrayList list = new ArrayList ();
229                         Hashtable got = new Hashtable ();
230                         
231                         while (itr.MoveNext()) {
232                                 Uri uri = Resolve (itr.Current.Value, baseUri != null ? baseUri : /*itr.Current.BaseURI*/doc.BaseURI, xsltContext.Processor);
233                                 if (!got.ContainsKey (uri)) {
234                                         got.Add (uri, null);
235                                         if (uri.ToString () == "") {
236                                                 XPathNavigator n = doc.Clone ();
237                                                 n.MoveToRoot ();
238                                                 list.Add (n);
239                                         } else
240                                                 list.Add (xsltContext.Processor.GetDocument (uri));
241                                 }
242                         }
243                         
244                         return new ListIterator (list, xsltContext, false);
245                 }
246         
247                 XPathNodeIterator GetDocument (XsltCompiledContext xsltContext, string arg0, string baseUri)
248                 {
249                         Uri uri = Resolve (arg0, baseUri != null ? baseUri : doc.BaseURI, xsltContext.Processor);
250                         XPathNavigator n;
251                         if (uri.ToString () == "") {
252                                 n = doc.Clone ();
253                                 n.MoveToRoot ();
254                         } else
255                                 n = xsltContext.Processor.GetDocument (uri);
256                         
257                         return new SelfIterator (n, xsltContext);
258                 }
259         }
260         
261         class XsltElementAvailable : XPathFunction 
262         {
263                 Expression arg0;
264                 XmlNamespaceManager nsm;
265                 
266                 public XsltElementAvailable (FunctionArguments args, IStaticXsltContext ctx) : base (args)
267                 {
268                         if (args == null || args.Tail != null)
269                                 throw new XPathException ("element-available takes 1 arg");
270                         
271                         arg0 = args.Arg;
272                         nsm = ctx.GetNsm ();
273                 }
274                 
275                 public override XPathResultType ReturnType { get { return XPathResultType.Boolean; }}
276
277                 public override object Evaluate (BaseIterator iter)
278                 {
279                         QName name = XslNameUtil.FromString (arg0.EvaluateString (iter), nsm);
280
281                         return (
282                                 (name.Namespace == Compiler.XsltNamespace) &&
283                                 (
284                                         //
285                                         // A list of all the instructions (does not include top-level-elements)
286                                         //
287                                         name.Name == "apply-imports" ||
288                                         name.Name == "apply-templates" ||
289                                         name.Name == "call-template" ||
290                                         name.Name == "choose" ||
291                                         name.Name == "comment" ||
292                                         name.Name == "copy" ||
293                                         name.Name == "copy-of" ||
294                                         name.Name == "element" ||
295                                         name.Name == "fallback" ||
296                                         name.Name == "for-each" ||
297                                         name.Name == "message" ||
298                                         name.Name == "number" ||
299                                         name.Name == "processing-instruction" ||
300                                         name.Name == "text" ||
301                                         name.Name == "value-of" ||
302                                         name.Name == "variable"
303                                 )
304                         );
305                 }
306         }
307
308         class XsltFormatNumber : XPathFunction 
309         {
310                 Expression arg0, arg1, arg2;
311                 XmlNamespaceManager nsm;
312                 
313                 public XsltFormatNumber (FunctionArguments args, IStaticXsltContext ctx) : base (args)
314                 {
315                         if (args == null || args.Tail == null || (args.Tail.Tail != null && args.Tail.Tail.Tail != null))
316                                 throw new XPathException ("format-number takes 2 or 3 args");
317                         
318                         arg0 = args.Arg;
319                         arg1 = args.Tail.Arg;
320                         if (args.Tail.Tail != null) {
321                                 arg2= args.Tail.Tail.Arg;
322                                 nsm = ctx.GetNsm ();
323                         }
324                 }
325                 public override XPathResultType ReturnType { get { return XPathResultType.String; }}
326                 
327                 public override object Evaluate (BaseIterator iter)
328                 {
329                         double d = arg0.EvaluateNumber (iter);
330                         string s = arg1.EvaluateString (iter);
331                         QName nm = QName.Empty;
332                         
333                         if (arg2 != null)
334                                 nm = XslNameUtil.FromString (arg2.EvaluateString (iter), nsm);
335                         
336                         try {
337                                 return (iter.NamespaceManager as XsltCompiledContext).Processor.CompiledStyle
338                                 .LookupDecimalFormat (nm).FormatNumber (d, s);
339                         } catch (ArgumentException ex) {
340                                 throw new XsltException (ex.Message, ex, iter.Current);
341                         }
342                 }
343         }
344         
345         class XsltFunctionAvailable : XPathFunction 
346         {
347                 Expression arg0;
348                 XmlNamespaceManager nsm;
349                 
350                 public XsltFunctionAvailable (FunctionArguments args, IStaticXsltContext ctx) : base (args)
351                 {
352                         if (args == null || args.Tail != null)
353                                 throw new XPathException ("element-available takes 1 arg");
354                         
355                         arg0 = args.Arg;
356                         nsm = ctx.GetNsm ();
357                 }
358                 
359                 public override XPathResultType ReturnType { get { return XPathResultType.Boolean; }}
360                 
361                 public override object Evaluate (BaseIterator iter)
362                 {
363                         
364                         string name = arg0.EvaluateString (iter);
365                         int colon = name.IndexOf (':');
366                         // extension function
367                         if (colon > 0)
368                                 return (iter.NamespaceManager as XsltCompiledContext).ResolveFunction (
369                                         XslNameUtil.FromString (name, nsm),
370                                         null) != null;
371                         
372                         return (
373                                 //
374                                 // XPath
375                                 //
376                                 name == "boolean" ||
377                                 name == "ceiling" ||
378                                 name == "concat" ||
379                                 name == "contains" ||
380                                 name == "count" ||
381                                 name == "false" ||
382                                 name == "floor" ||
383                                 name == "id"||
384                                 name == "lang" ||
385                                 name == "last" ||
386                                 name == "local-name" ||
387                                 name == "name" ||
388                                 name == "namespace-uri" ||
389                                 name == "normalize-space" ||
390                                 name == "not" ||
391                                 name == "number" ||
392                                 name == "position" ||
393                                 name == "round" ||
394                                 name == "starts-with" ||
395                                 name == "string" ||
396                                 name == "string-length" ||
397                                 name == "substring" ||
398                                 name == "substring-after" ||
399                                 name == "substring-before" ||
400                                 name == "sum" ||
401                                 name == "translate" ||
402                                 name == "true" ||
403                                 // XSLT
404                                 name == "document" ||
405                                 name == "format-number" ||
406                                 name == "function-available" ||
407                                 name == "generate-id" ||
408                                 name == "key" ||
409                                 name == "current" ||
410                                 name == "unparsed-entity-uri" ||
411                                 name == "element-available" ||
412                                 name == "system-property"
413                         );
414                 }
415         } 
416
417         class XsltGenerateId : XPathFunction 
418         {
419                 Expression arg0;
420                 public XsltGenerateId (FunctionArguments args) : base (args)
421                 {
422                         if (args != null) {
423                                 if (args.Tail != null)
424                                         throw new XPathException ("generate-id takes 1 or no args");
425                                 arg0 = args.Arg;
426                         }
427                 }
428                 
429                 public override XPathResultType ReturnType { get { return XPathResultType.String; }}
430                 public override object Evaluate (BaseIterator iter)
431                 {
432                         XPathNavigator n;
433                         if (arg0 != null) {
434                                 XPathNodeIterator itr = arg0.EvaluateNodeSet (iter);
435                                 if (itr.MoveNext ())
436                                         n = itr.Current.Clone ();
437                                 else
438                                         return string.Empty; // empty nodeset == empty string
439                         } else
440                                 n = iter.Current.Clone ();
441                         
442                         StringBuilder sb = new StringBuilder ("Mono"); // Ensure begins with alpha
443                         sb.Append (XmlConvert.EncodeLocalName (n.BaseURI));
444                         sb.Replace ('_', 'm'); // remove underscores from EncodeLocalName
445                         sb.Append (n.NodeType);
446                         sb.Append ('m');
447
448                         do {
449                                 sb.Append (IndexInParent (n));
450                                 sb.Append ('m');
451                         } while (n.MoveToParent ());
452                         
453                         return sb.ToString ();
454                 }
455                 
456                 int IndexInParent (XPathNavigator nav)
457                 {
458                         int n = 0;
459                         while (nav.MoveToPrevious ())
460                                 n++;
461                         
462                         return n;
463                 }
464         } 
465         
466         class XsltKey : XPathFunction 
467         {
468                 Expression arg0, arg1;
469                 XmlNamespaceManager nsm;
470                 XslKey key;
471                 
472                 public XsltKey (FunctionArguments args, IStaticXsltContext ctx) : base (args)
473                 {
474                         if (args == null || args.Tail == null)
475                                 throw new XPathException ("key takes 2 args");
476                         arg0 = args.Arg;
477                         arg1 = args.Tail.Arg;
478                         nsm = ctx.GetNsm ();
479                 }
480                 public Expression KeyName { get { return arg0; } }
481                 public Expression Field { get { return arg1; } }
482                 public XmlNamespaceManager NamespaceManager { get { return nsm; } }
483                 public override XPathResultType ReturnType { get { return XPathResultType.NodeSet; }}
484                 
485                 public override object Evaluate (BaseIterator iter)
486                 {
487                         QName name = XslNameUtil.FromString (arg0.EvaluateString (iter), nsm);
488                         XsltCompiledContext ctx = iter.NamespaceManager as XsltCompiledContext;
489                         if (key == null)
490                                 key = ctx.Processor.CompiledStyle.Style.FindKey (name);
491
492                         ArrayList result = new ArrayList ();
493                         object o = arg1.Evaluate (iter);
494                         XPathNodeIterator it = o as XPathNodeIterator;
495                         
496                         if (it != null) {
497                                 while (it.MoveNext())
498                                         FindKeyMatch (ctx, it.Current.Value, result, iter.Current);
499                         } else {
500                                 FindKeyMatch (ctx, XPathFunctions.ToString (o), result, iter.Current);
501                         }
502                         
503                         return new ListIterator (result, (iter.NamespaceManager as XsltCompiledContext), true);
504                 }
505                 
506                 void FindKeyMatch (XsltCompiledContext xsltContext, string value, ArrayList result, XPathNavigator context)
507                 {
508                         XPathNavigator searchDoc = context.Clone ();
509                         searchDoc.MoveToRoot ();
510                         if (key != null) {
511                                 XPathNodeIterator desc = searchDoc.SelectDescendants (XPathNodeType.All, true);
512
513                                 while (desc.MoveNext ()) {
514                                         if (key.Matches (desc.Current, xsltContext, value))
515                                                 AddResult (result, desc.Current);
516                                         
517                                         if (desc.Current.MoveToFirstAttribute ()) {
518                                                 do {
519                                                         if (key.Matches (desc.Current, xsltContext, value))
520                                                                 AddResult (result, desc.Current);       
521                                                 } while (desc.Current.MoveToNextAttribute ());
522                                                 
523                                                 desc.Current.MoveToParent ();
524                                         }
525                                 }
526                         }
527                 }
528
529                 void AddResult (ArrayList result, XPathNavigator nav)
530                 {
531                         for (int i = 0; i < result.Count; i++) {
532                                 XmlNodeOrder docOrder = nav.ComparePosition (((XPathNavigator)result [i]));
533                                 if (docOrder == XmlNodeOrder.Same)
534                                         return;
535                                 
536                                 if (docOrder == XmlNodeOrder.Before) {
537                                         result.Insert(i, nav.Clone ());
538                                         return;
539                                 }
540                         }
541                         result.Add (nav.Clone ());
542                 }
543         }
544         
545         class XsltSystemProperty : XPathFunction 
546         {
547                 Expression arg0;
548                 XmlNamespaceManager nsm;
549                 
550                 public XsltSystemProperty (FunctionArguments args, IStaticXsltContext ctx) : base (args)
551                 {
552                         if (args == null || args.Tail != null)
553                                 throw new XPathException ("system-property takes 1 arg");
554                         
555                         arg0 = args.Arg;
556                         nsm = ctx.GetNsm ();
557                 }
558                 
559                 public override XPathResultType ReturnType { get { return XPathResultType.String; }}
560                 public override object Evaluate (BaseIterator iter)
561                 {
562                         QName name = XslNameUtil.FromString (arg0.EvaluateString (iter), nsm);
563                         
564                         if (name.Namespace == Compiler.XsltNamespace) {
565                                 switch (name.Name) {
566                                         case "version": return "1.0";
567                                         case "vendor": return "Mono";
568                                         case "vendor-url": return "http://www.go-mono.com/";
569                                 }
570                         }
571                         
572                         return "";
573                 }
574         } 
575
576         class XsltUnparsedEntityUri : XPathFunction 
577         {
578                 Expression arg0;
579                 
580                 public XsltUnparsedEntityUri (FunctionArguments args) : base (args)
581                 {
582                         if (args == null || args.Tail != null)
583                                 throw new XPathException ("unparsed-entity-uri takes 1 arg");
584                         
585                         arg0 = args.Arg;
586                 }
587                 
588                 public override XPathResultType ReturnType { get { return XPathResultType.String; }}
589                 public override object Evaluate (BaseIterator iter)
590                 {
591                         IHasXmlNode xn = iter.Current as IHasXmlNode;
592                         if (xn == null)
593                                 return String.Empty;
594                         XmlNode n = xn.GetNode ();
595                         XmlDocumentType doctype = n.OwnerDocument.DocumentType;
596                         if (doctype == null)
597                                 return String.Empty;
598                         XmlEntity ent = doctype.Entities.GetNamedItem (arg0.EvaluateString (iter)) as XmlEntity;
599                         if (ent == null)
600                                 return String.Empty;
601                         else
602                                 return ent.BaseURI;
603                 }
604         }
605 }