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 object result = method.Invoke(extension, args);
332 IXPathNavigable navigable = result as IXPathNavigable;
333 if (navigable != null)
334 return navigable.CreateNavigator ();
338 Debug.WriteLine ("****** INCORRECT RESOLUTION **********");
344 class XsltCurrent : XPathFunction {
345 public XsltCurrent (FunctionArguments args) : base (args)
348 throw new XPathException ("current takes 0 args");
351 public override XPathResultType ReturnType { get { return XPathResultType.NodeSet; }}
353 public override object Evaluate (BaseIterator iter)
355 return new SelfIterator ((iter.NamespaceManager as XsltCompiledContext).Processor.CurrentNode, null);
359 class XsltDocument : XPathFunction {
360 Expression arg0, arg1;
363 public XsltDocument (FunctionArguments args, Compiler c) : base (args)
365 if (args == null || (args.Tail != null && args.Tail.Tail != null))
366 throw new XPathException ("document takes one or two args");
369 if (args.Tail != null)
370 arg1 = args.Tail.Arg;
371 doc = c.Input.Clone ();
373 public override XPathResultType ReturnType { get { return XPathResultType.NodeSet; }}
375 public override object Evaluate (BaseIterator iter)
377 string baseUri = null;
379 XPathNodeIterator it = arg1.EvaluateNodeSet (iter);
381 baseUri = it.Current.BaseURI;
383 baseUri = VoidBaseUriFlag;
386 object o = arg0.Evaluate (iter);
387 if (o is XPathNodeIterator)
388 return GetDocument ((iter.NamespaceManager as XsltCompiledContext), (XPathNodeIterator)o, baseUri);
390 return GetDocument ((iter.NamespaceManager as XsltCompiledContext), o.ToString (), baseUri);
393 static string VoidBaseUriFlag = "&^)(*&%*^$&$VOID!BASE!URI!";
395 Uri Resolve (string thisUri, string baseUri, XslTransformProcessor p)
397 Debug.WriteLine ("THIS: " + thisUri);
398 Debug.WriteLine ("BASE: " + baseUri);
399 XmlResolver r = p.Resolver;
402 if (! object.ReferenceEquals (baseUri, VoidBaseUriFlag))
403 uriBase = r.ResolveUri (null, baseUri);
405 return r.ResolveUri (uriBase, thisUri);
408 XPathNodeIterator GetDocument (XsltCompiledContext xsltContext, XPathNodeIterator itr, string baseUri)
410 ArrayList list = new ArrayList ();
411 Hashtable got = new Hashtable ();
413 while (itr.MoveNext()) {
414 Uri uri = Resolve (itr.Current.Value, baseUri != null ? baseUri : /*itr.Current.BaseURI*/doc.BaseURI, xsltContext.Processor);
415 if (!got.ContainsKey (uri)) {
417 if (uri.ToString () == "") {
418 XPathNavigator n = doc.Clone ();
422 list.Add (xsltContext.Processor.GetDocument (uri));
426 return new EnumeratorIterator (list.GetEnumerator (), xsltContext);
429 XPathNodeIterator GetDocument (XsltCompiledContext xsltContext, string arg0, string baseUri)
431 Uri uri = Resolve (arg0, baseUri != null ? baseUri : doc.BaseURI, xsltContext.Processor);
433 if (uri.ToString () == "") {
437 n = xsltContext.Processor.GetDocument (uri);
439 return new SelfIterator (n, xsltContext);
443 class XsltElementAvailable : XPathFunction {
445 XmlNamespaceManager nsm;
447 public XsltElementAvailable (FunctionArguments args, IStaticXsltContext ctx) : base (args)
449 if (args == null || args.Tail != null)
450 throw new XPathException ("element-available takes 1 arg");
456 public override XPathResultType ReturnType { get { return XPathResultType.Boolean; }}
458 public override object Evaluate (BaseIterator iter)
460 QName name = XslNameUtil.FromString (arg0.EvaluateString (iter), nsm);
463 (name.Namespace == Compiler.XsltNamespace) &&
466 // A list of all the instructions (does not include top-level-elements)
468 name.Name == "apply-imports" ||
469 name.Name == "apply-templates" ||
470 name.Name == "call-template" ||
471 name.Name == "choose" ||
472 name.Name == "comment" ||
473 name.Name == "copy" ||
474 name.Name == "copy-of" ||
475 name.Name == "element" ||
476 name.Name == "fallback" ||
477 name.Name == "for-each" ||
478 name.Name == "message" ||
479 name.Name == "number" ||
480 name.Name == "processing-instruction" ||
481 name.Name == "text" ||
482 name.Name == "value-of" ||
483 name.Name == "variable"
489 class XsltFormatNumber : XPathFunction {
490 Expression arg0, arg1, arg2;
491 XmlNamespaceManager nsm;
493 public XsltFormatNumber (FunctionArguments args, IStaticXsltContext ctx) : base (args)
495 if (args == null || args.Tail == null || (args.Tail.Tail != null && args.Tail.Tail.Tail != null))
496 throw new XPathException ("format-number takes 2 or 3 args");
499 arg1 = args.Tail.Arg;
500 if (args.Tail.Tail != null) {
501 arg2= args.Tail.Tail.Arg;
505 public override XPathResultType ReturnType { get { return XPathResultType.String; }}
507 public override object Evaluate (BaseIterator iter)
509 double d = arg0.EvaluateNumber (iter);
510 string s = arg1.EvaluateString (iter);
511 QName nm = QName.Empty;
514 nm = XslNameUtil.FromString (arg2.EvaluateString (iter), nsm);
516 return (iter.NamespaceManager as XsltCompiledContext).Processor.CompiledStyle
517 .LookupDecimalFormat (nm).FormatNumber (d, s);
521 class XsltFunctionAvailable : XPathFunction {
523 XmlNamespaceManager nsm;
525 public XsltFunctionAvailable (FunctionArguments args, IStaticXsltContext ctx) : base (args)
527 if (args == null || args.Tail != null)
528 throw new XPathException ("element-available takes 1 arg");
534 public override XPathResultType ReturnType { get { return XPathResultType.Boolean; }}
536 public override object Evaluate (BaseIterator iter)
539 string name = arg0.EvaluateString (iter);
540 int colon = name.IndexOf (':');
541 // extension function
543 return (iter.NamespaceManager as XsltCompiledContext).ResolveFunction (
544 XslNameUtil.FromString (name, nsm),
554 name == "contains" ||
561 name == "local-name" ||
563 name == "namespace-uri" ||
564 name == "normalize-space" ||
567 name == "position" ||
569 name == "starts-with" ||
571 name == "string-length" ||
572 name == "substring" ||
573 name == "substring-after" ||
574 name == "substring-before" ||
576 name == "translate" ||
579 name == "document" ||
580 name == "format-number" ||
581 name == "function-available" ||
582 name == "generate-id" ||
585 name == "unparsed-entity-uri" ||
586 name == "element-available" ||
587 name == "system-property"
592 class XsltGenerateId : XPathFunction {
594 public XsltGenerateId (FunctionArguments args) : base (args)
597 if (args.Tail != null)
598 throw new XPathException ("generate-id takes 1 or no args");
603 public override XPathResultType ReturnType { get { return XPathResultType.String; }}
604 public override object Evaluate (BaseIterator iter)
608 XPathNodeIterator itr = arg0.EvaluateNodeSet (iter);
610 n = itr.Current.Clone ();
612 return string.Empty; // empty nodeset == empty string
614 n = iter.Current.Clone ();
616 StringBuilder sb = new StringBuilder ("Mono"); // Ensure begins with alpha
617 sb.Append (XmlConvert.EncodeLocalName (n.BaseURI));
618 sb.Replace ('_', 'm'); // remove underscores from EncodeLocalName
619 sb.Append (n.NodeType);
623 sb.Append (IndexInParent (n));
625 } while (n.MoveToParent ());
627 return sb.ToString ();
630 int IndexInParent (XPathNavigator nav)
633 while (nav.MoveToPrevious ())
641 class XsltKey : XPathFunction {
642 Expression arg0, arg1;
643 XmlNamespaceManager nsm;
645 public XsltKey (FunctionArguments args, IStaticXsltContext ctx) : base (args)
647 if (args == null || args.Tail == null)
648 throw new XPathException ("key takes 2 args");
650 arg1 = args.Tail.Arg;
653 public Expression KeyName { get { return arg0; } }
654 public Expression Field { get { return arg1; } }
655 public XmlNamespaceManager NamespaceManager { get { return nsm; } }
656 public override XPathResultType ReturnType { get { return XPathResultType.NodeSet; }}
658 public override object Evaluate (BaseIterator iter)
660 ArrayList result = new ArrayList ();
661 QName name = XslNameUtil.FromString (arg0.EvaluateString (iter), nsm);
662 object o = arg1.Evaluate (iter);
663 XPathNodeIterator it = o as XPathNodeIterator;
666 while (it.MoveNext())
667 FindKeyMatch ((iter.NamespaceManager as XsltCompiledContext), name, it.Current.Value, result, iter.Current);
669 FindKeyMatch ((iter.NamespaceManager as XsltCompiledContext), name, XPathFunctions.ToString (o), result, iter.Current);
672 return new EnumeratorIterator (result.GetEnumerator (), (iter.NamespaceManager as XsltCompiledContext));
675 void FindKeyMatch (XsltCompiledContext xsltContext, QName name, string value, ArrayList result, XPathNavigator context)
677 XPathNavigator searchDoc = context.Clone ();
678 searchDoc.MoveToRoot ();
679 XslKey key = xsltContext.Processor.CompiledStyle.Style.FindKey (name);
681 XPathNodeIterator desc = searchDoc.SelectDescendants (XPathNodeType.All, true);
683 while (desc.MoveNext ()) {
684 if (key.Matches (desc.Current, value))
685 AddResult (result, desc.Current);
687 if (desc.Current.MoveToFirstAttribute ()) {
689 if (key.Matches (desc.Current, value))
690 AddResult (result, desc.Current);
691 } while (desc.Current.MoveToNextAttribute ());
693 desc.Current.MoveToParent ();
699 void AddResult (ArrayList result, XPathNavigator nav)
701 for (int i = 0; i < result.Count; i++) {
702 XmlNodeOrder docOrder = nav.ComparePosition (((XPathNavigator)result [i]));
703 if (docOrder == XmlNodeOrder.Same)
706 if (docOrder == XmlNodeOrder.Before) {
707 result.Insert(i, nav.Clone ());
711 result.Add (nav.Clone ());
715 class XsltSystemProperty : XPathFunction {
717 XmlNamespaceManager nsm;
719 public XsltSystemProperty (FunctionArguments args, IStaticXsltContext ctx) : base (args)
721 if (args == null || args.Tail != null)
722 throw new XPathException ("system-property takes 1 arg");
728 public override XPathResultType ReturnType { get { return XPathResultType.String; }}
729 public override object Evaluate (BaseIterator iter)
731 QName name = XslNameUtil.FromString (arg0.EvaluateString (iter), nsm);
733 if (name.Namespace == Compiler.XsltNamespace) {
735 case "version": return "1.0";
736 case "vendor": return "Mono";
737 case "vendor-url": return "http://www.go-mono.com/";
745 class XsltUnparsedEntityUri : XPathFunction {
748 public XsltUnparsedEntityUri (FunctionArguments args) : base (args)
750 if (args == null || args.Tail != null)
751 throw new XPathException ("unparsed-entity-uri takes 1 arg");
756 public override XPathResultType ReturnType { get { return XPathResultType.String; }}
757 public override object Evaluate (BaseIterator iter)
759 IHasXmlNode xn = iter.Current as IHasXmlNode;
762 XmlNode n = xn.GetNode ();
763 XmlDocumentType doctype = n.OwnerDocument.DocumentType;
766 XmlEntity ent = doctype.Entities.GetNamedItem (arg0.EvaluateString (iter)) as XmlEntity;