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) : base (new NameTable ())
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) return null;
56 object extension = null;
58 if (p.Arguments != null)
59 extension = p.Arguments.GetExtensionObject (ns);
61 bool isScript = false;
62 if (extension == null) {
63 extension = p.ScriptManager.GetExtensionObject (ns);
64 if (extension == null)
71 MethodInfo method = FindBestMethod (extension.GetType (), name.Name, argTypes, isScript);
74 return new XsltExtensionFunction (extension, method);
78 MethodInfo FindBestMethod (Type t, string name, XPathResultType [] argTypes, bool isScript)
82 MethodInfo [] mi = t.GetMethods ((isScript ? BF.Public | BF.NonPublic : BF.Public) | BF.Instance | BF.Static);
87 return mi [0]; // if we dont have info on the arg types, nothing we can do
91 // filter on name + num args
92 int numArgs = argTypes.Length;
93 for (int i = 0; i < mi.Length; i ++) {
94 if (mi [i].Name == name && mi [i].GetParameters ().Length == numArgs)
108 for (int i = 0; i < length; i ++) {
110 ParameterInfo [] pi = mi [i].GetParameters ();
112 for (int par = 0; par < pi.Length; par++) {
113 XPathResultType required = argTypes [par];
114 if (required == XPathResultType.Any)
115 continue; // dunno what it is
117 XPathResultType actual = XPFuncImpl.GetXPathType (pi [par].ParameterType);
118 if (actual != required && actual != XPathResultType.Any) {
123 if (actual == XPathResultType.Any) {
124 // try to get a stronger gind
125 if (required != XPathResultType.NodeSet && !(pi [par].ParameterType == typeof (object)))
132 if (match) return mi [i]; // TODO look for exact match
137 public override IXsltContextVariable ResolveVariable (string prefix, string name)
139 throw new Exception ("shouldn't get here");
142 public override IXsltContextFunction ResolveFunction (string prefix, string name, XPathResultType [] ArgTypes)
144 throw new Exception ("shouldn't get here");
147 internal override System.Xml.Xsl.IXsltContextVariable ResolveVariable(QName q)
149 return p.CompiledStyle.ResolveVariable (q);
152 public override int CompareDocument (string baseUri, string nextBaseUri) { throw new NotImplementedException (); }
154 public override bool PreserveWhitespace (XPathNavigator nav)
157 XPathNavigator tmp = nav.Clone ();
158 switch (tmp.NodeType) {
159 case XPathNodeType.Root:
161 case XPathNodeType.Element:
168 for (; tmp.NodeType == XPathNodeType.Element; tmp.MoveToParent ()) {
169 object o = p.CompiledStyle.Style.SpaceControls [new XmlQualifiedName (tmp.LocalName, tmp.NamespaceURI)];
171 object o = p.CompiledStyle.Style.SpaceControls [new XmlQualifiedName (nav.LocalName, nav.NamespaceURI)];
175 XmlSpace space = (XmlSpace) o;
176 switch ((XmlSpace) o) {
177 case XmlSpace.Preserve:
179 case XmlSpace.Default:
183 return true; // temporary
190 public override bool Whitespace { get { return WhitespaceHandling; } }
192 // Below are mimicking XmlNamespaceManager ;-)
193 public bool IsCData {
194 get { return scopes [scopeAt].IsCData; }
195 set { scopes [scopeAt].IsCData = value; }
197 public bool WhitespaceHandling {
198 get { return scopes [scopeAt].PreserveWhitespace; }
199 set { scopes [scopeAt].PreserveWhitespace = value; }
202 struct XsltContextInfo
205 public bool PreserveWhitespace;
208 // TODO: set 40 or so. It is set to 2 only for test.
209 XsltContextInfo [] scopes = new XsltContextInfo [2];
212 // precondition scopeAt == scopes.Length
215 XsltContextInfo [] old = scopes;
216 scopes = new XsltContextInfo [scopeAt * 2 + 1];
218 Array.Copy (old, 0, scopes, 0, scopeAt);
221 public override bool PopScope ()
231 public override void PushScope ()
236 if (scopeAt == scopes.Length)
243 namespace Mono.Xml.Xsl.Functions {
245 internal abstract class XPFuncImpl : IXsltContextFunction {
246 int minargs, maxargs;
247 XPathResultType returnType;
248 XPathResultType [] argTypes;
250 public XPFuncImpl () {}
251 public XPFuncImpl (int minArgs, int maxArgs, XPathResultType returnType, XPathResultType[] argTypes)
253 this.Init(minArgs, maxArgs, returnType, argTypes);
256 protected void Init (int minArgs, int maxArgs, XPathResultType returnType, XPathResultType[] argTypes)
258 this.minargs = minArgs;
259 this.maxargs = maxArgs;
260 this.returnType = returnType;
261 this.argTypes = argTypes;
264 public int Minargs { get { return this.minargs; }}
265 public int Maxargs { get { return this.maxargs; }}
266 public XPathResultType ReturnType { get { return this.returnType; }}
267 public XPathResultType [] ArgTypes { get { return this.argTypes; }}
268 public object Invoke (XsltContext xsltContext, object [] args, XPathNavigator docContext)
270 return Invoke ((XsltCompiledContext)xsltContext, args, docContext);
273 public abstract object Invoke (XsltCompiledContext xsltContext, object [] args, XPathNavigator docContext);
275 public static XPathResultType GetXPathType (Type type) {
276 switch (Type.GetTypeCode(type)) {
277 case TypeCode.String:
278 return XPathResultType.String;
279 case TypeCode.Boolean:
280 return XPathResultType.Boolean;
281 case TypeCode.Object:
282 if (typeof (XPathNavigator).IsAssignableFrom (type) || typeof (IXPathNavigable).IsAssignableFrom (type))
283 return XPathResultType.Navigator;
285 if (typeof (XPathNodeIterator).IsAssignableFrom (type))
286 return XPathResultType.NodeSet;
288 return XPathResultType.Any;
289 case TypeCode.DateTime :
290 throw new Exception ();
292 return XPathResultType.Number;
297 class XsltExtensionFunction : XPFuncImpl {
298 private object extension;
299 private MethodInfo method;
300 private TypeCode [] typeCodes;
302 public XsltExtensionFunction (object extension, MethodInfo method)
304 this.extension = extension;
305 this.method = method;
307 ParameterInfo [] parameters = method.GetParameters ();
308 int minArgs = parameters.Length;
309 int maxArgs = parameters.Length;
311 this.typeCodes = new TypeCode [parameters.Length];
312 XPathResultType[] argTypes = new XPathResultType [parameters.Length];
314 bool canBeOpt = true;
315 for (int i = parameters.Length - 1; 0 <= i; i--) { // optionals at the end
316 typeCodes [i] = Type.GetTypeCode (parameters [i].ParameterType);
317 argTypes [i] = GetXPathType (parameters [i].ParameterType);
319 if (parameters[i].IsOptional)
325 base.Init (minArgs, maxArgs, GetXPathType (method.ReturnType), argTypes);
328 public override object Invoke (XsltCompiledContext xsltContext, object [] args, XPathNavigator docContext)
331 ParameterInfo [] pis = method.GetParameters ();
332 object [] castedArgs = new object [pis.Length];
333 for (int i = 0; i < args.Length; i++) {
334 Type t = pis [i].ParameterType;
335 switch (t.FullName) {
337 case "System.UInt16":
339 case "System.UInt32":
341 case "System.UInt64":
342 case "System.Single":
343 case "System.Decimal":
344 castedArgs [i] = Convert.ChangeType (args [i], t);
347 castedArgs [i] = args [i];
352 object result = null;
353 switch (method.ReturnType.FullName) {
355 case "System.UInt16":
357 case "System.UInt32":
359 case "System.UInt64":
360 case "System.Single":
361 case "System.Decimal":
362 result = (double) method.Invoke (extension, castedArgs);
365 result = method.Invoke(extension, castedArgs);
368 IXPathNavigable navigable = result as IXPathNavigable;
369 if (navigable != null)
370 return navigable.CreateNavigator ();
373 } catch (Exception ex) {
374 // throw new XsltException ("Custom function reported an error.", ex);
375 Debug.WriteLine ("****** INCORRECT RESOLUTION **********");
381 class XsltCurrent : XPathFunction {
382 public XsltCurrent (FunctionArguments args) : base (args)
385 throw new XPathException ("current takes 0 args");
388 public override XPathResultType ReturnType { get { return XPathResultType.NodeSet; }}
390 public override object Evaluate (BaseIterator iter)
392 return new SelfIterator ((iter.NamespaceManager as XsltCompiledContext).Processor.CurrentNode, null);
396 class XsltDocument : XPathFunction {
397 Expression arg0, arg1;
400 public XsltDocument (FunctionArguments args, Compiler c) : base (args)
402 if (args == null || (args.Tail != null && args.Tail.Tail != null))
403 throw new XPathException ("document takes one or two args");
406 if (args.Tail != null)
407 arg1 = args.Tail.Arg;
408 doc = c.Input.Clone ();
410 public override XPathResultType ReturnType { get { return XPathResultType.NodeSet; }}
412 public override object Evaluate (BaseIterator iter)
414 string baseUri = null;
416 XPathNodeIterator it = arg1.EvaluateNodeSet (iter);
418 baseUri = it.Current.BaseURI;
420 baseUri = VoidBaseUriFlag;
423 object o = arg0.Evaluate (iter);
424 if (o is XPathNodeIterator)
425 return GetDocument ((iter.NamespaceManager as XsltCompiledContext), (XPathNodeIterator)o, baseUri);
427 return GetDocument ((iter.NamespaceManager as XsltCompiledContext), o.ToString (), baseUri);
430 static string VoidBaseUriFlag = "&^)(*&%*^$&$VOID!BASE!URI!";
432 Uri Resolve (string thisUri, string baseUri, XslTransformProcessor p)
434 Debug.WriteLine ("THIS: " + thisUri);
435 Debug.WriteLine ("BASE: " + baseUri);
436 XmlResolver r = p.Resolver;
439 if (! object.ReferenceEquals (baseUri, VoidBaseUriFlag))
440 uriBase = r.ResolveUri (null, baseUri);
442 return r.ResolveUri (uriBase, thisUri);
445 XPathNodeIterator GetDocument (XsltCompiledContext xsltContext, XPathNodeIterator itr, string baseUri)
447 ArrayList list = new ArrayList ();
448 Hashtable got = new Hashtable ();
450 while (itr.MoveNext()) {
451 Uri uri = Resolve (itr.Current.Value, baseUri != null ? baseUri : /*itr.Current.BaseURI*/doc.BaseURI, xsltContext.Processor);
452 if (!got.ContainsKey (uri)) {
454 if (uri.ToString () == "") {
455 XPathNavigator n = doc.Clone ();
459 list.Add (xsltContext.Processor.GetDocument (uri));
463 return new EnumeratorIterator (list.GetEnumerator (), xsltContext);
466 XPathNodeIterator GetDocument (XsltCompiledContext xsltContext, string arg0, string baseUri)
468 Uri uri = Resolve (arg0, baseUri != null ? baseUri : doc.BaseURI, xsltContext.Processor);
470 if (uri.ToString () == "") {
474 n = xsltContext.Processor.GetDocument (uri);
476 return new SelfIterator (n, xsltContext);
480 class XsltElementAvailable : XPathFunction {
482 XmlNamespaceManager nsm;
484 public XsltElementAvailable (FunctionArguments args, IStaticXsltContext ctx) : base (args)
486 if (args == null || args.Tail != null)
487 throw new XPathException ("element-available takes 1 arg");
493 public override XPathResultType ReturnType { get { return XPathResultType.Boolean; }}
495 public override object Evaluate (BaseIterator iter)
497 QName name = XslNameUtil.FromString (arg0.EvaluateString (iter), nsm);
500 (name.Namespace == Compiler.XsltNamespace) &&
503 // A list of all the instructions (does not include top-level-elements)
505 name.Name == "apply-imports" ||
506 name.Name == "apply-templates" ||
507 name.Name == "call-template" ||
508 name.Name == "choose" ||
509 name.Name == "comment" ||
510 name.Name == "copy" ||
511 name.Name == "copy-of" ||
512 name.Name == "element" ||
513 name.Name == "fallback" ||
514 name.Name == "for-each" ||
515 name.Name == "message" ||
516 name.Name == "number" ||
517 name.Name == "processing-instruction" ||
518 name.Name == "text" ||
519 name.Name == "value-of" ||
520 name.Name == "variable"
526 class XsltFormatNumber : XPathFunction {
527 Expression arg0, arg1, arg2;
528 XmlNamespaceManager nsm;
530 public XsltFormatNumber (FunctionArguments args, IStaticXsltContext ctx) : base (args)
532 if (args == null || args.Tail == null || (args.Tail.Tail != null && args.Tail.Tail.Tail != null))
533 throw new XPathException ("format-number takes 2 or 3 args");
536 arg1 = args.Tail.Arg;
537 if (args.Tail.Tail != null) {
538 arg2= args.Tail.Tail.Arg;
542 public override XPathResultType ReturnType { get { return XPathResultType.String; }}
544 public override object Evaluate (BaseIterator iter)
546 double d = arg0.EvaluateNumber (iter);
547 string s = arg1.EvaluateString (iter);
548 QName nm = QName.Empty;
551 nm = XslNameUtil.FromString (arg2.EvaluateString (iter), nsm);
553 return (iter.NamespaceManager as XsltCompiledContext).Processor.CompiledStyle
554 .LookupDecimalFormat (nm).FormatNumber (d, s);
558 class XsltFunctionAvailable : XPathFunction {
560 XmlNamespaceManager nsm;
562 public XsltFunctionAvailable (FunctionArguments args, IStaticXsltContext ctx) : base (args)
564 if (args == null || args.Tail != null)
565 throw new XPathException ("element-available takes 1 arg");
571 public override XPathResultType ReturnType { get { return XPathResultType.Boolean; }}
573 public override object Evaluate (BaseIterator iter)
576 string name = arg0.EvaluateString (iter);
577 int colon = name.IndexOf (':');
578 // extension function
580 return (iter.NamespaceManager as XsltCompiledContext).ResolveFunction (
581 XslNameUtil.FromString (name, nsm),
591 name == "contains" ||
598 name == "local-name" ||
600 name == "namespace-uri" ||
601 name == "normalize-space" ||
604 name == "position" ||
606 name == "starts-with" ||
608 name == "string-length" ||
609 name == "substring" ||
610 name == "substring-after" ||
611 name == "substring-before" ||
613 name == "translate" ||
616 name == "document" ||
617 name == "format-number" ||
618 name == "function-available" ||
619 name == "generate-id" ||
622 name == "unparsed-entity-uri" ||
623 name == "element-available" ||
624 name == "system-property"
629 class XsltGenerateId : XPathFunction {
631 public XsltGenerateId (FunctionArguments args) : base (args)
634 if (args.Tail != null)
635 throw new XPathException ("generate-id takes 1 or no args");
640 public override XPathResultType ReturnType { get { return XPathResultType.String; }}
641 public override object Evaluate (BaseIterator iter)
645 XPathNodeIterator itr = arg0.EvaluateNodeSet (iter);
647 n = itr.Current.Clone ();
649 return string.Empty; // empty nodeset == empty string
651 n = iter.Current.Clone ();
653 StringBuilder sb = new StringBuilder ("Mono"); // Ensure begins with alpha
654 sb.Append (XmlConvert.EncodeLocalName (n.BaseURI));
655 sb.Replace ('_', 'm'); // remove underscores from EncodeLocalName
656 sb.Append (n.NodeType);
660 sb.Append (IndexInParent (n));
662 } while (n.MoveToParent ());
664 return sb.ToString ();
667 int IndexInParent (XPathNavigator nav)
670 while (nav.MoveToPrevious ())
678 class XsltKey : XPathFunction {
679 Expression arg0, arg1;
680 XmlNamespaceManager nsm;
682 public XsltKey (FunctionArguments args, IStaticXsltContext ctx) : base (args)
684 if (args == null || args.Tail == null)
685 throw new XPathException ("key takes 2 args");
687 arg1 = args.Tail.Arg;
690 public Expression KeyName { get { return arg0; } }
691 public Expression Field { get { return arg1; } }
692 public XmlNamespaceManager NamespaceManager { get { return nsm; } }
693 public override XPathResultType ReturnType { get { return XPathResultType.NodeSet; }}
695 public override object Evaluate (BaseIterator iter)
697 ArrayList result = new ArrayList ();
698 QName name = XslNameUtil.FromString (arg0.EvaluateString (iter), nsm);
699 object o = arg1.Evaluate (iter);
700 XPathNodeIterator it = o as XPathNodeIterator;
703 while (it.MoveNext())
704 FindKeyMatch ((iter.NamespaceManager as XsltCompiledContext), name, it.Current.Value, result, iter.Current);
706 FindKeyMatch ((iter.NamespaceManager as XsltCompiledContext), name, XPathFunctions.ToString (o), result, iter.Current);
709 return new EnumeratorIterator (result.GetEnumerator (), (iter.NamespaceManager as XsltCompiledContext));
712 void FindKeyMatch (XsltCompiledContext xsltContext, QName name, string value, ArrayList result, XPathNavigator context)
714 XPathNavigator searchDoc = context.Clone ();
715 searchDoc.MoveToRoot ();
716 XslKey key = xsltContext.Processor.CompiledStyle.Style.FindKey (name);
718 XPathNodeIterator desc = searchDoc.SelectDescendants (XPathNodeType.All, true);
720 while (desc.MoveNext ()) {
721 if (key.Matches (desc.Current, value))
722 AddResult (result, desc.Current);
724 if (desc.Current.MoveToFirstAttribute ()) {
726 if (key.Matches (desc.Current, value))
727 AddResult (result, desc.Current);
728 } while (desc.Current.MoveToNextAttribute ());
730 desc.Current.MoveToParent ();
736 void AddResult (ArrayList result, XPathNavigator nav)
738 for (int i = 0; i < result.Count; i++) {
739 XmlNodeOrder docOrder = nav.ComparePosition (((XPathNavigator)result [i]));
740 if (docOrder == XmlNodeOrder.Same)
743 if (docOrder == XmlNodeOrder.Before) {
744 result.Insert(i, nav.Clone ());
748 result.Add (nav.Clone ());
752 class XsltSystemProperty : XPathFunction {
754 XmlNamespaceManager nsm;
756 public XsltSystemProperty (FunctionArguments args, IStaticXsltContext ctx) : base (args)
758 if (args == null || args.Tail != null)
759 throw new XPathException ("system-property takes 1 arg");
765 public override XPathResultType ReturnType { get { return XPathResultType.String; }}
766 public override object Evaluate (BaseIterator iter)
768 QName name = XslNameUtil.FromString (arg0.EvaluateString (iter), nsm);
770 if (name.Namespace == Compiler.XsltNamespace) {
772 case "version": return "1.0";
773 case "vendor": return "Mono";
774 case "vendor-url": return "http://www.go-mono.com/";
782 class XsltUnparsedEntityUri : XPathFunction {
785 public XsltUnparsedEntityUri (FunctionArguments args) : base (args)
787 if (args == null || args.Tail != null)
788 throw new XPathException ("unparsed-entity-uri takes 1 arg");
793 public override XPathResultType ReturnType { get { return XPathResultType.String; }}
794 public override object Evaluate (BaseIterator iter)
796 IHasXmlNode xn = iter.Current as IHasXmlNode;
799 XmlNode n = xn.GetNode ();
800 XmlDocumentType doctype = n.OwnerDocument.DocumentType;
803 XmlEntity ent = doctype.Entities.GetNamedItem (arg0.EvaluateString (iter)) as XmlEntity;