2 // XsltCompiledContext.cs
5 // Ben Maurer (bmaurer@users.sourceforge.net)
12 using System.Collections;
13 using System.Collections.Specialized;
15 using System.Xml.Schema;
16 using System.Xml.XPath;
20 using Mono.Xml.Xsl.Functions;
21 using Mono.Xml.Xsl.Operations;
22 using System.Reflection;
23 using BF = System.Reflection.BindingFlags;
25 using QName = System.Xml.XmlQualifiedName;
28 namespace Mono.Xml.Xsl {
30 internal class XsltCompiledContext : XsltContext {
31 XslTransformProcessor p;
33 public XslTransformProcessor Processor { get { return p; }}
35 public XsltCompiledContext (XslTransformProcessor p)
40 public override string DefaultNamespace { get { return String.Empty; }}
43 public override string LookupNamespace (string prefix)
45 throw new Exception ("we should never get here");
48 internal override IXsltContextFunction ResolveFunction (XmlQualifiedName name, XPathResultType [] argTypes)
50 IXsltContextFunction func = null;
52 string ns = name.Namespace;
54 if (ns == null || p.Arguments == null) return null;
56 object extension = p.Arguments.GetExtensionObject (ns);
58 if (extension == null)
61 MethodInfo method = FindBestMethod (extension.GetType (), name.Name, argTypes);
64 return new XsltExtensionFunction (extension, method);
68 MethodInfo FindBestMethod (Type t, string name, XPathResultType [] argTypes)
72 MethodInfo [] mi = t.GetMethods (BF.Public | BF.Instance | BF.Static);
77 return mi [0]; // if we dont have info on the arg types, nothing we can do
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)
98 for (int i = 0; i < length; i ++) {
100 ParameterInfo [] pi = mi [i].GetParameters ();
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
107 XPathResultType actual = XPFuncImpl.GetXPathType (pi [par].ParameterType);
108 if (actual != required && actual != XPathResultType.Any) {
113 if (actual == XPathResultType.Any) {
114 // try to get a stronger gind
115 if (required != XPathResultType.NodeSet && !(pi [par].ParameterType == typeof (object)))
122 if (match) return mi [i]; // TODO look for exact match
127 public override IXsltContextVariable ResolveVariable (string prefix, string name)
129 throw new Exception ("shouldn't get here");
132 public override IXsltContextFunction ResolveFunction (string prefix, string name, XPathResultType [] ArgTypes)
134 throw new Exception ("shouldn't get here");
137 internal override System.Xml.Xsl.IXsltContextVariable ResolveVariable(QName q)
139 return p.CompiledStyle.ResolveVariable (q);
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 (); }}
149 namespace Mono.Xml.Xsl.Functions {
151 internal abstract class XPFuncImpl : IXsltContextFunction {
152 int minargs, maxargs;
153 XPathResultType returnType;
154 XPathResultType [] argTypes;
156 public XPFuncImpl () {}
157 public XPFuncImpl (int minArgs, int maxArgs, XPathResultType returnType, XPathResultType[] argTypes)
159 this.Init(minArgs, maxArgs, returnType, argTypes);
162 protected void Init (int minArgs, int maxArgs, XPathResultType returnType, XPathResultType[] argTypes)
164 this.minargs = minArgs;
165 this.maxargs = maxArgs;
166 this.returnType = returnType;
167 this.argTypes = argTypes;
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)
176 return Invoke ((XsltCompiledContext)xsltContext, args, docContext);
179 public abstract object Invoke (XsltCompiledContext xsltContext, object [] args, XPathNavigator docContext);
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;
191 if (typeof (XPathNodeIterator).IsAssignableFrom (type))
192 return XPathResultType.NodeSet;
194 return XPathResultType.Any;
195 case TypeCode.DateTime :
196 throw new Exception ();
198 return XPathResultType.Number;
203 class XsltExtensionFunction : XPFuncImpl {
204 private object extension;
205 private MethodInfo method;
206 private TypeCode [] typeCodes;
208 public XsltExtensionFunction (object extension, MethodInfo method)
210 this.extension = extension;
211 this.method = method;
213 ParameterInfo [] parameters = method.GetParameters ();
214 int minArgs = parameters.Length;
215 int maxArgs = parameters.Length;
217 this.typeCodes = new TypeCode [parameters.Length];
218 XPathResultType[] argTypes = new XPathResultType [parameters.Length];
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);
225 if (parameters[i].IsOptional)
231 base.Init (minArgs, maxArgs, GetXPathType (method.ReturnType), argTypes);
234 public override object Invoke (XsltCompiledContext xsltContext, object [] args, XPathNavigator docContext)
237 object result = method.Invoke(extension, args);
238 IXPathNavigable navigable = result as IXPathNavigable;
239 if (navigable != null)
240 return navigable.CreateNavigator ();
244 Debug.WriteLine ("****** INCORRECT RESOLUTION **********");
250 class XsltCurrent : XPathFunction {
251 public XsltCurrent (FunctionArguments args) : base (args)
254 throw new XPathException ("current takes 0 args");
257 public override XPathResultType ReturnType { get { return XPathResultType.NodeSet; }}
259 public override object Evaluate (BaseIterator iter)
261 return new SelfIterator ((iter.NamespaceManager as XsltCompiledContext).Processor.CurrentNode, null);
265 class XsltDocument : XPathFunction {
266 Expression arg0, arg1;
269 public XsltDocument (FunctionArguments args, Compiler c) : base (args)
271 if (args == null || (args.Tail != null && args.Tail.Tail != null))
272 throw new XPathException ("document takes one or two args");
275 if (args.Tail != null)
276 arg1 = args.Tail.Arg;
277 doc = c.Input.Clone ();
279 public override XPathResultType ReturnType { get { return XPathResultType.NodeSet; }}
281 public override object Evaluate (BaseIterator iter)
283 string baseUri = null;
285 XPathNodeIterator it = arg1.EvaluateNodeSet (iter);
287 baseUri = it.Current.BaseURI;
289 baseUri = VoidBaseUriFlag;
292 object o = arg0.Evaluate (iter);
293 if (o is XPathNodeIterator)
294 return GetDocument ((iter.NamespaceManager as XsltCompiledContext), (XPathNodeIterator)o, baseUri);
296 return GetDocument ((iter.NamespaceManager as XsltCompiledContext), o.ToString (), baseUri);
299 static string VoidBaseUriFlag = "&^)(*&%*^$&$VOID!BASE!URI!";
301 Uri Resolve (string thisUri, string baseUri, XslTransformProcessor p)
303 Debug.WriteLine ("THIS: " + thisUri);
304 Debug.WriteLine ("BASE: " + baseUri);
305 XmlResolver r = p.Resolver;
308 if (! object.ReferenceEquals (baseUri, VoidBaseUriFlag))
309 uriBase = r.ResolveUri (null, baseUri);
311 return r.ResolveUri (uriBase, thisUri);
314 XPathNodeIterator GetDocument (XsltCompiledContext xsltContext, XPathNodeIterator itr, string baseUri)
316 ArrayList list = new ArrayList ();
317 Hashtable got = new Hashtable ();
319 while (itr.MoveNext()) {
320 Uri uri = Resolve (itr.Current.Value, baseUri != null ? baseUri : itr.Current.BaseURI, xsltContext.Processor);
321 if (!got.ContainsKey (uri)) {
323 if (uri.ToString () == "") {
324 XPathNavigator n = doc.Clone ();
328 list.Add (xsltContext.Processor.GetDocument (uri));
332 return new EnumeratorIterator (list.GetEnumerator (), xsltContext);
335 XPathNodeIterator GetDocument (XsltCompiledContext xsltContext, string arg0, string baseUri)
337 Uri uri = Resolve (arg0, baseUri != null ? baseUri : doc.BaseURI, xsltContext.Processor);
339 if (uri.ToString () == "") {
343 n = xsltContext.Processor.GetDocument (uri);
345 return new SelfIterator (n, null);
349 class XsltElementAvailable : XPathFunction {
351 XmlNamespaceManager nsm;
353 public XsltElementAvailable (FunctionArguments args, IStaticXsltContext ctx) : base (args)
355 if (args == null || args.Tail != null)
356 throw new XPathException ("element-available takes 1 arg");
362 public override XPathResultType ReturnType { get { return XPathResultType.Boolean; }}
364 public override object Evaluate (BaseIterator iter)
366 QName name = XslNameUtil.FromString (arg0.EvaluateString (iter), nsm);
369 (name.Namespace == Compiler.XsltNamespace) &&
372 // A list of all the instructions (does not include top-level-elements)
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"
395 class XsltFormatNumber : XPathFunction {
396 Expression arg0, arg1, arg2;
397 XmlNamespaceManager nsm;
399 public XsltFormatNumber (FunctionArguments args, IStaticXsltContext ctx) : base (args)
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");
405 arg1 = args.Tail.Arg;
406 if (args.Tail.Tail != null) {
407 arg2= args.Tail.Tail.Arg;
411 public override XPathResultType ReturnType { get { return XPathResultType.String; }}
413 public override object Evaluate (BaseIterator iter)
415 double d = arg0.EvaluateNumber (iter);
416 string s = arg1.EvaluateString (iter);
417 QName nm = QName.Empty;
420 nm = XslNameUtil.FromString (arg2.EvaluateString (iter), nsm);
422 return (iter.NamespaceManager as XsltCompiledContext).Processor.CompiledStyle
423 .LookupDecimalFormat (nm).FormatNumber (d, s);
427 class XsltFunctionAvailable : XPathFunction {
429 XmlNamespaceManager nsm;
431 public XsltFunctionAvailable (FunctionArguments args, IStaticXsltContext ctx) : base (args)
433 if (args == null || args.Tail != null)
434 throw new XPathException ("element-available takes 1 arg");
440 public override XPathResultType ReturnType { get { return XPathResultType.Boolean; }}
442 public override object Evaluate (BaseIterator iter)
445 string name = arg0.EvaluateString (iter);
446 int colon = name.IndexOf (':');
447 // extension function
449 return (iter.NamespaceManager as XsltCompiledContext).ResolveFunction (
450 XslNameUtil.FromString (name, nsm),
460 name == "contains" ||
467 name == "local-name" ||
469 name == "namespace-uri" ||
470 name == "normalize-space" ||
473 name == "position" ||
475 name == "starts-with" ||
477 name == "string-length" ||
478 name == "substring" ||
479 name == "substring-after" ||
480 name == "substring-before" ||
482 name == "translate" ||
485 name == "document" ||
486 name == "format-number" ||
487 name == "function-available" ||
488 name == "generate-id" ||
491 name == "unparsed-entity-uri" ||
492 name == "element-available" ||
493 name == "system-property"
498 class XsltGenerateId : XPathFunction {
500 public XsltGenerateId (FunctionArguments args) : base (args)
503 if (args.Tail != null)
504 throw new XPathException ("generate-id takes 1 or no args");
509 public override XPathResultType ReturnType { get { return XPathResultType.NodeSet; }}
510 public override object Evaluate (BaseIterator iter)
514 XPathNodeIterator itr = arg0.EvaluateNodeSet (iter);
516 n = itr.Current.Clone ();
518 return string.Empty; // empty nodeset == empty string
520 n = iter.Current.Clone ();
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
527 sb.Append (IndexInParent (n));
529 } while (n.MoveToParent ());
531 return sb.ToString ();
534 int IndexInParent (XPathNavigator nav)
539 while (nav.MoveToPrevious ())
547 class XsltKey : XPathFunction {
548 Expression arg0, arg1;
549 XmlNamespaceManager nsm;
551 public XsltKey (FunctionArguments args, IStaticXsltContext ctx) : base (args)
553 if (args == null || args.Tail == null)
554 throw new XPathException ("key takes 2 args");
557 arg1 = args.Tail.Arg;
560 public override XPathResultType ReturnType { get { return XPathResultType.NodeSet; }}
562 public override object Evaluate (BaseIterator iter)
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;
570 while (it.MoveNext())
571 FindKeyMatch ((iter.NamespaceManager as XsltCompiledContext), name, it.Current.Value, result, iter.Current);
573 FindKeyMatch ((iter.NamespaceManager as XsltCompiledContext), name, XPathFunctions.ToString (o), result, iter.Current);
576 return new EnumeratorIterator (result.GetEnumerator (), (iter.NamespaceManager as XsltCompiledContext));
579 void FindKeyMatch (XsltCompiledContext xsltContext, QName name, string value, ArrayList result, XPathNavigator context)
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);
587 while (desc.MoveNext ()) {
588 if (key.Matches (desc.Current, value))
589 AddResult (result, desc.Current);
591 if (desc.Current.MoveToFirstAttribute ()) {
593 if (key.Matches (desc.Current, value))
594 AddResult (result, desc.Current);
595 } while (desc.Current.MoveToNext ());
597 desc.Current.MoveToParent ();
604 void AddResult (ArrayList result, XPathNavigator nav)
606 for (int i = 0; i < result.Count; i++) {
607 XmlNodeOrder docOrder = nav.ComparePosition (((XPathNavigator)result [i]));
608 if (docOrder == XmlNodeOrder.Same)
611 if (docOrder == XmlNodeOrder.Before) {
612 result.Insert(i, nav.Clone ());
616 result.Add (nav.Clone ());
620 class XsltSystemProperty : XPathFunction {
622 XmlNamespaceManager nsm;
624 public XsltSystemProperty (FunctionArguments args, IStaticXsltContext ctx) : base (args)
626 if (args == null || args.Tail != null)
627 throw new XPathException ("system-property takes 1 arg");
633 public override XPathResultType ReturnType { get { return XPathResultType.String; }}
634 public override object Evaluate (BaseIterator iter)
636 QName name = XslNameUtil.FromString (arg0.EvaluateString (iter), nsm);
638 if (name.Namespace == Compiler.XsltNamespace) {
640 case "version": return "1.0";
641 case "vendor": return "Mono";
642 case "vendor-url": return "http://www.go-mono.com/";
650 class XsltUnparsedEntityUri : XPathFunction {
653 public XsltUnparsedEntityUri (FunctionArguments args) : base (args)
655 if (args == null || args.Tail != null)
656 throw new XPathException ("unparsed-entity-uri takes 1 arg");
661 public override XPathResultType ReturnType { get { return XPathResultType.String; }}
662 public override object Evaluate (BaseIterator iter)
664 throw new NotImplementedException ();