2 // XsltCompiledContext.cs
5 // Ben Maurer (bmaurer@users.sourceforge.net)
6 // Atsushi Enomoto (atsushi@ximian.com)
8 // (C) 2004 Atsushi Enomoto
11 using System.Collections;
12 using System.Reflection;
15 using System.Xml.XPath;
19 using QName = System.Xml.XmlQualifiedName;
21 namespace Mono.Xml.Xsl
23 internal abstract class XPFuncImpl : IXsltContextFunction
26 XPathResultType returnType;
27 XPathResultType [] argTypes;
29 public XPFuncImpl () {}
30 public XPFuncImpl (int minArgs, int maxArgs, XPathResultType returnType, XPathResultType[] argTypes)
32 this.Init(minArgs, maxArgs, returnType, argTypes);
35 protected void Init (int minArgs, int maxArgs, XPathResultType returnType, XPathResultType[] argTypes)
37 this.minargs = minArgs;
38 this.maxargs = maxArgs;
39 this.returnType = returnType;
40 this.argTypes = argTypes;
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)
49 return Invoke ((XsltCompiledContext)xsltContext, args, docContext);
52 public abstract object Invoke (XsltCompiledContext xsltContext, object [] args, XPathNavigator docContext);
54 public static XPathResultType GetXPathType (Type type, XPathNavigator node) {
55 switch (Type.GetTypeCode(type)) {
57 return XPathResultType.String;
58 case TypeCode.Boolean:
59 return XPathResultType.Boolean;
61 if (typeof (XPathNavigator).IsAssignableFrom (type) || typeof (IXPathNavigable).IsAssignableFrom (type))
62 return XPathResultType.Navigator;
64 if (typeof (XPathNodeIterator).IsAssignableFrom (type))
65 return XPathResultType.NodeSet;
67 return XPathResultType.Any;
68 case TypeCode.DateTime :
69 throw new XsltException ("Invalid type DateTime was specified.", null, node);
71 return XPathResultType.Number;
76 class XsltExtensionFunction : XPFuncImpl
78 private object extension;
79 private MethodInfo method;
80 private TypeCode [] typeCodes;
82 public XsltExtensionFunction (object extension, MethodInfo method, XPathNavigator currentNode)
84 this.extension = extension;
87 ParameterInfo [] parameters = method.GetParameters ();
88 int minArgs = parameters.Length;
89 int maxArgs = parameters.Length;
91 this.typeCodes = new TypeCode [parameters.Length];
92 XPathResultType[] argTypes = new XPathResultType [parameters.Length];
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);
99 if (parameters[i].IsOptional)
105 base.Init (minArgs, maxArgs, GetXPathType (method.ReturnType, currentNode), argTypes);
108 public override object Invoke (XsltCompiledContext xsltContext, object [] args, XPathNavigator docContext)
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) {
117 case "System.UInt16":
119 case "System.UInt32":
121 case "System.UInt64":
122 case "System.Single":
123 case "System.Decimal":
124 castedArgs [i] = Convert.ChangeType (args [i], t);
127 castedArgs [i] = args [i];
132 object result = null;
133 switch (method.ReturnType.FullName) {
135 case "System.UInt16":
137 case "System.UInt32":
139 case "System.UInt64":
140 case "System.Single":
141 case "System.Decimal":
142 result = (double) method.Invoke (extension, castedArgs);
145 result = method.Invoke(extension, castedArgs);
148 IXPathNavigable navigable = result as IXPathNavigable;
149 if (navigable != null)
150 return navigable.CreateNavigator ();
153 } catch (Exception ex) {
154 throw new XsltException ("Custom function reported an error.", ex);
155 // Debug.WriteLine ("****** INCORRECT RESOLUTION **********");
160 class XsltCurrent : XPathFunction
162 public XsltCurrent (FunctionArguments args) : base (args)
165 throw new XPathException ("current takes 0 args");
168 public override XPathResultType ReturnType { get { return XPathResultType.NodeSet; }}
170 public override object Evaluate (BaseIterator iter)
172 return new SelfIterator ((iter.NamespaceManager as XsltCompiledContext).Processor.CurrentNode, null);
176 class XsltDocument : XPathFunction
178 Expression arg0, arg1;
181 public XsltDocument (FunctionArguments args, Compiler c) : base (args)
183 if (args == null || (args.Tail != null && args.Tail.Tail != null))
184 throw new XPathException ("document takes one or two args");
187 if (args.Tail != null)
188 arg1 = args.Tail.Arg;
189 doc = c.Input.Clone ();
191 public override XPathResultType ReturnType { get { return XPathResultType.NodeSet; }}
193 public override object Evaluate (BaseIterator iter)
195 string baseUri = null;
197 XPathNodeIterator it = arg1.EvaluateNodeSet (iter);
199 baseUri = it.Current.BaseURI;
201 baseUri = VoidBaseUriFlag;
204 object o = arg0.Evaluate (iter);
205 if (o is XPathNodeIterator)
206 return GetDocument ((iter.NamespaceManager as XsltCompiledContext), (XPathNodeIterator)o, baseUri);
208 return GetDocument ((iter.NamespaceManager as XsltCompiledContext), o.ToString (), baseUri);
211 static string VoidBaseUriFlag = "&^)(*&%*^$&$VOID!BASE!URI!";
213 Uri Resolve (string thisUri, string baseUri, XslTransformProcessor p)
215 // Debug.WriteLine ("THIS: " + thisUri);
216 // Debug.WriteLine ("BASE: " + baseUri);
217 XmlResolver r = p.Resolver;
220 if (! object.ReferenceEquals (baseUri, VoidBaseUriFlag))
221 uriBase = r.ResolveUri (null, baseUri);
223 return r.ResolveUri (uriBase, thisUri);
226 XPathNodeIterator GetDocument (XsltCompiledContext xsltContext, XPathNodeIterator itr, string baseUri)
228 ArrayList list = new ArrayList ();
229 Hashtable got = new Hashtable ();
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)) {
235 if (uri.ToString () == "") {
236 XPathNavigator n = doc.Clone ();
240 list.Add (xsltContext.Processor.GetDocument (uri));
244 return new ListIterator (list, xsltContext, false);
247 XPathNodeIterator GetDocument (XsltCompiledContext xsltContext, string arg0, string baseUri)
249 Uri uri = Resolve (arg0, baseUri != null ? baseUri : doc.BaseURI, xsltContext.Processor);
251 if (uri.ToString () == "") {
255 n = xsltContext.Processor.GetDocument (uri);
257 return new SelfIterator (n, xsltContext);
261 class XsltElementAvailable : XPathFunction
264 XmlNamespaceManager nsm;
266 public XsltElementAvailable (FunctionArguments args, IStaticXsltContext ctx) : base (args)
268 if (args == null || args.Tail != null)
269 throw new XPathException ("element-available takes 1 arg");
275 public override XPathResultType ReturnType { get { return XPathResultType.Boolean; }}
277 public override object Evaluate (BaseIterator iter)
279 QName name = XslNameUtil.FromString (arg0.EvaluateString (iter), nsm);
282 (name.Namespace == Compiler.XsltNamespace) &&
285 // A list of all the instructions (does not include top-level-elements)
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"
308 class XsltFormatNumber : XPathFunction
310 Expression arg0, arg1, arg2;
311 XmlNamespaceManager nsm;
313 public XsltFormatNumber (FunctionArguments args, IStaticXsltContext ctx) : base (args)
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");
319 arg1 = args.Tail.Arg;
320 if (args.Tail.Tail != null) {
321 arg2= args.Tail.Tail.Arg;
325 public override XPathResultType ReturnType { get { return XPathResultType.String; }}
327 public override object Evaluate (BaseIterator iter)
329 double d = arg0.EvaluateNumber (iter);
330 string s = arg1.EvaluateString (iter);
331 QName nm = QName.Empty;
334 nm = XslNameUtil.FromString (arg2.EvaluateString (iter), nsm);
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);
345 class XsltFunctionAvailable : XPathFunction
348 XmlNamespaceManager nsm;
350 public XsltFunctionAvailable (FunctionArguments args, IStaticXsltContext ctx) : base (args)
352 if (args == null || args.Tail != null)
353 throw new XPathException ("element-available takes 1 arg");
359 public override XPathResultType ReturnType { get { return XPathResultType.Boolean; }}
361 public override object Evaluate (BaseIterator iter)
364 string name = arg0.EvaluateString (iter);
365 int colon = name.IndexOf (':');
366 // extension function
368 return (iter.NamespaceManager as XsltCompiledContext).ResolveFunction (
369 XslNameUtil.FromString (name, nsm),
379 name == "contains" ||
386 name == "local-name" ||
388 name == "namespace-uri" ||
389 name == "normalize-space" ||
392 name == "position" ||
394 name == "starts-with" ||
396 name == "string-length" ||
397 name == "substring" ||
398 name == "substring-after" ||
399 name == "substring-before" ||
401 name == "translate" ||
404 name == "document" ||
405 name == "format-number" ||
406 name == "function-available" ||
407 name == "generate-id" ||
410 name == "unparsed-entity-uri" ||
411 name == "element-available" ||
412 name == "system-property"
417 class XsltGenerateId : XPathFunction
420 public XsltGenerateId (FunctionArguments args) : base (args)
423 if (args.Tail != null)
424 throw new XPathException ("generate-id takes 1 or no args");
429 public override XPathResultType ReturnType { get { return XPathResultType.String; }}
430 public override object Evaluate (BaseIterator iter)
434 XPathNodeIterator itr = arg0.EvaluateNodeSet (iter);
436 n = itr.Current.Clone ();
438 return string.Empty; // empty nodeset == empty string
440 n = iter.Current.Clone ();
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);
449 sb.Append (IndexInParent (n));
451 } while (n.MoveToParent ());
453 return sb.ToString ();
456 int IndexInParent (XPathNavigator nav)
459 while (nav.MoveToPrevious ())
466 class XsltKey : XPathFunction
468 Expression arg0, arg1;
469 XmlNamespaceManager nsm;
472 public XsltKey (FunctionArguments args, IStaticXsltContext ctx) : base (args)
474 if (args == null || args.Tail == null)
475 throw new XPathException ("key takes 2 args");
477 arg1 = args.Tail.Arg;
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; }}
485 public override object Evaluate (BaseIterator iter)
487 QName name = XslNameUtil.FromString (arg0.EvaluateString (iter), nsm);
488 XsltCompiledContext ctx = iter.NamespaceManager as XsltCompiledContext;
490 key = ctx.Processor.CompiledStyle.Style.FindKey (name);
492 ArrayList result = new ArrayList ();
493 object o = arg1.Evaluate (iter);
494 XPathNodeIterator it = o as XPathNodeIterator;
497 while (it.MoveNext())
498 FindKeyMatch (ctx, it.Current.Value, result, iter.Current);
500 FindKeyMatch (ctx, XPathFunctions.ToString (o), result, iter.Current);
503 return new ListIterator (result, (iter.NamespaceManager as XsltCompiledContext), true);
506 void FindKeyMatch (XsltCompiledContext xsltContext, string value, ArrayList result, XPathNavigator context)
508 XPathNavigator searchDoc = context.Clone ();
509 searchDoc.MoveToRoot ();
511 XPathNodeIterator desc = searchDoc.SelectDescendants (XPathNodeType.All, true);
513 while (desc.MoveNext ()) {
514 if (key.Matches (desc.Current, xsltContext, value))
515 AddResult (result, desc.Current);
517 if (desc.Current.MoveToFirstAttribute ()) {
519 if (key.Matches (desc.Current, xsltContext, value))
520 AddResult (result, desc.Current);
521 } while (desc.Current.MoveToNextAttribute ());
523 desc.Current.MoveToParent ();
529 void AddResult (ArrayList result, XPathNavigator nav)
531 for (int i = 0; i < result.Count; i++) {
532 XmlNodeOrder docOrder = nav.ComparePosition (((XPathNavigator)result [i]));
533 if (docOrder == XmlNodeOrder.Same)
536 if (docOrder == XmlNodeOrder.Before) {
537 result.Insert(i, nav.Clone ());
541 result.Add (nav.Clone ());
545 class XsltSystemProperty : XPathFunction
548 XmlNamespaceManager nsm;
550 public XsltSystemProperty (FunctionArguments args, IStaticXsltContext ctx) : base (args)
552 if (args == null || args.Tail != null)
553 throw new XPathException ("system-property takes 1 arg");
559 public override XPathResultType ReturnType { get { return XPathResultType.String; }}
560 public override object Evaluate (BaseIterator iter)
562 QName name = XslNameUtil.FromString (arg0.EvaluateString (iter), nsm);
564 if (name.Namespace == Compiler.XsltNamespace) {
566 case "version": return "1.0";
567 case "vendor": return "Mono";
568 case "vendor-url": return "http://www.go-mono.com/";
576 class XsltUnparsedEntityUri : XPathFunction
580 public XsltUnparsedEntityUri (FunctionArguments args) : base (args)
582 if (args == null || args.Tail != null)
583 throw new XPathException ("unparsed-entity-uri takes 1 arg");
588 public override XPathResultType ReturnType { get { return XPathResultType.String; }}
589 public override object Evaluate (BaseIterator iter)
591 IHasXmlNode xn = iter.Current as IHasXmlNode;
594 XmlNode n = xn.GetNode ();
595 XmlDocumentType doctype = n.OwnerDocument.DocumentType;
598 XmlEntity ent = doctype.Entities.GetNamedItem (arg0.EvaluateString (iter)) as XmlEntity;