// System.Xml.Xsl.XslTransform // // Authors: // Tim Coleman // Gonzalo Paniagua Javier (gonzalo@ximian.com) // // (C) Copyright 2002 Tim Coleman // (c) 2003 Ximian Inc. (http://www.ximian.com) // using System; using System.Collections; using System.IO; using System.Text; using System.Runtime.InteropServices; using System.Xml.XPath; using BF = System.Reflection.BindingFlags; namespace System.Xml.Xsl { public unsafe sealed class XslTransform { #region Fields XmlResolver xmlResolver; IntPtr stylesheet; Hashtable extensionObjectCache = new Hashtable(); #endregion #region Constructors public XslTransform () { stylesheet = IntPtr.Zero; } #endregion #region Properties public XmlResolver XmlResolver { set { xmlResolver = value; } } #endregion #region Methods ~XslTransform () { FreeStylesheetIfNeeded (); } void FreeStylesheetIfNeeded () { if (stylesheet != IntPtr.Zero) { xsltFreeStylesheet (stylesheet); stylesheet = IntPtr.Zero; } } // Loads the XSLT stylesheet contained in the IXPathNavigable. public void Load (IXPathNavigable stylesheet) { Load (stylesheet.CreateNavigator ()); } // Loads the XSLT stylesheet specified by a URL. public void Load (string url) { if (url == null) throw new ArgumentNullException ("url"); FreeStylesheetIfNeeded (); stylesheet = xsltParseStylesheetFile (url); Cleanup (); if (stylesheet == IntPtr.Zero) throw new XmlException ("Error creating stylesheet"); } static IntPtr GetStylesheetFromString (string xml) { IntPtr result = IntPtr.Zero; IntPtr xmlDoc = xmlParseDoc (xml); if (xmlDoc == IntPtr.Zero) { Cleanup (); throw new XmlException ("Error parsing stylesheet"); } result = xsltParseStylesheetDoc (xmlDoc); Cleanup (); if (result == IntPtr.Zero) throw new XmlException ("Error creating stylesheet"); return result; } // Loads the XSLT stylesheet contained in the XmlReader public void Load (XmlReader stylesheet) { FreeStylesheetIfNeeded (); // Create a document for the stylesheet XmlDocument doc = new XmlDocument (); doc.Load (stylesheet); // Store the XML in a StringBuilder StringWriter sr = new UTF8StringWriter (); XmlTextWriter writer = new XmlTextWriter (sr); doc.Save (writer); this.stylesheet = GetStylesheetFromString (sr.GetStringBuilder ().ToString ()); Cleanup (); if (this.stylesheet == IntPtr.Zero) throw new XmlException ("Error creating stylesheet"); } // Loads the XSLT stylesheet contained in the XPathNavigator public void Load (XPathNavigator stylesheet) { FreeStylesheetIfNeeded (); StringWriter sr = new UTF8StringWriter (); Save (stylesheet, sr); this.stylesheet = GetStylesheetFromString (sr.GetStringBuilder ().ToString ()); Cleanup (); if (this.stylesheet == IntPtr.Zero) throw new XmlException ("Error creating stylesheet"); } [MonoTODO("use the resolver")] // Loads the XSLT stylesheet contained in the IXPathNavigable. public void Load (IXPathNavigable stylesheet, XmlResolver resolver) { Load (stylesheet); } [MonoTODO("use the resolver")] // Loads the XSLT stylesheet specified by a URL. public void Load (string url, XmlResolver resolver) { Load (url); } [MonoTODO("use the resolver")] // Loads the XSLT stylesheet contained in the XmlReader public void Load (XmlReader stylesheet, XmlResolver resolver) { Load (stylesheet); } [MonoTODO("use the resolver")] // Loads the XSLT stylesheet contained in the XPathNavigator public void Load (XPathNavigator stylesheet, XmlResolver resolver) { Load (stylesheet); } // Transforms the XML data in the IXPathNavigable using // the specified args and outputs the result to an XmlReader. public XmlReader Transform (IXPathNavigable input, XsltArgumentList args) { if (input == null) throw new ArgumentNullException ("input"); return Transform (input.CreateNavigator (), args); } // Transforms the XML data in the input file and outputs // the result to an output file. public void Transform (string inputfile, string outputfile) { IntPtr xmlDocument = IntPtr.Zero; IntPtr resultDocument = IntPtr.Zero; try { xmlDocument = xmlParseFile (inputfile); if (xmlDocument == IntPtr.Zero) throw new XmlException ("Error parsing input file"); resultDocument = ApplyStylesheet (xmlDocument, null, null); /* * If I do this, the type == 2) // Booleans args.Add( xmlXPathCastToBoolean(aptr) == 0 ? false : true ); else if (aptr->type == 3) // Doubles args.Add( xmlXPathCastToNumber(aptr)); else if (aptr->type == 4) // Strings args.Add( xmlXPathCastToString(aptr)); else if (aptr->type == 1 && aptr->nodesetptr != null) { // Node Sets ==> ArrayList of strings System.Collections.ArrayList a = new System.Collections.ArrayList(); for (int ni = 0; ni < aptr->nodesetptr->count; ni++) { xpathobject *n = xmlXPathNewNodeSet(aptr->nodesetptr->nodes[ni]); valuePush(xpath_ctxt, n); xmlXPathStringFunction(xpath_ctxt, 1); a.Add(xmlXPathCastToString(valuePop(xpath_ctxt))); xmlXPathFreeObject(n); } args.Add(a); } else { // Anything else => string valuePush(xpath_ctxt, aptr); xmlXPathStringFunction(xpath_ctxt, 1); args.Add(xmlXPathCastToString(valuePop(xpath_ctxt))); } xmlXPathFreeObject(aptr); } args.Reverse(); object ret = func(args.ToArray()); // Convert the result back to an XPath object if (ret == null) // null => "" valuePush(xpath_ctxt, xmlXPathNewCString("")); else if (ret is bool) // Booleans valuePush(xpath_ctxt, xmlXPathNewBoolean((bool)ret ? 1 : 0)); else if (ret is int || ret is long || ret is double || ret is float || ret is decimal) // Numbers valuePush(xpath_ctxt, xmlXPathNewFloat((double)ret)); else // Everything else => String valuePush(xpath_ctxt, xmlXPathNewCString(ret.ToString())); } } // Provides a delegate for calling a late-bound method of a type with a given name. // Determines method based on types of arguments. private class ReflectedExtensionFunction { System.Type type; object src; string methodname; public ReflectedExtensionFunction(System.Type type, object src, string methodname) { this.type = type; this.src = src; this.methodname = methodname; } public object Function(object[] args) { // Construct arg type array, and a stringified version in case of problem System.Type[] argtypes = new System.Type[args.Length]; string argtypelist = null; for (int i = 0; i < args.Length; i++) { argtypes[i] = (args[i] == null ? typeof(object) : args[i].GetType() ); if (argtypelist != null) argtypelist += ", "; argtypelist += argtypes[i].FullName; } if (argtypelist == null) argtypelist = ""; // Find the method System.Reflection.MethodInfo mi = type.GetMethod(methodname, (src == null ? BF.Static : BF.Instance | BF.Static) | BF.Public, null, argtypes, null); // No method? if (mi == null) throw new XmlException("No applicable function for " + methodname + " takes (" + argtypelist + ")"); if (!mi.IsStatic && src == null) throw new XmlException("Attempt to call static method without instantiated extension object."); // Invoke return mi.Invoke(src, args); } } // Special Mono-specific class that allows static methods of a type to // be bound without needing an instance of that type. Useful for // registering System.Math functions, for example. // Usage: args.AddExtensionObject( new XslTransform.UseStaticMethods(typeof(thetype)) ); public sealed class UseStaticMethods { public readonly System.Type Type; public UseStaticMethods(System.Type Type) { this.Type = Type; } } #endregion #region Calls to external libraries // libxslt [DllImport ("xslt")] static extern IntPtr xsltParseStylesheetFile (string filename); [DllImport ("xslt")] static extern IntPtr xsltParseStylesheetDoc (IntPtr docPtr); [DllImport ("xslt")] static extern IntPtr xsltApplyStylesheet (IntPtr stylePtr, IntPtr DocPtr, string[] argPtr); [DllImport ("xslt")] static extern int xsltSaveResultToString (ref IntPtr stringPtr, ref int stringLen, IntPtr docPtr, IntPtr stylePtr); [DllImport ("xslt")] static extern int xsltSaveResultToFilename (string URI, IntPtr doc, IntPtr styleSheet, int compression); [DllImport ("xslt")] static extern void xsltCleanupGlobals (); [DllImport ("xslt")] static extern void xsltFreeStylesheet (IntPtr cur); // libxml2 [DllImport ("xml2")] static extern IntPtr xmlNewDoc (string version); [DllImport ("xml2")] static extern int xmlSaveFile (string filename, IntPtr cur); [DllImport ("xml2")] static extern IntPtr xmlParseFile (string filename); [DllImport ("xml2")] static extern IntPtr xmlParseDoc (string document); [DllImport ("xml2")] static extern void xmlFreeDoc (IntPtr doc); [DllImport ("xml2")] static extern void xmlCleanupParser (); [DllImport ("xml2")] static extern void xmlDocDumpMemory (IntPtr doc, ref IntPtr mem, ref int size); [DllImport ("xml2")] static extern void xmlFree (IntPtr data); // Functions and structures for extension objects [DllImport ("xslt")] static extern IntPtr xsltNewTransformContext (IntPtr style, IntPtr doc); [DllImport ("xslt")] static extern void xsltFreeTransformContext (IntPtr context); [DllImport ("xslt")] static extern IntPtr xsltApplyStylesheetUser (IntPtr stylePtr, IntPtr DocPtr, string[] argPtr, string output, IntPtr profile, IntPtr context); [DllImport ("xslt")] static extern int xsltRegisterExtFunction (IntPtr context, string name, string uri, libxsltXPathFunction function); [DllImport ("xml2")] unsafe static extern xpathobject* valuePop (IntPtr context); [DllImport ("xml2")] unsafe static extern void valuePush (IntPtr context, xpathobject* data); [DllImport("xml2")] unsafe static extern void xmlXPathFreeObject(xpathobject* obj); [DllImport("xml2")] unsafe static extern xpathobject* xmlXPathNewCString(string str); [DllImport("xml2")] unsafe static extern xpathobject* xmlXPathNewFloat(double val); [DllImport("xml2")] unsafe static extern xpathobject* xmlXPathNewBoolean(int val); [DllImport("xml2")] unsafe static extern xpathobject* xmlXPathNewNodeSet(IntPtr nodeptr); [DllImport("xml2")] unsafe static extern int xmlXPathCastToBoolean(xpathobject* val); [DllImport("xml2")] unsafe static extern double xmlXPathCastToNumber(xpathobject* val); [DllImport("xml2")] unsafe static extern string xmlXPathCastToString(xpathobject* val); [DllImport("xml2")] static extern void xmlXPathStringFunction(IntPtr context, int nargs); private delegate void libxsltXPathFunction(IntPtr xpath_ctxt, int nargs); private struct xpathobject { public int type; public xmlnodelist* nodesetptr; } private struct xmlnodelist { public int count; public int allocated; public IntPtr* nodes; } #endregion // This classes just makes the base class use 'encoding="utf-8"' class UTF8StringWriter : StringWriter { static Encoding encoding = new UTF8Encoding (false); public override Encoding Encoding { get { return encoding; } } } } }