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 protected static Hashtable xsltFunctions = new Hashtable ();
33 static XsltCompiledContext ()
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 ());
46 XslTransformProcessor p;
50 public XslTransformProcessor Processor { get { return p; }}
52 public XsltCompiledContext (XslTransformProcessor p, VariableScope v, XPathNavigator doc)
59 public override string DefaultNamespace { get { return String.Empty; }}
62 public override string LookupNamespace (string prefix)
64 if (prefix == "" || prefix == null)
67 return this.doc.GetNamespace (prefix);
70 public override IXsltContextFunction ResolveFunction (string prefix, string name, XPathResultType[] argTypes)
72 IXsltContextFunction func = null;
73 if (prefix == String.Empty || prefix == null) {
74 return xsltFunctions [name] as IXsltContextFunction;
76 string ns = this.LookupNamespace (prefix);
78 if (ns == null || p.Arguments == null) return null;
80 object extension = p.Arguments.GetExtensionObject (ns);
82 if (extension == null)
85 MethodInfo method = FindBestMethod (extension.GetType (), name, argTypes);
88 return new XsltExtensionFunction (extension, method);
94 MethodInfo FindBestMethod (Type t, string name, XPathResultType [] argTypes)
98 MethodInfo [] mi = t.GetMethods (BF.Public | BF.Instance | BF.Static);
102 if (argTypes == null)
103 return mi [0]; // if we dont have info on the arg types, nothing we can do
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];
124 for (int i = 0; i < length; i ++) {
126 ParameterInfo [] pi = mi [i].GetParameters ();
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
133 XPathResultType actual = XPFuncImpl.GetXPathType (pi [par].ParameterType);
134 if (actual != required && actual != XPathResultType.Any) {
139 if (actual == XPathResultType.Any) {
140 // try to get a stronger gind
141 if (required != XPathResultType.NodeSet && !(pi [par].ParameterType == typeof (object)))
148 if (match) return mi [i]; // TODO look for exact match
154 public override System.Xml.Xsl.IXsltContextVariable ResolveVariable(string prefix, string name)
156 QName varName = new QName (name, LookupNamespace (prefix));
159 XslGeneralVariable var = v.Resolve (p, varName);
164 return p.CompiledStyle.ResolveVariable (varName);
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 {
173 XPathNavigator ret = doc.Clone ();
179 public XPathNavigator GetDocument (Uri uri)
181 if (uri.ToString () == string.Empty)
184 return p.GetDocument (uri);
190 namespace Mono.Xml.Xsl.Functions {
192 internal abstract class XPFuncImpl : IXsltContextFunction {
193 int minargs, maxargs;
194 XPathResultType returnType;
195 XPathResultType [] argTypes;
197 public XPFuncImpl () {}
198 public XPFuncImpl (int minArgs, int maxArgs, XPathResultType returnType, XPathResultType[] argTypes)
200 this.Init(minArgs, maxArgs, returnType, argTypes);
203 protected void Init (int minArgs, int maxArgs, XPathResultType returnType, XPathResultType[] argTypes)
205 this.minargs = minArgs;
206 this.maxargs = maxArgs;
207 this.returnType = returnType;
208 this.argTypes = argTypes;
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)
217 return Invoke ((XsltCompiledContext)xsltContext, args, docContext);
220 public abstract object Invoke (XsltCompiledContext xsltContext, object [] args, XPathNavigator docContext);
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;
232 if (typeof (XPathNodeIterator).IsAssignableFrom (type))
233 return XPathResultType.NodeSet;
235 return XPathResultType.Any;
236 case TypeCode.DateTime :
237 throw new Exception ();
239 return XPathResultType.Number;
244 class XsltExtensionFunction : XPFuncImpl {
245 private object extension;
246 private MethodInfo method;
247 private TypeCode [] typeCodes;
249 public XsltExtensionFunction (object extension, MethodInfo method)
251 this.extension = extension;
252 this.method = method;
254 ParameterInfo [] parameters = method.GetParameters ();
255 int minArgs = parameters.Length;
256 int maxArgs = parameters.Length;
258 this.typeCodes = new TypeCode [parameters.Length];
259 XPathResultType[] argTypes = new XPathResultType [parameters.Length];
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);
266 if (parameters[i].IsOptional)
272 base.Init (minArgs, maxArgs, GetXPathType (method.ReturnType), argTypes);
275 public override object Invoke (XsltCompiledContext xsltContext, object [] args, XPathNavigator docContext)
278 object result = method.Invoke(extension, args);
279 IXPathNavigable navigable = result as IXPathNavigable;
280 if (navigable != null)
281 return navigable.CreateNavigator ();
285 Debug.WriteLine ("****** INCORRECT RESOLUTION **********");
291 class XsltCurrent : XPFuncImpl {
292 public XsltCurrent () : base (0, 0, XPathResultType.NodeSet, null) {}
294 public override object Invoke (XsltCompiledContext xsltContext, object [] args, XPathNavigator docContext)
296 return new SelfIterator (xsltContext.Processor.CurrentNode, null);
300 class XsltDocument : XPFuncImpl {
301 public XsltDocument () : base (1, 2, XPathResultType.NodeSet, new XPathResultType [] { XPathResultType.Any, XPathResultType.NodeSet }) {}
303 public override object Invoke (XsltCompiledContext xsltContext, object [] args, XPathNavigator docContext)
305 string baseUri = null;
306 if (args.Length == 2) {
307 XPathNodeIterator it = (XPathNodeIterator) args [1];
309 baseUri = it.Current.BaseURI;
311 baseUri = VoidBaseUriFlag;
314 if (args [0] is XPathNodeIterator)
315 return GetDocument (xsltContext, (XPathNodeIterator)args [0], baseUri);
317 return GetDocument (xsltContext, args [0].ToString (), baseUri);
320 static string VoidBaseUriFlag = "&^)(*&%*^$&$VOID!BASE!URI!";
322 Uri Resolve (string thisUri, string baseUri, XslTransformProcessor p)
324 Debug.WriteLine ("THIS: " + thisUri);
325 Debug.WriteLine ("BASE: " + baseUri);
326 XmlResolver r = p.Resolver;
329 if (! object.ReferenceEquals (baseUri, VoidBaseUriFlag))
330 uriBase = r.ResolveUri (null, baseUri);
332 return r.ResolveUri (uriBase, thisUri);
335 XPathNodeIterator GetDocument (XsltCompiledContext xsltContext, XPathNodeIterator itr, string baseUri)
337 ArrayList list = new ArrayList ();
338 Hashtable got = new Hashtable ();
340 while (itr.MoveNext()) {
341 Uri uri = Resolve (itr.Current.Value, baseUri != null ? baseUri : itr.Current.BaseURI, xsltContext.Processor);
342 if (!got.ContainsKey (uri)) {
344 list.Add (xsltContext.GetDocument (uri));
348 return new EnumeratorIterator (list.GetEnumerator (), xsltContext);
351 XPathNodeIterator GetDocument (XsltCompiledContext xsltContext, string arg0, string baseUri)
353 return new SelfIterator (
354 xsltContext.GetDocument (
355 Resolve (arg0, baseUri != null ? baseUri : xsltContext.BaseUri, xsltContext.Processor)
361 class XsltElementAvailable : XPFuncImpl {
362 public XsltElementAvailable () : base (1, 1, XPathResultType.Boolean, new XPathResultType [] { XPathResultType.String }) {}
364 public override object Invoke (XsltCompiledContext xsltContext, object [] args, XPathNavigator docContext)
366 QName name = XslNameUtil.FromString ((string)args [0], xsltContext);
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"
396 class XsltFormatNumber : XPFuncImpl {
397 public XsltFormatNumber () : base (2, 3, XPathResultType.String , new XPathResultType [] { XPathResultType.Number, XPathResultType.String, XPathResultType.String }) {}
399 public override object Invoke (XsltCompiledContext xsltContext, object [] args, XPathNavigator docContext)
401 double d = (double)args [0];
402 string s = (string)args [1];
403 QName nm = QName.Empty;
405 if (args.Length == 3)
406 nm = XslNameUtil.FromString ((string)args [0], xsltContext);
408 return xsltContext.Processor.CompiledStyle.LookupDecimalFormat (nm).FormatNumber (d, s);
412 class XsltFunctionAvailable : XPFuncImpl {
413 public XsltFunctionAvailable () : base (1, 1, XPathResultType.Boolean, new XPathResultType [] { XPathResultType.String }) {}
415 public override object Invoke (XsltCompiledContext xsltContext, object [] args, XPathNavigator docContext)
418 string name = (string)args [0];
419 int colon = name.IndexOf (':');
420 // extension function
422 return xsltContext.ResolveFunction (
423 name.Substring (0, colon),
424 name.Substring (colon, name.Length - colon),
434 name == "contains" ||
441 name == "local-name" ||
443 name == "namespace-uri" ||
444 name == "normalize-space" ||
447 name == "position" ||
449 name == "starts-with" ||
451 name == "string-length" ||
452 name == "substring" ||
453 name == "substring-after" ||
454 name == "substring-before" ||
456 name == "translate" ||
458 xsltContext.ResolveFunction ("", name, null) != null // rest of xslt functions
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)
468 if (args.Length == 1) {
469 XPathNodeIterator itr = (XPathNodeIterator) args [0];
471 n = itr.Current.Clone ();
473 return string.Empty; // empty nodeset == empty string
475 n = docContext.Clone ();
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
482 sb.Append (IndexInParent (n));
484 } while (n.MoveToParent ());
486 return sb.ToString ();
489 int IndexInParent (XPathNavigator nav)
494 while (nav.MoveToPrevious ())
502 class XsltKey : XPFuncImpl {
503 public XsltKey () : base (2, 2, XPathResultType.NodeSet, new XPathResultType [] { XPathResultType.String, XPathResultType.Any }) {}
505 public override object Invoke (XsltCompiledContext xsltContext, object [] args, XPathNavigator docContext)
507 ArrayList result = new ArrayList ();
508 QName name = XslNameUtil.FromString ((string)args [0], xsltContext);
509 XPathNodeIterator it = args [1] as XPathNodeIterator;
512 while (it.MoveNext())
513 FindKeyMatch (xsltContext, name, it.Current.Value, result, docContext);
515 FindKeyMatch (xsltContext, name, XPathFunctions.ToString (args [1]), result, docContext);
518 return new EnumeratorIterator (result.GetEnumerator (), xsltContext);
521 void FindKeyMatch (XsltCompiledContext xsltContext, QName name, string value, ArrayList result, XPathNavigator context)
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);
529 while (desc.MoveNext ()) {
530 if (key.Matches (desc.Current, value))
531 AddResult (result, desc.Current);
533 if (desc.Current.MoveToFirstAttribute ()) {
535 if (key.Matches (desc.Current, value))
536 AddResult (result, desc.Current);
537 } while (desc.Current.MoveToNext ());
539 desc.Current.MoveToParent ();
546 void AddResult (ArrayList result, XPathNavigator nav)
548 for (int i = 0; i < result.Count; i++) {
549 XmlNodeOrder docOrder = nav.ComparePosition (((XPathNavigator)result [i]));
550 if (docOrder == XmlNodeOrder.Same)
553 if (docOrder == XmlNodeOrder.Before) {
554 result.Insert(i, nav.Clone ());
558 result.Add (nav.Clone ());
562 class XsltSystemProperty : XPFuncImpl {
563 public XsltSystemProperty () : base (1, 1, XPathResultType.String , new XPathResultType [] { XPathResultType.String }) {}
565 public override object Invoke (XsltCompiledContext xsltContext, object [] args, XPathNavigator docContext)
567 QName name = XslNameUtil.FromString ((string)args [0], xsltContext);
569 if (name.Namespace == Compiler.XsltNamespace) {
571 case "version": return "1.0";
572 case "vendor": return "Mono";
573 case "vendor-url": return "http://www.go-mono.com/";
581 class XsltUnparsedEntityUri : XPFuncImpl {
582 public XsltUnparsedEntityUri () : base (1, 1, XPathResultType.String , new XPathResultType [] { XPathResultType.String }) {}
584 public override object Invoke (XsltCompiledContext xsltContext, object [] args, XPathNavigator docContext)
586 throw new NotImplementedException ();