2005-11-17 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / class / System.XML / Mono.Xml.Xsl / XslFunctions.cs
old mode 100755 (executable)
new mode 100644 (file)
index c98902f..cf061cc
@@ -7,8 +7,30 @@
 // (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;
@@ -51,7 +73,7 @@ namespace Mono.Xml.Xsl
                
                public abstract object Invoke (XsltCompiledContext xsltContext, object [] args, XPathNavigator docContext);
                
-               public static XPathResultType GetXPathType (Type type) {
+               public static XPathResultType GetXPathType (Type type, XPathNavigator node) {
                        switch (Type.GetTypeCode(type)) {
                        case TypeCode.String:
                                return XPathResultType.String;
@@ -66,7 +88,7 @@ namespace Mono.Xml.Xsl
                                
                                return XPathResultType.Any;
                        case TypeCode.DateTime :
-                               throw new Exception ();
+                               throw new XsltException ("Invalid type DateTime was specified.", null, node);
                        default: // Numeric
                                return XPathResultType.Number;
                        } 
@@ -79,7 +101,7 @@ namespace Mono.Xml.Xsl
                private MethodInfo method;
                private TypeCode [] typeCodes;
 
-               public XsltExtensionFunction (object extension, MethodInfo method)
+               public XsltExtensionFunction (object extension, MethodInfo method, XPathNavigator currentNode)
                {
                        this.extension = extension;
                        this.method = method;
@@ -94,7 +116,7 @@ namespace Mono.Xml.Xsl
                        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);
+                               argTypes [i] = GetXPathType (parameters [i].ParameterType, currentNode);
                                if (canBeOpt) {
                                        if (parameters[i].IsOptional)
                                                minArgs --;
@@ -102,7 +124,7 @@ namespace Mono.Xml.Xsl
                                                canBeOpt = false;
                                }
                        }
-                       base.Init (minArgs, maxArgs, GetXPathType (method.ReturnType), argTypes);
+                       base.Init (minArgs, maxArgs, GetXPathType (method.ReturnType, currentNode), argTypes);
                }
 
                public override object Invoke (XsltCompiledContext xsltContext, object [] args, XPathNavigator docContext)
@@ -171,6 +193,15 @@ namespace Mono.Xml.Xsl
                {
                        return new SelfIterator ((iter.NamespaceManager as XsltCompiledContext).Processor.CurrentNode, null);
                }
+
+               internal override bool Peer {
+                       get { return false; }
+               }
+
+               public override string ToString ()
+               {
+                       return "current()";
+               }
        }
        
        class XsltDocument : XPathFunction 
@@ -189,7 +220,11 @@ namespace Mono.Xml.Xsl
                        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;
@@ -205,7 +240,7 @@ namespace Mono.Xml.Xsl
                        if (o is XPathNodeIterator)
                                return GetDocument ((iter.NamespaceManager as XsltCompiledContext), (XPathNodeIterator)o, baseUri);
                        else
-                               return GetDocument ((iter.NamespaceManager as XsltCompiledContext), o.ToString (), baseUri);
+                               return GetDocument ((iter.NamespaceManager as XsltCompiledContext), o is IFormattable ? ((IFormattable) o).ToString (null, CultureInfo.InvariantCulture) : o.ToString (), baseUri);
                }
                
                static string VoidBaseUriFlag = "&^)(*&%*^$&$VOID!BASE!URI!";
@@ -215,9 +250,10 @@ namespace Mono.Xml.Xsl
 //                     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))
+                       if (! object.ReferenceEquals (baseUri, VoidBaseUriFlag) && baseUri != String.Empty)
                                uriBase = r.ResolveUri (null, baseUri);
                                
                        return r.ResolveUri (uriBase, thisUri);
@@ -226,42 +262,61 @@ namespace Mono.Xml.Xsl
                XPathNodeIterator GetDocument (XsltCompiledContext xsltContext, XPathNodeIterator itr, string baseUri)
                {
                        ArrayList list = new ArrayList ();
-                       Hashtable got = new Hashtable ();
+                       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.ToString () == "") {
-                                               XPathNavigator n = doc.Clone ();
-                                               n.MoveToRoot ();
-                                               list.Add (n);
-                                       } else
-                                               list.Add (xsltContext.Processor.GetDocument (uri));
+                               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, false);
+                       return new ListIterator (list, xsltContext);
                }
        
                XPathNodeIterator GetDocument (XsltCompiledContext xsltContext, string arg0, string baseUri)
                {
-                       Uri uri = Resolve (arg0, baseUri != null ? baseUri : doc.BaseURI, xsltContext.Processor);
-                       XPathNavigator n;
-                       if (uri.ToString () == "") {
-                               n = doc.Clone ();
-                               n.MoveToRoot ();
-                       } else
-                               n = xsltContext.Processor.GetDocument (uri);
+                       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);
+                               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;
-               XmlNamespaceManager nsm;
+               IStaticXsltContext ctx;
                
                public XsltElementAvailable (FunctionArguments args, IStaticXsltContext ctx) : base (args)
                {
@@ -269,14 +324,18 @@ namespace Mono.Xml.Xsl
                                throw new XPathException ("element-available takes 1 arg");
                        
                        arg0 = args.Arg;
-                       nsm = ctx.GetNsm ();
+                       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), nsm);
+                       QName name = XslNameUtil.FromString (arg0.EvaluateString (iter), ctx);
 
                        return (
                                (name.Namespace == Compiler.XsltNamespace) &&
@@ -308,7 +367,7 @@ namespace Mono.Xml.Xsl
        class XsltFormatNumber : XPathFunction 
        {
                Expression arg0, arg1, arg2;
-               XmlNamespaceManager nsm;
+               IStaticXsltContext ctx;
                
                public XsltFormatNumber (FunctionArguments args, IStaticXsltContext ctx) : base (args)
                {
@@ -319,10 +378,14 @@ namespace Mono.Xml.Xsl
                        arg1 = args.Tail.Arg;
                        if (args.Tail.Tail != null) {
                                arg2= args.Tail.Tail.Arg;
-                               nsm = ctx.GetNsm ();
+                               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)
                {
@@ -331,7 +394,7 @@ namespace Mono.Xml.Xsl
                        QName nm = QName.Empty;
                        
                        if (arg2 != null)
-                               nm = XslNameUtil.FromString (arg2.EvaluateString (iter), nsm);
+                               nm = XslNameUtil.FromString (arg2.EvaluateString (iter), ctx);
                        
                        try {
                                return (iter.NamespaceManager as XsltCompiledContext).Processor.CompiledStyle
@@ -345,7 +408,7 @@ namespace Mono.Xml.Xsl
        class XsltFunctionAvailable : XPathFunction 
        {
                Expression arg0;
-               XmlNamespaceManager nsm;
+               IStaticXsltContext ctx;
                
                public XsltFunctionAvailable (FunctionArguments args, IStaticXsltContext ctx) : base (args)
                {
@@ -353,10 +416,14 @@ namespace Mono.Xml.Xsl
                                throw new XPathException ("element-available takes 1 arg");
                        
                        arg0 = args.Arg;
-                       nsm = ctx.GetNsm ();
+                       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)
                {
@@ -366,7 +433,7 @@ namespace Mono.Xml.Xsl
                        // extension function
                        if (colon > 0)
                                return (iter.NamespaceManager as XsltCompiledContext).ResolveFunction (
-                                       XslNameUtil.FromString (name, nsm),
+                                       XslNameUtil.FromString (name, ctx),
                                        null) != null;
                        
                        return (
@@ -416,6 +483,7 @@ namespace Mono.Xml.Xsl
 
        class XsltGenerateId : XPathFunction 
        {
+               //FIXME: generate short string, not the huge thing it makes now
                Expression arg0;
                public XsltGenerateId (FunctionArguments args) : base (args)
                {
@@ -427,6 +495,11 @@ namespace Mono.Xml.Xsl
                }
                
                public override XPathResultType ReturnType { get { return XPathResultType.String; }}
+
+               internal override bool Peer {
+                       get { return arg0.Peer; }
+               }
+
                public override object Evaluate (BaseIterator iter)
                {
                        XPathNavigator n;
@@ -466,86 +539,45 @@ namespace Mono.Xml.Xsl
        class XsltKey : XPathFunction 
        {
                Expression arg0, arg1;
-               XmlNamespaceManager nsm;
-               XslKey key;
+               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;
-                       nsm = ctx.GetNsm ();
                }
                public Expression KeyName { get { return arg0; } }
                public Expression Field { get { return arg1; } }
-               public XmlNamespaceManager NamespaceManager { get { return nsm; } }
                public override XPathResultType ReturnType { get { return XPathResultType.NodeSet; }}
-               
-               public override object Evaluate (BaseIterator iter)
-               {
-                       QName name = XslNameUtil.FromString (arg0.EvaluateString (iter), nsm);
-                       XsltCompiledContext ctx = iter.NamespaceManager as XsltCompiledContext;
-                       if (key == null)
-                               key = ctx.Processor.CompiledStyle.Style.FindKey (name);
 
-                       ArrayList result = new ArrayList ();
-                       object o = arg1.Evaluate (iter);
-                       XPathNodeIterator it = o as XPathNodeIterator;
-                       
-                       if (it != null) {
-                               while (it.MoveNext())
-                                       FindKeyMatch (ctx, it.Current.Value, result, iter.Current);
-                       } else {
-                               FindKeyMatch (ctx, XPathFunctions.ToString (o), result, iter.Current);
-                       }
-                       
-                       return new ListIterator (result, (iter.NamespaceManager as XsltCompiledContext), true);
+               internal override bool Peer {
+                       get { return arg0.Peer && arg1.Peer; }
                }
-               
-               void FindKeyMatch (XsltCompiledContext xsltContext, string value, ArrayList result, XPathNavigator context)
-               {
-                       XPathNavigator searchDoc = context.Clone ();
-                       searchDoc.MoveToRoot ();
-                       if (key != null) {
-                               XPathNodeIterator desc = searchDoc.SelectDescendants (XPathNodeType.All, true);
-
-                               while (desc.MoveNext ()) {
-                                       if (key.Matches (desc.Current, xsltContext, value))
-                                               AddResult (result, desc.Current);
-                                       
-                                       if (desc.Current.MoveToFirstAttribute ()) {
-                                               do {
-                                                       if (key.Matches (desc.Current, xsltContext, value))
-                                                               AddResult (result, desc.Current);       
-                                               } while (desc.Current.MoveToNextAttribute ());
-                                               
-                                               desc.Current.MoveToParent ();
-                                       }
-                               }
-                       }
+
+               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);
                }
 
-               void AddResult (ArrayList result, XPathNavigator nav)
+               public override object Evaluate (BaseIterator iter)
                {
-                       for (int i = 0; i < result.Count; i++) {
-                               XmlNodeOrder docOrder = nav.ComparePosition (((XPathNavigator)result [i]));
-                               if (docOrder == XmlNodeOrder.Same)
-                                       return;
-                               
-                               if (docOrder == XmlNodeOrder.Before) {
-                                       result.Insert(i, nav.Clone ());
-                                       return;
-                               }
-                       }
-                       result.Add (nav.Clone ());
+                       XsltCompiledContext ctx = iter.NamespaceManager
+                               as XsltCompiledContext;
+                       return ctx.EvaluateKey (staticContext, iter, arg0, arg1);
                }
        }
        
        class XsltSystemProperty : XPathFunction 
        {
                Expression arg0;
-               XmlNamespaceManager nsm;
+               IStaticXsltContext ctx;
                
                public XsltSystemProperty (FunctionArguments args, IStaticXsltContext ctx) : base (args)
                {
@@ -553,13 +585,18 @@ namespace Mono.Xml.Xsl
                                throw new XPathException ("system-property takes 1 arg");
                        
                        arg0 = args.Arg;
-                       nsm = ctx.GetNsm ();
+                       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), nsm);
+                       QName name = XslNameUtil.FromString (arg0.EvaluateString (iter), ctx);
                        
                        if (name.Namespace == Compiler.XsltNamespace) {
                                switch (name.Name) {
@@ -586,20 +623,65 @@ namespace Mono.Xml.Xsl
                }
                
                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;
-                       else
-                               return ent.BaseURI;
+                       return ent.SystemId != null ? ent.SystemId : String.Empty;
+               }
+       }
+
+       class MSXslNodeSet : XPathFunction
+       {
+               Expression arg0;
+
+               public MSXslNodeSet (FunctionArguments args) : base (args)
+               {
+                       if (args == null || args.Tail != null)
+                               throw new XPathException ("element-available takes 1 arg");
+                       
+                       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;
+                       XPathNavigator nav = arg0.EvaluateAs (iter, XPathResultType.Navigator) as XPathNavigator;
+                       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);
                }
        }
 }