2 // XsltCompiledContext.cs
5 // Ben Maurer (bmaurer@users.sourceforge.net)
6 // Atsushi Enomoto (atsushi@ximian.com)
8 // (C) 2004 Atsushi Enomoto
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 using System.Collections;
33 using System.Globalization;
34 using System.Reflection;
37 using System.Xml.XPath;
41 using QName = System.Xml.XmlQualifiedName;
43 namespace Mono.Xml.Xsl
45 internal abstract class XPFuncImpl : IXsltContextFunction
48 XPathResultType returnType;
49 XPathResultType [] argTypes;
51 public XPFuncImpl () {}
52 public XPFuncImpl (int minArgs, int maxArgs, XPathResultType returnType, XPathResultType[] argTypes)
54 this.Init(minArgs, maxArgs, returnType, argTypes);
57 protected void Init (int minArgs, int maxArgs, XPathResultType returnType, XPathResultType[] argTypes)
59 this.minargs = minArgs;
60 this.maxargs = maxArgs;
61 this.returnType = returnType;
62 this.argTypes = argTypes;
65 public int Minargs { get { return this.minargs; }}
66 public int Maxargs { get { return this.maxargs; }}
67 public XPathResultType ReturnType { get { return this.returnType; }}
68 public XPathResultType [] ArgTypes { get { return this.argTypes; }}
69 public object Invoke (XsltContext xsltContext, object [] args, XPathNavigator docContext)
71 return Invoke ((XsltCompiledContext)xsltContext, args, docContext);
74 public abstract object Invoke (XsltCompiledContext xsltContext, object [] args, XPathNavigator docContext);
76 public static XPathResultType GetXPathType (Type type, XPathNavigator node) {
77 switch (Type.GetTypeCode(type)) {
79 return XPathResultType.String;
80 case TypeCode.Boolean:
81 return XPathResultType.Boolean;
83 if (typeof (XPathNavigator).IsAssignableFrom (type) || typeof (IXPathNavigable).IsAssignableFrom (type))
84 return XPathResultType.Navigator;
86 if (typeof (XPathNodeIterator).IsAssignableFrom (type))
87 return XPathResultType.NodeSet;
89 return XPathResultType.Any;
90 case TypeCode.DateTime :
91 throw new XsltException ("Invalid type DateTime was specified.", null, node);
93 return XPathResultType.Number;
98 class XsltExtensionFunction : XPFuncImpl
100 private object extension;
101 private MethodInfo method;
102 private TypeCode [] typeCodes;
104 public XsltExtensionFunction (object extension, MethodInfo method, XPathNavigator currentNode)
106 this.extension = extension;
107 this.method = method;
109 ParameterInfo [] parameters = method.GetParameters ();
110 int minArgs = parameters.Length;
111 int maxArgs = parameters.Length;
113 this.typeCodes = new TypeCode [parameters.Length];
114 XPathResultType[] argTypes = new XPathResultType [parameters.Length];
116 bool canBeOpt = true;
117 for (int i = parameters.Length - 1; 0 <= i; i--) { // optionals at the end
118 typeCodes [i] = Type.GetTypeCode (parameters [i].ParameterType);
119 argTypes [i] = GetXPathType (parameters [i].ParameterType, currentNode);
121 if (parameters[i].IsOptional)
127 base.Init (minArgs, maxArgs, GetXPathType (method.ReturnType, currentNode), argTypes);
130 public override object Invoke (XsltCompiledContext xsltContext, object [] args, XPathNavigator docContext)
133 ParameterInfo [] pis = method.GetParameters ();
134 object [] castedArgs = new object [pis.Length];
135 for (int i = 0; i < args.Length; i++) {
136 Type t = pis [i].ParameterType;
137 switch (t.FullName) {
139 case "System.UInt16":
141 case "System.UInt32":
143 case "System.UInt64":
144 case "System.Single":
145 case "System.Decimal":
146 castedArgs [i] = Convert.ChangeType (args [i], t);
149 castedArgs [i] = args [i];
154 object result = null;
155 switch (method.ReturnType.FullName) {
157 case "System.UInt16":
159 case "System.UInt32":
161 case "System.UInt64":
162 case "System.Single":
163 case "System.Decimal":
164 result = Convert.ChangeType (method.Invoke (extension, castedArgs), typeof (double));
167 result = method.Invoke(extension, castedArgs);
170 IXPathNavigable navigable = result as IXPathNavigable;
171 if (navigable != null)
172 return navigable.CreateNavigator ();
175 } catch (Exception ex) {
176 throw new XsltException ("Custom function reported an error.", ex);
177 // Debug.WriteLine ("****** INCORRECT RESOLUTION **********");
182 class XsltCurrent : XPathFunction
184 public XsltCurrent (FunctionArguments args) : base (args)
187 throw new XPathException ("current takes 0 args");
190 public override XPathResultType ReturnType { get { return XPathResultType.NodeSet; }}
192 public override object Evaluate (BaseIterator iter)
194 XsltCompiledContext ctx = (XsltCompiledContext) iter.NamespaceManager;
195 return new SelfIterator ((ctx).Processor.CurrentNode, ctx);
198 internal override bool Peer {
199 get { return false; }
202 public override string ToString ()
208 class XsltDocument : XPathFunction
210 Expression arg0, arg1;
213 public XsltDocument (FunctionArguments args, Compiler c) : base (args)
215 if (args == null || (args.Tail != null && args.Tail.Tail != null))
216 throw new XPathException ("document takes one or two args");
219 if (args.Tail != null)
220 arg1 = args.Tail.Arg;
221 doc = c.Input.Clone ();
223 public override XPathResultType ReturnType { get { return XPathResultType.NodeSet; }}
225 internal override bool Peer {
226 get { return arg0.Peer && (arg1 != null ? arg1.Peer : true); }
229 public override object Evaluate (BaseIterator iter)
231 string baseUri = null;
233 XPathNodeIterator it = arg1.EvaluateNodeSet (iter);
235 baseUri = it.Current.BaseURI;
237 baseUri = VoidBaseUriFlag;
240 object o = arg0.Evaluate (iter);
241 if (o is XPathNodeIterator)
242 return GetDocument ((iter.NamespaceManager as XsltCompiledContext), (XPathNodeIterator)o, baseUri);
244 return GetDocument ((iter.NamespaceManager as XsltCompiledContext), o is IFormattable ? ((IFormattable) o).ToString (null, CultureInfo.InvariantCulture) : o.ToString (), baseUri);
247 static string VoidBaseUriFlag = "&^)(*&%*^$&$VOID!BASE!URI!";
249 Uri Resolve (string thisUri, string baseUri, XslTransformProcessor p)
251 // Debug.WriteLine ("THIS: " + thisUri);
252 // Debug.WriteLine ("BASE: " + baseUri);
253 XmlResolver r = p.Resolver;
257 if (! object.ReferenceEquals (baseUri, VoidBaseUriFlag) && baseUri != String.Empty)
258 uriBase = r.ResolveUri (null, baseUri);
260 return r.ResolveUri (uriBase, thisUri);
263 XPathNodeIterator GetDocument (XsltCompiledContext xsltContext, XPathNodeIterator itr, string baseUri)
265 ArrayList list = new ArrayList ();
267 Hashtable got = new Hashtable ();
269 while (itr.MoveNext()) {
270 Uri uri = Resolve (itr.Current.Value, baseUri != null ? baseUri : /*itr.Current.BaseURI*/doc.BaseURI, xsltContext.Processor);
271 if (!got.ContainsKey (uri)) {
273 if (uri != null && uri.ToString () == "") {
274 XPathNavigator n = doc.Clone ();
278 list.Add (xsltContext.Processor.GetDocument (uri));
281 } catch (Exception) {
283 // See http://www.w3.org/TR/xslt#document and
287 return new ListIterator (list, xsltContext);
290 XPathNodeIterator GetDocument (XsltCompiledContext xsltContext, string arg0, string baseUri)
293 Uri uri = Resolve (arg0, baseUri != null ? baseUri : doc.BaseURI, xsltContext.Processor);
295 if (uri != null && uri.ToString () == "") {
299 n = xsltContext.Processor.GetDocument (uri);
301 return new SelfIterator (n, xsltContext);
302 } catch (Exception) {
303 return new ListIterator (new ArrayList (), xsltContext);
307 public override string ToString ()
309 return String.Concat ("document(",
311 arg1 != null ? "," : String.Empty,
312 arg1 != null ? arg1.ToString () : String.Empty,
317 class XsltElementAvailable : XPathFunction
320 IStaticXsltContext ctx;
322 public XsltElementAvailable (FunctionArguments args, IStaticXsltContext ctx) : base (args)
324 if (args == null || args.Tail != null)
325 throw new XPathException ("element-available takes 1 arg");
331 public override XPathResultType ReturnType { get { return XPathResultType.Boolean; }}
333 internal override bool Peer {
334 get { return arg0.Peer; }
337 public override object Evaluate (BaseIterator iter)
339 QName name = XslNameUtil.FromString (arg0.EvaluateString (iter), ctx);
342 (name.Namespace == Compiler.XsltNamespace) &&
345 // A list of all the instructions (does not include top-level-elements)
347 name.Name == "apply-imports" ||
348 name.Name == "apply-templates" ||
349 name.Name == "call-template" ||
350 name.Name == "choose" ||
351 name.Name == "comment" ||
352 name.Name == "copy" ||
353 name.Name == "copy-of" ||
354 name.Name == "element" ||
355 name.Name == "fallback" ||
356 name.Name == "for-each" ||
357 name.Name == "message" ||
358 name.Name == "number" ||
359 name.Name == "processing-instruction" ||
360 name.Name == "text" ||
361 name.Name == "value-of" ||
362 name.Name == "variable"
368 class XsltFormatNumber : XPathFunction
370 Expression arg0, arg1, arg2;
371 IStaticXsltContext ctx;
373 public XsltFormatNumber (FunctionArguments args, IStaticXsltContext ctx) : base (args)
375 if (args == null || args.Tail == null || (args.Tail.Tail != null && args.Tail.Tail.Tail != null))
376 throw new XPathException ("format-number takes 2 or 3 args");
379 arg1 = args.Tail.Arg;
380 if (args.Tail.Tail != null) {
381 arg2= args.Tail.Tail.Arg;
385 public override XPathResultType ReturnType { get { return XPathResultType.String; }}
387 internal override bool Peer {
388 get { return arg0.Peer && arg1.Peer && (arg2 != null ? arg2.Peer : true); }
391 public override object Evaluate (BaseIterator iter)
393 double d = arg0.EvaluateNumber (iter);
394 string s = arg1.EvaluateString (iter);
395 QName nm = QName.Empty;
398 nm = XslNameUtil.FromString (arg2.EvaluateString (iter), ctx);
401 return ((XsltCompiledContext) iter.NamespaceManager).Processor.CompiledStyle
402 .LookupDecimalFormat (nm).FormatNumber (d, s);
403 } catch (ArgumentException ex) {
404 throw new XsltException (ex.Message, ex, iter.Current);
409 class XsltFunctionAvailable : XPathFunction
412 IStaticXsltContext ctx;
414 public XsltFunctionAvailable (FunctionArguments args, IStaticXsltContext ctx) : base (args)
416 if (args == null || args.Tail != null)
417 throw new XPathException ("element-available takes 1 arg");
423 public override XPathResultType ReturnType { get { return XPathResultType.Boolean; }}
425 internal override bool Peer {
426 get { return arg0.Peer; }
429 public override object Evaluate (BaseIterator iter)
432 string name = arg0.EvaluateString (iter);
433 int colon = name.IndexOf (':');
434 // extension function
436 return (iter.NamespaceManager as XsltCompiledContext).ResolveFunction (
437 XslNameUtil.FromString (name, ctx),
447 name == "contains" ||
454 name == "local-name" ||
456 name == "namespace-uri" ||
457 name == "normalize-space" ||
460 name == "position" ||
462 name == "starts-with" ||
464 name == "string-length" ||
465 name == "substring" ||
466 name == "substring-after" ||
467 name == "substring-before" ||
469 name == "translate" ||
472 name == "document" ||
473 name == "format-number" ||
474 name == "function-available" ||
475 name == "generate-id" ||
478 name == "unparsed-entity-uri" ||
479 name == "element-available" ||
480 name == "system-property"
485 class XsltGenerateId : XPathFunction
487 //FIXME: generate short string, not the huge thing it makes now
489 public XsltGenerateId (FunctionArguments args) : base (args)
492 if (args.Tail != null)
493 throw new XPathException ("generate-id takes 1 or no args");
498 public override XPathResultType ReturnType { get { return XPathResultType.String; }}
500 internal override bool Peer {
501 get { return arg0.Peer; }
504 public override object Evaluate (BaseIterator iter)
508 XPathNodeIterator itr = arg0.EvaluateNodeSet (iter);
510 n = itr.Current.Clone ();
512 return string.Empty; // empty nodeset == empty string
514 n = iter.Current.Clone ();
516 StringBuilder sb = new StringBuilder ("Mono"); // Ensure begins with alpha
517 sb.Append (XmlConvert.EncodeLocalName (n.BaseURI));
518 sb.Replace ('_', 'm'); // remove underscores from EncodeLocalName
519 sb.Append (n.NodeType);
523 sb.Append (IndexInParent (n));
525 } while (n.MoveToParent ());
527 return sb.ToString ();
530 int IndexInParent (XPathNavigator nav)
533 while (nav.MoveToPrevious ())
540 class XsltKey : XPathFunction
542 Expression arg0, arg1;
543 IStaticXsltContext staticContext;
545 public XsltKey (FunctionArguments args, IStaticXsltContext ctx) : base (args)
548 if (args == null || args.Tail == null)
549 throw new XPathException ("key takes 2 args");
551 arg1 = args.Tail.Arg;
553 public Expression KeyName { get { return arg0; } }
554 public Expression Field { get { return arg1; } }
555 public override XPathResultType ReturnType { get { return XPathResultType.NodeSet; }}
557 internal override bool Peer {
558 get { return arg0.Peer && arg1.Peer; }
561 public bool PatternMatches (XPathNavigator nav, XsltContext nsmgr)
563 XsltCompiledContext ctx = nsmgr as XsltCompiledContext;
564 // for key pattern, it must contain literal value
565 return ctx.MatchesKey (nav, staticContext,
566 arg0.StaticValueAsString,
567 arg1.StaticValueAsString);
570 public override object Evaluate (BaseIterator iter)
572 XsltCompiledContext ctx = iter.NamespaceManager
573 as XsltCompiledContext;
574 return ctx.EvaluateKey (staticContext, iter, arg0, arg1);
578 class XsltSystemProperty : XPathFunction
581 IStaticXsltContext ctx;
583 public XsltSystemProperty (FunctionArguments args, IStaticXsltContext ctx) : base (args)
585 if (args == null || args.Tail != null)
586 throw new XPathException ("system-property takes 1 arg");
592 public override XPathResultType ReturnType { get { return XPathResultType.String; }}
594 internal override bool Peer {
595 get { return arg0.Peer; }
598 public override object Evaluate (BaseIterator iter)
600 QName name = XslNameUtil.FromString (arg0.EvaluateString (iter), ctx);
602 if (name.Namespace == Compiler.XsltNamespace) {
604 case "version": return "1.0";
605 case "vendor": return "Mono";
606 case "vendor-url": return "http://www.go-mono.com/";
614 class XsltUnparsedEntityUri : XPathFunction
618 public XsltUnparsedEntityUri (FunctionArguments args) : base (args)
620 if (args == null || args.Tail != null)
621 throw new XPathException ("unparsed-entity-uri takes 1 arg");
626 public override XPathResultType ReturnType { get { return XPathResultType.String; }}
628 internal override bool Peer {
629 get { return arg0.Peer; }
632 public override object Evaluate (BaseIterator iter)
634 IHasXmlNode xn = iter.Current as IHasXmlNode;
637 XmlNode n = xn.GetNode ();
638 if (n.OwnerDocument == null)
640 XmlDocumentType doctype = n.OwnerDocument.DocumentType;
643 XmlEntity ent = doctype.Entities.GetNamedItem (arg0.EvaluateString (iter)) as XmlEntity;
646 return ent.SystemId != null ? ent.SystemId : String.Empty;
650 class MSXslNodeSet : XPathFunction
654 public MSXslNodeSet (FunctionArguments args) : base (args)
656 if (args == null || args.Tail != null)
657 throw new XPathException ("element-available takes 1 arg");
662 public override XPathResultType ReturnType {
664 return XPathResultType.NodeSet;
668 internal override bool Peer {
669 get { return arg0.Peer; }
672 public override object Evaluate (BaseIterator iter)
674 XsltCompiledContext ctx = iter.NamespaceManager as XsltCompiledContext;
675 XPathNavigator loc = iter.Current != null ? iter.Current.Clone () : null;
676 XPathNavigator nav = arg0.EvaluateAs (iter, XPathResultType.Navigator) as XPathNavigator;
679 return new XsltException ("Cannot convert the XPath argument to a result tree fragment.", null, loc);
681 return new XsltException ("Cannot convert the XPath argument to a result tree fragment.", null);
683 ArrayList al = new ArrayList ();
685 return new ListIterator (al, ctx);