Jo Shields a575963da9 Imported Upstream version 3.6.0
Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
2014-08-13 10:39:27 +01:00

707 lines
20 KiB
C#

//
// XsltCompiledContext.cs
//
// Authors:
// Ben Maurer (bmaurer@users.sourceforge.net)
// Atsushi Enomoto (atsushi@ximian.com)
// (C) 2003 Ben Maurer
// (C) 2004 Atsushi Enomoto
//
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections;
using System.Globalization;
using System.Reflection;
using System.Text;
using System.Xml;
using System.Xml.XPath;
using System.Xml.Xsl;
using Mono.Xml.Xsl;
using Mono.Xml.XPath;
using QName = System.Xml.XmlQualifiedName;
namespace Mono.Xml.Xsl
{
internal abstract class XPFuncImpl : IXsltContextFunction
{
int minargs, maxargs;
XPathResultType returnType;
XPathResultType [] argTypes;
public XPFuncImpl () {}
public XPFuncImpl (int minArgs, int maxArgs, XPathResultType returnType, XPathResultType[] argTypes)
{
this.Init(minArgs, maxArgs, returnType, argTypes);
}
protected void Init (int minArgs, int maxArgs, XPathResultType returnType, XPathResultType[] argTypes)
{
this.minargs = minArgs;
this.maxargs = maxArgs;
this.returnType = returnType;
this.argTypes = argTypes;
}
public int Minargs { get { return this.minargs; }}
public int Maxargs { get { return this.maxargs; }}
public XPathResultType ReturnType { get { return this.returnType; }}
public XPathResultType [] ArgTypes { get { return this.argTypes; }}
public object Invoke (XsltContext xsltContext, object [] args, XPathNavigator docContext)
{
return Invoke ((XsltCompiledContext)xsltContext, args, docContext);
}
public abstract object Invoke (XsltCompiledContext xsltContext, object [] args, XPathNavigator docContext);
public static XPathResultType GetXPathType (Type type, XPathNavigator node) {
switch (Type.GetTypeCode(type)) {
case TypeCode.String:
return XPathResultType.String;
case TypeCode.Boolean:
return XPathResultType.Boolean;
case TypeCode.Object:
if (typeof (XPathNavigator).IsAssignableFrom (type) || typeof (IXPathNavigable).IsAssignableFrom (type))
return XPathResultType.Navigator;
if (typeof (XPathNodeIterator).IsAssignableFrom (type))
return XPathResultType.NodeSet;
return XPathResultType.Any;
case TypeCode.DateTime :
throw new XsltException ("Invalid type DateTime was specified.", null, node);
default: // Numeric
return XPathResultType.Number;
}
}
}
class XsltExtensionFunction : XPFuncImpl
{
private object extension;
private MethodInfo method;
private TypeCode [] typeCodes;
public XsltExtensionFunction (object extension, MethodInfo method, XPathNavigator currentNode)
{
this.extension = extension;
this.method = method;
ParameterInfo [] parameters = method.GetParameters ();
int minArgs = parameters.Length;
int maxArgs = parameters.Length;
this.typeCodes = new TypeCode [parameters.Length];
XPathResultType[] argTypes = new XPathResultType [parameters.Length];
bool canBeOpt = true;
for (int i = parameters.Length - 1; 0 <= i; i--) { // optionals at the end
typeCodes [i] = Type.GetTypeCode (parameters [i].ParameterType);
argTypes [i] = GetXPathType (parameters [i].ParameterType, currentNode);
if (canBeOpt) {
if (parameters[i].IsOptional)
minArgs --;
else
canBeOpt = false;
}
}
base.Init (minArgs, maxArgs, GetXPathType (method.ReturnType, currentNode), argTypes);
}
public override object Invoke (XsltCompiledContext xsltContext, object [] args, XPathNavigator docContext)
{
try {
ParameterInfo [] pis = method.GetParameters ();
object [] castedArgs = new object [pis.Length];
for (int i = 0; i < args.Length; i++) {
Type t = pis [i].ParameterType;
switch (t.FullName) {
case "System.Int16":
case "System.UInt16":
case "System.Int32":
case "System.UInt32":
case "System.Int64":
case "System.UInt64":
case "System.Single":
case "System.Decimal":
castedArgs [i] = Convert.ChangeType (args [i], t);
break;
default:
castedArgs [i] = args [i];
break;
}
}
object result = null;
switch (method.ReturnType.FullName) {
case "System.Int16":
case "System.UInt16":
case "System.Int32":
case "System.UInt32":
case "System.Int64":
case "System.UInt64":
case "System.Single":
case "System.Decimal":
result = Convert.ChangeType (method.Invoke (extension, castedArgs), typeof (double));
break;
default:
result = method.Invoke(extension, castedArgs);
break;
}
IXPathNavigable navigable = result as IXPathNavigable;
if (navigable != null)
return navigable.CreateNavigator ();
return result;
} catch (Exception ex) {
throw new XsltException ("Custom function reported an error.", ex);
// Debug.WriteLine ("****** INCORRECT RESOLUTION **********");
}
}
}
class XsltCurrent : XPathFunction
{
public XsltCurrent (FunctionArguments args) : base (args)
{
if (args != null)
throw new XPathException ("current takes 0 args");
}
public override XPathResultType ReturnType { get { return XPathResultType.NodeSet; }}
public override object Evaluate (BaseIterator iter)
{
XsltCompiledContext ctx = (XsltCompiledContext) iter.NamespaceManager;
return new SelfIterator ((ctx).Processor.CurrentNode, ctx);
}
internal override bool Peer {
get { return false; }
}
public override string ToString ()
{
return "current()";
}
}
class XsltDocument : XPathFunction
{
Expression arg0, arg1;
XPathNavigator doc;
public XsltDocument (FunctionArguments args, Compiler c) : base (args)
{
if (args == null || (args.Tail != null && args.Tail.Tail != null))
throw new XPathException ("document takes one or two args");
arg0 = args.Arg;
if (args.Tail != null)
arg1 = args.Tail.Arg;
doc = c.Input.Clone ();
}
public override XPathResultType ReturnType { get { return XPathResultType.NodeSet; }}
internal override bool Peer {
get { return arg0.Peer && (arg1 != null ? arg1.Peer : true); }
}
public override object Evaluate (BaseIterator iter)
{
string baseUri = null;
if (arg1 != null) {
XPathNodeIterator it = arg1.EvaluateNodeSet (iter);
if (it.MoveNext())
baseUri = it.Current.BaseURI;
else
baseUri = VoidBaseUriFlag;
}
object o = arg0.Evaluate (iter);
if (o is XPathNodeIterator)
return GetDocument ((iter.NamespaceManager as XsltCompiledContext), (XPathNodeIterator)o, baseUri);
else
return GetDocument ((iter.NamespaceManager as XsltCompiledContext), o is IFormattable ? ((IFormattable) o).ToString (null, CultureInfo.InvariantCulture) : (o != null ? o.ToString () : null), baseUri);
}
static string VoidBaseUriFlag = "&^)(*&%*^$&$VOID!BASE!URI!";
Uri Resolve (string thisUri, string baseUri, XslTransformProcessor p)
{
// Debug.WriteLine ("THIS: " + thisUri);
// Debug.WriteLine ("BASE: " + baseUri);
XmlResolver r = p.Resolver;
if (r == null)
return null;
Uri uriBase = null;
if (! object.ReferenceEquals (baseUri, VoidBaseUriFlag) && baseUri != String.Empty)
uriBase = r.ResolveUri (null, baseUri);
return r.ResolveUri (uriBase, thisUri);
}
XPathNodeIterator GetDocument (XsltCompiledContext xsltContext, XPathNodeIterator itr, string baseUri)
{
ArrayList list = new ArrayList ();
try {
Hashtable got = new Hashtable ();
while (itr.MoveNext()) {
Uri uri = Resolve (itr.Current.Value, baseUri != null ? baseUri : /*itr.Current.BaseURI*/doc.BaseURI, xsltContext.Processor);
if (!got.ContainsKey (uri)) {
got.Add (uri, null);
if (uri != null && uri.ToString () == "") {
XPathNavigator n = doc.Clone ();
n.MoveToRoot ();
list.Add (n);
} else
list.Add (xsltContext.Processor.GetDocument (uri));
}
}
} catch (Exception) {
// Error recovery.
// See http://www.w3.org/TR/xslt#document and
// bug #75663.
list.Clear ();
}
return new ListIterator (list, xsltContext);
}
XPathNodeIterator GetDocument (XsltCompiledContext xsltContext, string arg0, string baseUri)
{
try {
Uri uri = Resolve (arg0, baseUri != null ? baseUri : doc.BaseURI, xsltContext.Processor);
XPathNavigator n;
if (uri != null && uri.ToString () == "") {
n = doc.Clone ();
n.MoveToRoot ();
} else
n = xsltContext.Processor.GetDocument (uri);
return new SelfIterator (n, xsltContext);
} catch (Exception) {
return new ListIterator (new ArrayList (), xsltContext);
}
}
public override string ToString ()
{
return String.Concat ("document(",
arg0.ToString (),
arg1 != null ? "," : String.Empty,
arg1 != null ? arg1.ToString () : String.Empty,
")");
}
}
class XsltElementAvailable : XPathFunction
{
Expression arg0;
IStaticXsltContext ctx;
public XsltElementAvailable (FunctionArguments args, IStaticXsltContext ctx) : base (args)
{
if (args == null || args.Tail != null)
throw new XPathException ("element-available takes 1 arg");
arg0 = args.Arg;
this.ctx = ctx;
}
public override XPathResultType ReturnType { get { return XPathResultType.Boolean; }}
internal override bool Peer {
get { return arg0.Peer; }
}
public override object Evaluate (BaseIterator iter)
{
QName name = XslNameUtil.FromString (arg0.EvaluateString (iter), ctx);
return (
(name.Namespace == Compiler.XsltNamespace) &&
(
//
// A list of all the instructions (does not include top-level-elements)
//
name.Name == "apply-imports" ||
name.Name == "apply-templates" ||
name.Name == "call-template" ||
name.Name == "choose" ||
name.Name == "comment" ||
name.Name == "copy" ||
name.Name == "copy-of" ||
name.Name == "element" ||
name.Name == "fallback" ||
name.Name == "for-each" ||
name.Name == "message" ||
name.Name == "number" ||
name.Name == "processing-instruction" ||
name.Name == "text" ||
name.Name == "value-of" ||
name.Name == "variable"
)
);
}
}
class XsltFormatNumber : XPathFunction
{
Expression arg0, arg1, arg2;
IStaticXsltContext ctx;
public XsltFormatNumber (FunctionArguments args, IStaticXsltContext ctx) : base (args)
{
if (args == null || args.Tail == null || (args.Tail.Tail != null && args.Tail.Tail.Tail != null))
throw new XPathException ("format-number takes 2 or 3 args");
arg0 = args.Arg;
arg1 = args.Tail.Arg;
if (args.Tail.Tail != null) {
arg2= args.Tail.Tail.Arg;
this.ctx = ctx;
}
}
public override XPathResultType ReturnType { get { return XPathResultType.String; }}
internal override bool Peer {
get { return arg0.Peer && arg1.Peer && (arg2 != null ? arg2.Peer : true); }
}
public override object Evaluate (BaseIterator iter)
{
double d = arg0.EvaluateNumber (iter);
string s = arg1.EvaluateString (iter);
QName nm = QName.Empty;
if (arg2 != null)
nm = XslNameUtil.FromString (arg2.EvaluateString (iter), ctx);
try {
return ((XsltCompiledContext) iter.NamespaceManager).Processor.CompiledStyle
.LookupDecimalFormat (nm).FormatNumber (d, s);
} catch (ArgumentException ex) {
throw new XsltException (ex.Message, ex, iter.Current);
}
}
}
class XsltFunctionAvailable : XPathFunction
{
Expression arg0;
IStaticXsltContext ctx;
public XsltFunctionAvailable (FunctionArguments args, IStaticXsltContext ctx) : base (args)
{
if (args == null || args.Tail != null)
throw new XPathException ("element-available takes 1 arg");
arg0 = args.Arg;
this.ctx = ctx;
}
public override XPathResultType ReturnType { get { return XPathResultType.Boolean; }}
internal override bool Peer {
get { return arg0.Peer; }
}
public override object Evaluate (BaseIterator iter)
{
string name = arg0.EvaluateString (iter);
int colon = name.IndexOf (':');
// extension function
if (colon > 0)
return (iter.NamespaceManager as XsltCompiledContext).ResolveFunction (
XslNameUtil.FromString (name, ctx),
null) != null;
return (
//
// XPath
//
name == "boolean" ||
name == "ceiling" ||
name == "concat" ||
name == "contains" ||
name == "count" ||
name == "false" ||
name == "floor" ||
name == "id"||
name == "lang" ||
name == "last" ||
name == "local-name" ||
name == "name" ||
name == "namespace-uri" ||
name == "normalize-space" ||
name == "not" ||
name == "number" ||
name == "position" ||
name == "round" ||
name == "starts-with" ||
name == "string" ||
name == "string-length" ||
name == "substring" ||
name == "substring-after" ||
name == "substring-before" ||
name == "sum" ||
name == "translate" ||
name == "true" ||
// XSLT
name == "document" ||
name == "format-number" ||
name == "function-available" ||
name == "generate-id" ||
name == "key" ||
name == "current" ||
name == "unparsed-entity-uri" ||
name == "element-available" ||
name == "system-property"
);
}
}
class XsltGenerateId : XPathFunction
{
//FIXME: generate short string, not the huge thing it makes now
Expression arg0;
public XsltGenerateId (FunctionArguments args) : base (args)
{
if (args != null) {
if (args.Tail != null)
throw new XPathException ("generate-id takes 1 or no args");
arg0 = args.Arg;
}
}
public override XPathResultType ReturnType { get { return XPathResultType.String; }}
internal override bool Peer {
get { return arg0.Peer; }
}
public override object Evaluate (BaseIterator iter)
{
XPathNavigator n;
if (arg0 != null) {
XPathNodeIterator itr = arg0.EvaluateNodeSet (iter);
if (itr.MoveNext ())
n = itr.Current.Clone ();
else
return string.Empty; // empty nodeset == empty string
} else
n = iter.Current.Clone ();
StringBuilder sb = new StringBuilder ("Mono"); // Ensure begins with alpha
sb.Append (XmlConvert.EncodeLocalName (n.BaseURI));
sb.Replace ('_', 'm'); // remove underscores from EncodeLocalName
sb.Append (n.NodeType);
sb.Append ('m');
do {
sb.Append (IndexInParent (n));
sb.Append ('m');
} while (n.MoveToParent ());
return sb.ToString ();
}
int IndexInParent (XPathNavigator nav)
{
int n = 0;
while (nav.MoveToPrevious ())
n++;
return n;
}
}
class XsltKey : XPathFunction
{
Expression arg0, arg1;
IStaticXsltContext staticContext;
public XsltKey (FunctionArguments args, IStaticXsltContext ctx) : base (args)
{
staticContext = ctx;
if (args == null || args.Tail == null)
throw new XPathException ("key takes 2 args");
arg0 = args.Arg;
arg1 = args.Tail.Arg;
}
public Expression KeyName { get { return arg0; } }
public Expression Field { get { return arg1; } }
public override XPathResultType ReturnType { get { return XPathResultType.NodeSet; }}
internal override bool Peer {
get { return arg0.Peer && arg1.Peer; }
}
public bool PatternMatches (XPathNavigator nav, XsltContext nsmgr)
{
XsltCompiledContext ctx = nsmgr as XsltCompiledContext;
// for key pattern, it must contain literal value
return ctx.MatchesKey (nav, staticContext,
arg0.StaticValueAsString,
arg1.StaticValueAsString);
}
public override object Evaluate (BaseIterator iter)
{
XsltCompiledContext ctx = iter.NamespaceManager
as XsltCompiledContext;
return ctx.EvaluateKey (staticContext, iter, arg0, arg1);
}
}
class XsltSystemProperty : XPathFunction
{
Expression arg0;
IStaticXsltContext ctx;
public XsltSystemProperty (FunctionArguments args, IStaticXsltContext ctx) : base (args)
{
if (args == null || args.Tail != null)
throw new XPathException ("system-property takes 1 arg");
arg0 = args.Arg;
this.ctx = ctx;
}
public override XPathResultType ReturnType { get { return XPathResultType.String; }}
internal override bool Peer {
get { return arg0.Peer; }
}
public override object Evaluate (BaseIterator iter)
{
QName name = XslNameUtil.FromString (arg0.EvaluateString (iter), ctx);
if (name.Namespace == Compiler.XsltNamespace) {
switch (name.Name) {
case "version": return "1.0";
case "vendor": return "Mono";
case "vendor-url": return "http://www.go-mono.com/";
}
}
return "";
}
}
class XsltUnparsedEntityUri : XPathFunction
{
Expression arg0;
public XsltUnparsedEntityUri (FunctionArguments args) : base (args)
{
if (args == null || args.Tail != null)
throw new XPathException ("unparsed-entity-uri takes 1 arg");
arg0 = args.Arg;
}
public override XPathResultType ReturnType { get { return XPathResultType.String; }}
internal override bool Peer {
get { return arg0.Peer; }
}
public override object Evaluate (BaseIterator iter)
{
IHasXmlNode xn = iter.Current as IHasXmlNode;
if (xn == null)
return String.Empty;
XmlNode n = xn.GetNode ();
if (n.OwnerDocument == null)
return String.Empty;
XmlDocumentType doctype = n.OwnerDocument.DocumentType;
if (doctype == null)
return String.Empty;
XmlEntity ent = doctype.Entities.GetNamedItem (arg0.EvaluateString (iter)) as XmlEntity;
if (ent == null)
return String.Empty;
return ent.SystemId != null ? ent.SystemId : String.Empty;
}
}
class MSXslNodeSet : XPathFunction
{
bool strict;
Expression arg0;
public MSXslNodeSet (bool strict, FunctionArguments args) : base (args)
{
if (args == null || args.Tail != null)
throw new XPathException ("element-available takes 1 arg");
this.strict = strict;
arg0 = args.Arg;
}
public override XPathResultType ReturnType {
get {
return XPathResultType.NodeSet;
}
}
internal override bool Peer {
get { return arg0.Peer; }
}
public override object Evaluate (BaseIterator iter)
{
XsltCompiledContext ctx = iter.NamespaceManager as XsltCompiledContext;
XPathNavigator loc = iter.Current != null ? iter.Current.Clone () : null;
object val = arg0.Evaluate (iter);
XPathNavigator nav = val as XPathNavigator;
if (nav == null && !strict) {
var iterResult = val as XPathNodeIterator;
if (iterResult != null)
return iterResult;
var strResult = val as string;
if (strResult == string.Empty) {
DTMXPathDocumentWriter2 w = new DTMXPathDocumentWriter2 (ctx.Processor.Root.NameTable, 10);
nav = w.CreateDocument ().CreateNavigator ();
}
}
if (nav == null) {
if (loc != null)
return new XsltException ("Cannot convert the XPath argument to a result tree fragment.", null, loc);
else
return new XsltException ("Cannot convert the XPath argument to a result tree fragment.", null);
}
ArrayList al = new ArrayList ();
al.Add (nav);
return new ListIterator (al, ctx);
}
}
}