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;
42 using QName = System.Xml.XmlQualifiedName;
44 namespace Mono.Xml.Xsl
46 internal abstract class XPFuncImpl : IXsltContextFunction
49 XPathResultType returnType;
50 XPathResultType [] argTypes;
52 public XPFuncImpl () {}
53 public XPFuncImpl (int minArgs, int maxArgs, XPathResultType returnType, XPathResultType[] argTypes)
55 this.Init(minArgs, maxArgs, returnType, argTypes);
58 protected void Init (int minArgs, int maxArgs, XPathResultType returnType, XPathResultType[] argTypes)
60 this.minargs = minArgs;
61 this.maxargs = maxArgs;
62 this.returnType = returnType;
63 this.argTypes = argTypes;
66 public int Minargs { get { return this.minargs; }}
67 public int Maxargs { get { return this.maxargs; }}
68 public XPathResultType ReturnType { get { return this.returnType; }}
69 public XPathResultType [] ArgTypes { get { return this.argTypes; }}
70 public object Invoke (XsltContext xsltContext, object [] args, XPathNavigator docContext)
72 return Invoke ((XsltCompiledContext)xsltContext, args, docContext);
75 public abstract object Invoke (XsltCompiledContext xsltContext, object [] args, XPathNavigator docContext);
77 public static XPathResultType GetXPathType (Type type, XPathNavigator node) {
78 switch (Type.GetTypeCode(type)) {
80 return XPathResultType.String;
81 case TypeCode.Boolean:
82 return XPathResultType.Boolean;
84 if (typeof (XPathNavigator).IsAssignableFrom (type) || typeof (IXPathNavigable).IsAssignableFrom (type))
85 return XPathResultType.Navigator;
87 if (typeof (XPathNodeIterator).IsAssignableFrom (type))
88 return XPathResultType.NodeSet;
90 return XPathResultType.Any;
91 case TypeCode.DateTime :
92 throw new XsltException ("Invalid type DateTime was specified.", null, node);
94 return XPathResultType.Number;
99 class XsltExtensionFunction : XPFuncImpl
101 private object extension;
102 private MethodInfo method;
103 private TypeCode [] typeCodes;
105 public XsltExtensionFunction (object extension, MethodInfo method, XPathNavigator currentNode)
107 this.extension = extension;
108 this.method = method;
110 ParameterInfo [] parameters = method.GetParameters ();
111 int minArgs = parameters.Length;
112 int maxArgs = parameters.Length;
114 this.typeCodes = new TypeCode [parameters.Length];
115 XPathResultType[] argTypes = new XPathResultType [parameters.Length];
117 bool canBeOpt = true;
118 for (int i = parameters.Length - 1; 0 <= i; i--) { // optionals at the end
119 typeCodes [i] = Type.GetTypeCode (parameters [i].ParameterType);
120 argTypes [i] = GetXPathType (parameters [i].ParameterType, currentNode);
122 if (parameters[i].IsOptional)
128 base.Init (minArgs, maxArgs, GetXPathType (method.ReturnType, currentNode), argTypes);
131 public override object Invoke (XsltCompiledContext xsltContext, object [] args, XPathNavigator docContext)
134 ParameterInfo [] pis = method.GetParameters ();
135 object [] castedArgs = new object [pis.Length];
136 for (int i = 0; i < args.Length; i++) {
137 Type t = pis [i].ParameterType;
138 switch (t.FullName) {
140 case "System.UInt16":
142 case "System.UInt32":
144 case "System.UInt64":
145 case "System.Single":
146 case "System.Decimal":
147 castedArgs [i] = Convert.ChangeType (args [i], t);
150 castedArgs [i] = args [i];
155 object result = null;
156 switch (method.ReturnType.FullName) {
158 case "System.UInt16":
160 case "System.UInt32":
162 case "System.UInt64":
163 case "System.Single":
164 case "System.Decimal":
165 result = Convert.ChangeType (method.Invoke (extension, castedArgs), typeof (double));
168 result = method.Invoke(extension, castedArgs);
171 IXPathNavigable navigable = result as IXPathNavigable;
172 if (navigable != null)
173 return navigable.CreateNavigator ();
176 } catch (Exception ex) {
177 throw new XsltException ("Custom function reported an error.", ex);
178 // Debug.WriteLine ("****** INCORRECT RESOLUTION **********");
183 class XsltCurrent : XPathFunction
185 public XsltCurrent (FunctionArguments args) : base (args)
188 throw new XPathException ("current takes 0 args");
191 public override XPathResultType ReturnType { get { return XPathResultType.NodeSet; }}
193 public override object Evaluate (BaseIterator iter)
195 XsltCompiledContext ctx = (XsltCompiledContext) iter.NamespaceManager;
196 return new SelfIterator ((ctx).Processor.CurrentNode, ctx);
199 internal override bool Peer {
200 get { return false; }
203 public override string ToString ()
209 class XsltDocument : XPathFunction
211 Expression arg0, arg1;
214 public XsltDocument (FunctionArguments args, Compiler c) : base (args)
216 if (args == null || (args.Tail != null && args.Tail.Tail != null))
217 throw new XPathException ("document takes one or two args");
220 if (args.Tail != null)
221 arg1 = args.Tail.Arg;
222 doc = c.Input.Clone ();
224 public override XPathResultType ReturnType { get { return XPathResultType.NodeSet; }}
226 internal override bool Peer {
227 get { return arg0.Peer && (arg1 != null ? arg1.Peer : true); }
230 public override object Evaluate (BaseIterator iter)
232 string baseUri = null;
234 XPathNodeIterator it = arg1.EvaluateNodeSet (iter);
236 baseUri = it.Current.BaseURI;
238 baseUri = VoidBaseUriFlag;
241 object o = arg0.Evaluate (iter);
242 if (o is XPathNodeIterator)
243 return GetDocument ((iter.NamespaceManager as XsltCompiledContext), (XPathNodeIterator)o, baseUri);
245 return GetDocument ((iter.NamespaceManager as XsltCompiledContext), o is IFormattable ? ((IFormattable) o).ToString (null, CultureInfo.InvariantCulture) : (o != null ? o.ToString () : null), baseUri);
248 static string VoidBaseUriFlag = "&^)(*&%*^$&$VOID!BASE!URI!";
250 Uri Resolve (string thisUri, string baseUri, XslTransformProcessor p)
252 // Debug.WriteLine ("THIS: " + thisUri);
253 // Debug.WriteLine ("BASE: " + baseUri);
254 XmlResolver r = p.Resolver;
258 if (! object.ReferenceEquals (baseUri, VoidBaseUriFlag) && baseUri != String.Empty)
259 uriBase = r.ResolveUri (null, baseUri);
261 return r.ResolveUri (uriBase, thisUri);
264 XPathNodeIterator GetDocument (XsltCompiledContext xsltContext, XPathNodeIterator itr, string baseUri)
266 ArrayList list = new ArrayList ();
268 Hashtable got = new Hashtable ();
270 while (itr.MoveNext()) {
271 Uri uri = Resolve (itr.Current.Value, baseUri != null ? baseUri : /*itr.Current.BaseURI*/doc.BaseURI, xsltContext.Processor);
272 if (!got.ContainsKey (uri)) {
274 if (uri != null && uri.ToString () == "") {
275 XPathNavigator n = doc.Clone ();
279 list.Add (xsltContext.Processor.GetDocument (uri));
282 } catch (Exception) {
284 // See http://www.w3.org/TR/xslt#document and
288 return new ListIterator (list, xsltContext);
291 XPathNodeIterator GetDocument (XsltCompiledContext xsltContext, string arg0, string baseUri)
294 Uri uri = Resolve (arg0, baseUri != null ? baseUri : doc.BaseURI, xsltContext.Processor);
296 if (uri != null && uri.ToString () == "") {
300 n = xsltContext.Processor.GetDocument (uri);
302 return new SelfIterator (n, xsltContext);
303 } catch (Exception) {
304 return new ListIterator (new ArrayList (), xsltContext);
308 public override string ToString ()
310 return String.Concat ("document(",
312 arg1 != null ? "," : String.Empty,
313 arg1 != null ? arg1.ToString () : String.Empty,
318 class XsltElementAvailable : XPathFunction
321 IStaticXsltContext ctx;
323 public XsltElementAvailable (FunctionArguments args, IStaticXsltContext ctx) : base (args)
325 if (args == null || args.Tail != null)
326 throw new XPathException ("element-available takes 1 arg");
332 public override XPathResultType ReturnType { get { return XPathResultType.Boolean; }}
334 internal override bool Peer {
335 get { return arg0.Peer; }
338 public override object Evaluate (BaseIterator iter)
340 QName name = XslNameUtil.FromString (arg0.EvaluateString (iter), ctx);
343 (name.Namespace == Compiler.XsltNamespace) &&
346 // A list of all the instructions (does not include top-level-elements)
348 name.Name == "apply-imports" ||
349 name.Name == "apply-templates" ||
350 name.Name == "call-template" ||
351 name.Name == "choose" ||
352 name.Name == "comment" ||
353 name.Name == "copy" ||
354 name.Name == "copy-of" ||
355 name.Name == "element" ||
356 name.Name == "fallback" ||
357 name.Name == "for-each" ||
358 name.Name == "message" ||
359 name.Name == "number" ||
360 name.Name == "processing-instruction" ||
361 name.Name == "text" ||
362 name.Name == "value-of" ||
363 name.Name == "variable"
369 class XsltFormatNumber : XPathFunction
371 Expression arg0, arg1, arg2;
372 IStaticXsltContext ctx;
374 public XsltFormatNumber (FunctionArguments args, IStaticXsltContext ctx) : base (args)
376 if (args == null || args.Tail == null || (args.Tail.Tail != null && args.Tail.Tail.Tail != null))
377 throw new XPathException ("format-number takes 2 or 3 args");
380 arg1 = args.Tail.Arg;
381 if (args.Tail.Tail != null) {
382 arg2= args.Tail.Tail.Arg;
386 public override XPathResultType ReturnType { get { return XPathResultType.String; }}
388 internal override bool Peer {
389 get { return arg0.Peer && arg1.Peer && (arg2 != null ? arg2.Peer : true); }
392 public override object Evaluate (BaseIterator iter)
394 double d = arg0.EvaluateNumber (iter);
395 string s = arg1.EvaluateString (iter);
396 QName nm = QName.Empty;
399 nm = XslNameUtil.FromString (arg2.EvaluateString (iter), ctx);
402 return ((XsltCompiledContext) iter.NamespaceManager).Processor.CompiledStyle
403 .LookupDecimalFormat (nm).FormatNumber (d, s);
404 } catch (ArgumentException ex) {
405 throw new XsltException (ex.Message, ex, iter.Current);
410 class XsltFunctionAvailable : XPathFunction
413 IStaticXsltContext ctx;
415 public XsltFunctionAvailable (FunctionArguments args, IStaticXsltContext ctx) : base (args)
417 if (args == null || args.Tail != null)
418 throw new XPathException ("element-available takes 1 arg");
424 public override XPathResultType ReturnType { get { return XPathResultType.Boolean; }}
426 internal override bool Peer {
427 get { return arg0.Peer; }
430 public override object Evaluate (BaseIterator iter)
433 string name = arg0.EvaluateString (iter);
434 int colon = name.IndexOf (':');
435 // extension function
437 return (iter.NamespaceManager as XsltCompiledContext).ResolveFunction (
438 XslNameUtil.FromString (name, ctx),
448 name == "contains" ||
455 name == "local-name" ||
457 name == "namespace-uri" ||
458 name == "normalize-space" ||
461 name == "position" ||
463 name == "starts-with" ||
465 name == "string-length" ||
466 name == "substring" ||
467 name == "substring-after" ||
468 name == "substring-before" ||
470 name == "translate" ||
473 name == "document" ||
474 name == "format-number" ||
475 name == "function-available" ||
476 name == "generate-id" ||
479 name == "unparsed-entity-uri" ||
480 name == "element-available" ||
481 name == "system-property"
486 class XsltGenerateId : XPathFunction
488 //FIXME: generate short string, not the huge thing it makes now
490 public XsltGenerateId (FunctionArguments args) : base (args)
493 if (args.Tail != null)
494 throw new XPathException ("generate-id takes 1 or no args");
499 public override XPathResultType ReturnType { get { return XPathResultType.String; }}
501 internal override bool Peer {
502 get { return arg0.Peer; }
505 public override object Evaluate (BaseIterator iter)
509 XPathNodeIterator itr = arg0.EvaluateNodeSet (iter);
511 n = itr.Current.Clone ();
513 return string.Empty; // empty nodeset == empty string
515 n = iter.Current.Clone ();
517 StringBuilder sb = new StringBuilder ("Mono"); // Ensure begins with alpha
518 sb.Append (XmlConvert.EncodeLocalName (n.BaseURI));
519 sb.Replace ('_', 'm'); // remove underscores from EncodeLocalName
520 sb.Append (n.NodeType);
524 sb.Append (IndexInParent (n));
526 } while (n.MoveToParent ());
528 return sb.ToString ();
531 int IndexInParent (XPathNavigator nav)
534 while (nav.MoveToPrevious ())
541 class XsltKey : XPathFunction
543 Expression arg0, arg1;
544 IStaticXsltContext staticContext;
546 public XsltKey (FunctionArguments args, IStaticXsltContext ctx) : base (args)
549 if (args == null || args.Tail == null)
550 throw new XPathException ("key takes 2 args");
552 arg1 = args.Tail.Arg;
554 public Expression KeyName { get { return arg0; } }
555 public Expression Field { get { return arg1; } }
556 public override XPathResultType ReturnType { get { return XPathResultType.NodeSet; }}
558 internal override bool Peer {
559 get { return arg0.Peer && arg1.Peer; }
562 public bool PatternMatches (XPathNavigator nav, XsltContext nsmgr)
564 XsltCompiledContext ctx = nsmgr as XsltCompiledContext;
565 // for key pattern, it must contain literal value
566 return ctx.MatchesKey (nav, staticContext,
567 arg0.StaticValueAsString,
568 arg1.StaticValueAsString);
571 public override object Evaluate (BaseIterator iter)
573 XsltCompiledContext ctx = iter.NamespaceManager
574 as XsltCompiledContext;
575 return ctx.EvaluateKey (staticContext, iter, arg0, arg1);
579 class XsltSystemProperty : XPathFunction
582 IStaticXsltContext ctx;
584 public XsltSystemProperty (FunctionArguments args, IStaticXsltContext ctx) : base (args)
586 if (args == null || args.Tail != null)
587 throw new XPathException ("system-property takes 1 arg");
593 public override XPathResultType ReturnType { get { return XPathResultType.String; }}
595 internal override bool Peer {
596 get { return arg0.Peer; }
599 public override object Evaluate (BaseIterator iter)
601 QName name = XslNameUtil.FromString (arg0.EvaluateString (iter), ctx);
603 if (name.Namespace == Compiler.XsltNamespace) {
605 case "version": return "1.0";
606 case "vendor": return "Mono";
607 case "vendor-url": return "http://www.go-mono.com/";
615 class XsltUnparsedEntityUri : XPathFunction
619 public XsltUnparsedEntityUri (FunctionArguments args) : base (args)
621 if (args == null || args.Tail != null)
622 throw new XPathException ("unparsed-entity-uri takes 1 arg");
627 public override XPathResultType ReturnType { get { return XPathResultType.String; }}
629 internal override bool Peer {
630 get { return arg0.Peer; }
633 public override object Evaluate (BaseIterator iter)
635 IHasXmlNode xn = iter.Current as IHasXmlNode;
638 XmlNode n = xn.GetNode ();
639 if (n.OwnerDocument == null)
641 XmlDocumentType doctype = n.OwnerDocument.DocumentType;
644 XmlEntity ent = doctype.Entities.GetNamedItem (arg0.EvaluateString (iter)) as XmlEntity;
647 return ent.SystemId != null ? ent.SystemId : String.Empty;
651 class MSXslNodeSet : XPathFunction
656 public MSXslNodeSet (bool strict, FunctionArguments args) : base (args)
658 if (args == null || args.Tail != null)
659 throw new XPathException ("element-available takes 1 arg");
661 this.strict = strict;
665 public override XPathResultType ReturnType {
667 return XPathResultType.NodeSet;
671 internal override bool Peer {
672 get { return arg0.Peer; }
675 public override object Evaluate (BaseIterator iter)
677 XsltCompiledContext ctx = iter.NamespaceManager as XsltCompiledContext;
678 XPathNavigator loc = iter.Current != null ? iter.Current.Clone () : null;
679 object val = arg0.Evaluate (iter);
681 XPathNavigator nav = val as XPathNavigator;
682 if (nav == null && !strict) {
683 var iterResult = val as XPathNodeIterator;
684 if (iterResult != null)
687 var strResult = val as string;
688 if (strResult == string.Empty) {
689 DTMXPathDocumentWriter2 w = new DTMXPathDocumentWriter2 (ctx.Processor.Root.NameTable, 10);
690 nav = w.CreateDocument ().CreateNavigator ();
696 return new XsltException ("Cannot convert the XPath argument to a result tree fragment.", null, loc);
698 return new XsltException ("Cannot convert the XPath argument to a result tree fragment.", null);
701 ArrayList al = new ArrayList ();
703 return new ListIterator (al, ctx);