1 // UnmanagedXslTransform
\r
4 // Tim Coleman <tim@timcoleman.com>
\r
5 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
6 // Ben Maurer (bmaurer@users.sourceforge.net)
\r
8 // (C) Copyright 2002 Tim Coleman
\r
9 // (c) 2003 Ximian Inc. (http://www.ximian.com)
10 // (C) Ben Maurer 2003
\r
13 // DO NOT MOVE THIS FILE. WE WANT HISTORY
\r
15 using System.Collections;
\r
17 using System.Security.Policy;
\r
19 using System.Runtime.InteropServices;
\r
20 using System.Xml.XPath;
\r
22 using BF = System.Reflection.BindingFlags;
\r
24 namespace System.Xml.Xsl
\r
26 internal unsafe sealed class UnmanagedXslTransform : XslTransformImpl
\r
32 Hashtable extensionObjectCache = new Hashtable();
\r
36 #region Constructors
\r
37 public UnmanagedXslTransform ()
\r
39 stylesheet = IntPtr.Zero;
\r
46 ~UnmanagedXslTransform ()
\r
48 FreeStylesheetIfNeeded ();
\r
51 void FreeStylesheetIfNeeded ()
\r
53 if (stylesheet != IntPtr.Zero) {
\r
54 xsltFreeStylesheet (stylesheet);
\r
55 stylesheet = IntPtr.Zero;
\r
59 public override void Load (string url, XmlResolver resolver)
\r
61 FreeStylesheetIfNeeded ();
\r
62 stylesheet = xsltParseStylesheetFile (url);
\r
64 if (stylesheet == IntPtr.Zero)
\r
65 throw new XmlException ("Error creating stylesheet");
\r
68 public override void Load (XmlReader stylesheet, XmlResolver resolver, Evidence evidence)
70 FreeStylesheetIfNeeded ();
71 // Create a document for the stylesheet
72 XmlDocument doc = new XmlDocument ();
73 doc.Load (stylesheet);
75 // Store the XML in a StringBuilder
76 StringWriter sr = new UTF8StringWriter ();
77 XmlTextWriter writer = new XmlTextWriter (sr);
80 this.stylesheet = GetStylesheetFromString (sr.GetStringBuilder ().ToString ());
82 if (this.stylesheet == IntPtr.Zero)
83 throw new XmlException ("Error creating stylesheet");
86 public override void Load (XPathNavigator stylesheet, XmlResolver resolver, Evidence evidence)
88 FreeStylesheetIfNeeded ();
89 StringWriter sr = new UTF8StringWriter ();
90 Save (stylesheet, sr);
91 this.stylesheet = GetStylesheetFromString (sr.GetStringBuilder ().ToString ());
93 if (this.stylesheet == IntPtr.Zero)
94 throw new XmlException ("Error creating stylesheet");
97 static IntPtr GetStylesheetFromString (string xml)
\r
99 IntPtr result = IntPtr.Zero;
\r
101 IntPtr xmlDoc = xmlParseDoc (xml);
\r
103 if (xmlDoc == IntPtr.Zero) {
\r
105 throw new XmlException ("Error parsing stylesheet");
\r
108 result = xsltParseStylesheetDoc (xmlDoc);
\r
110 if (result == IntPtr.Zero)
\r
111 throw new XmlException ("Error creating stylesheet");
\r
116 IntPtr ApplyStylesheet (IntPtr doc, string[] argArr, Hashtable extobjects)
\r
118 if (stylesheet == IntPtr.Zero)
\r
119 throw new XmlException ("No style sheet!");
\r
123 if (extobjects == null || extobjects.Count == 0) {
\r
124 // If there are no extension objects, use the simple (old) method.
\r
125 result = xsltApplyStylesheet (stylesheet, doc, argArr);
\r
127 // If there are extension objects, create a context and register the functions.
\r
129 IntPtr context = xsltNewTransformContext(stylesheet, doc);
\r
131 if (context == IntPtr.Zero) throw new XmlException("Error creating transformation context.");
\r
134 foreach (string ns in extobjects.Keys) {
\r
135 object ext = extobjects[ns];
\r
137 if (extensionObjectCache.ContainsKey(ext)) {
\r
138 foreach (ExtensionFunctionHolder ef in (ArrayList)extensionObjectCache[ext]) {
\r
139 int ret = xsltRegisterExtFunction(context, ef.name, ef.ns, ef.func);
\r
140 if (ret != 0) throw new XmlException("Could not reregister extension function " + ef.name + " in " + ef.ns);
\r
147 System.Collections.IEnumerable methods;
\r
149 // As an added bonus, if the extension object is a UseStaticMethods object
\r
150 // (defined below), then add the static methods of the specified type.
\r
151 if (ext is UseStaticMethods) {
\r
152 type = ((UseStaticMethods)ext).Type;
\r
153 methods = type.GetMethods(System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public);
\r
157 type = ext.GetType();
\r
158 methods = type.GetMethods();
\r
161 ArrayList functionstocache = new ArrayList();
\r
163 Hashtable alreadyadded = new Hashtable ();
\r
164 foreach (System.Reflection.MethodInfo mi in methods) {
\r
165 if (alreadyadded.ContainsKey(mi.Name)) continue; // don't add twice
\r
166 alreadyadded[mi.Name] = 1;
\r
168 // Simple extension function delegate
\r
169 ExtensionFunction func = new ExtensionFunction(new ReflectedExtensionFunction(type, extsrc, mi.Name).Function);
\r
171 // Delegate for libxslt library call
\r
172 libxsltXPathFunction libfunc = new libxsltXPathFunction(new ExtensionFunctionWrapper(func).Function);
\r
174 int ret = xsltRegisterExtFunction(context, mi.Name, ns, libfunc);
\r
175 if (ret != 0) throw new XmlException("Could not register extension function " + mi.DeclaringType.FullName + "." + mi.Name + " in " + ns);
\r
177 ExtensionFunctionHolder efh;
\r
178 efh.name = mi.Name;
\r
180 efh.func = libfunc;
\r
181 functionstocache.Add(efh);
\r
184 extensionObjectCache[ext] = functionstocache;
\r
190 result = xsltApplyStylesheetUser(stylesheet, doc, argArr, null, IntPtr.Zero, context);
\r
192 xsltFreeTransformContext(context);
\r
197 if (result == IntPtr.Zero)
\r
198 throw new XmlException ("Error applying style sheet");
\r
203 static void Cleanup ()
\r
205 xsltCleanupGlobals ();
\r
206 xmlCleanupParser ();
\r
209 static string GetStringFromDocument (IntPtr doc, IntPtr stylesheet)
\r
211 IntPtr mem = IntPtr.Zero;
\r
214 int res = xsltSaveResultToString (ref mem, ref size, doc,
\r
217 throw new XmlException ("xsltSaveResultToString () failed.");
\r
219 string docStr = Marshal.PtrToStringAnsi (mem, size);
\r
220 Marshal.FreeHGlobal (mem);
\r
224 string ApplyStylesheetAndGetString (IntPtr doc, string[] argArr, Hashtable extobjects)
\r
226 IntPtr xmlOutput = ApplyStylesheet (doc, argArr, extobjects);
\r
227 string strOutput = GetStringFromDocument (xmlOutput, stylesheet);
\r
228 xmlFreeDoc (xmlOutput);
\r
233 IntPtr GetDocumentFromNavigator (XPathNavigator nav)
\r
235 StringWriter sr = new UTF8StringWriter ();
\r
237 IntPtr xmlInput = xmlParseDoc (sr.GetStringBuilder ().ToString ());
\r
238 if (xmlInput == IntPtr.Zero)
\r
239 throw new XmlException ("Error getting XML from input");
\r
244 public override void Transform (XPathNavigator input, XsltArgumentList args, XmlWriter output, XmlResolver resolver)
247 throw new ArgumentNullException ("input");
250 throw new ArgumentNullException ("output");
252 StringWriter writer = new UTF8StringWriter ();
254 IntPtr inputDoc = GetDocumentFromNavigator (input);
255 string[] argArr = null;
256 Hashtable extensionObjects = null;
258 extensionObjects = args.extensionObjects;
259 argArr = new string[args.parameters.Count * 2 + 1];
261 foreach (object key in args.parameters.Keys) {
262 argArr [index++] = key.ToString();
263 object value = args.parameters [key];
264 if (value is Boolean)
265 argArr [index++] = XmlConvert.ToString((bool) value); // FIXME: How to encode it for libxslt?
266 else if (value is Double)
267 argArr [index++] = XmlConvert.ToString((double) value); // FIXME: How to encode infinity's and Nan?
269 argArr [index++] = "'" + value.ToString() + "'"; // FIXME: How to encode "'"?
271 argArr[index] = null;
273 string transform = ApplyStylesheetAndGetString (inputDoc, argArr, extensionObjects);
274 xmlFreeDoc (inputDoc);
276 writer.Write (transform);
279 output.WriteRaw (writer.GetStringBuilder ().ToString ());
282 public override void Transform (XPathNavigator input, XsltArgumentList args, TextWriter output, XmlResolver resolver)
284 Transform(input, args, new XmlTextWriter(output), resolver);
287 public override void Transform(string inputfile, string outputfile, XmlResolver resolver)
289 IntPtr xmlDocument = IntPtr.Zero;
290 IntPtr resultDocument = IntPtr.Zero;
293 xmlDocument = xmlParseFile (inputfile);
294 if (xmlDocument == IntPtr.Zero)
295 throw new XmlException ("Error parsing input file");
297 resultDocument = ApplyStylesheet (xmlDocument, null, null);
299 if (-1 == xsltSaveResultToFilename (outputfile, resultDocument, stylesheet, 0))
300 throw new XmlException ("Error in xsltSaveResultToFilename");
302 if (xmlDocument != IntPtr.Zero)
303 xmlFreeDoc (xmlDocument);
305 if (resultDocument != IntPtr.Zero)
306 xmlFreeDoc (resultDocument);
314 static void Save (XmlReader rdr, TextWriter baseWriter)
\r
316 XmlTextWriter writer = new XmlTextWriter (baseWriter);
\r
318 while (rdr.Read ()) {
\r
319 switch (rdr.NodeType) {
\r
321 case XmlNodeType.CDATA:
\r
322 writer.WriteCData (rdr.Value);
\r
325 case XmlNodeType.Comment:
\r
326 writer.WriteComment (rdr.Value);
\r
329 case XmlNodeType.DocumentType:
\r
330 writer.WriteDocType (rdr.Value, null, null, null);
\r
333 case XmlNodeType.Element:
\r
334 writer.WriteStartElement (rdr.Name, rdr.Value);
\r
336 while (rdr.MoveToNextAttribute ())
\r
337 writer.WriteAttributes (rdr, true);
\r
340 case XmlNodeType.EndElement:
\r
341 writer.WriteEndElement ();
\r
344 case XmlNodeType.ProcessingInstruction:
\r
345 writer.WriteProcessingInstruction (rdr.Name, rdr.Value);
\r
348 case XmlNodeType.Text:
\r
349 writer.WriteString (rdr.Value);
\r
352 case XmlNodeType.Whitespace:
\r
353 writer.WriteWhitespace (rdr.Value);
\r
356 case XmlNodeType.XmlDeclaration:
\r
357 writer.WriteStartDocument ();
\r
365 static void Save (XPathNavigator navigator, TextWriter writer)
\r
367 XmlTextWriter xmlWriter = new XmlTextWriter (writer);
\r
369 WriteTree (navigator, xmlWriter);
\r
370 xmlWriter.WriteEndDocument ();
\r
371 xmlWriter.Flush ();
\r
374 // Walks the XPathNavigator tree recursively
\r
375 static void WriteTree (XPathNavigator navigator, XmlTextWriter writer)
\r
377 WriteCurrentNode (navigator, writer);
\r
379 if (navigator.MoveToFirstNamespace (XPathNamespaceScope.Local)) {
\r
381 WriteCurrentNode (navigator, writer);
\r
382 } while (navigator.MoveToNextNamespace (XPathNamespaceScope.Local));
\r
384 navigator.MoveToParent ();
\r
387 if (navigator.MoveToFirstAttribute ()) {
\r
389 WriteCurrentNode (navigator, writer);
\r
390 } while (navigator.MoveToNextAttribute ());
\r
392 navigator.MoveToParent ();
\r
395 if (navigator.MoveToFirstChild ()) {
\r
397 WriteTree (navigator, writer);
\r
398 } while (navigator.MoveToNext ());
\r
400 navigator.MoveToParent ();
\r
401 if (navigator.NodeType != XPathNodeType.Root)
\r
402 writer.WriteEndElement ();
\r
403 } else if (navigator.NodeType == XPathNodeType.Element) {
\r
404 writer.WriteEndElement ();
\r
408 // Format the output
\r
409 static void WriteCurrentNode (XPathNavigator navigator, XmlTextWriter writer)
\r
411 switch (navigator.NodeType) {
\r
412 case XPathNodeType.Root:
\r
413 writer.WriteStartDocument ();
\r
415 case XPathNodeType.Namespace:
\r
416 if (navigator.Name == String.Empty)
\r
417 writer.WriteAttributeString ("xmlns", navigator.Value);
\r
419 writer.WriteAttributeString ("xmlns",
\r
421 "http://www.w3.org/2000/xmlns/",
\r
424 case XPathNodeType.Attribute:
\r
425 writer.WriteAttributeString (navigator.Name, navigator.Value);
\r
428 case XPathNodeType.Comment:
\r
429 writer.WriteComment (navigator.Value);
\r
432 case XPathNodeType.Element:
\r
433 writer.WriteStartElement (navigator.Name);
\r
436 case XPathNodeType.ProcessingInstruction:
\r
437 writer.WriteProcessingInstruction (navigator.Name, navigator.Value);
\r
440 case XPathNodeType.Text:
\r
441 writer.WriteString (navigator.Value);
\r
444 case XPathNodeType.SignificantWhitespace:
\r
445 case XPathNodeType.Whitespace:
\r
446 writer.WriteWhitespace (navigator.Value);
\r
451 // Extension Objects
\r
453 internal delegate object ExtensionFunction(object[] args);
\r
455 private struct ExtensionFunctionHolder {
\r
456 public libxsltXPathFunction func;
\r
457 public string ns, name;
\r
460 // Wraps an ExtensionFunction into a function that is callable from the libxslt library.
\r
461 private unsafe class ExtensionFunctionWrapper {
\r
462 private readonly ExtensionFunction func;
\r
464 public ExtensionFunctionWrapper(ExtensionFunction func) {
\r
465 if ((object)func == null) throw new ArgumentNullException("func");
\r
469 public unsafe void Function(IntPtr xpath_ctxt, int nargs) {
\r
470 // Convert XPath arguments into "managed" arguments
\r
471 System.Collections.ArrayList args = new System.Collections.ArrayList();
\r
472 for (int i = 0; i < nargs; i++) {
\r
473 xpathobject* aptr = valuePop(xpath_ctxt);
\r
474 if (aptr->type == 2) // Booleans
\r
475 args.Add( xmlXPathCastToBoolean(aptr) == 0 ? false : true );
\r
476 else if (aptr->type == 3) // Doubles
\r
477 args.Add( xmlXPathCastToNumber(aptr));
\r
478 else if (aptr->type == 4) // Strings
\r
479 args.Add( xmlXPathCastToString(aptr));
\r
480 else if (aptr->type == 1 && aptr->nodesetptr != null) { // Node Sets ==> ArrayList of strings
\r
481 System.Collections.ArrayList a = new System.Collections.ArrayList();
\r
482 for (int ni = 0; ni < aptr->nodesetptr->count; ni++) {
\r
483 xpathobject *n = xmlXPathNewNodeSet(aptr->nodesetptr->nodes[ni]);
\r
484 valuePush(xpath_ctxt, n);
\r
485 xmlXPathStringFunction(xpath_ctxt, 1);
\r
486 a.Add(xmlXPathCastToString(valuePop(xpath_ctxt)));
\r
487 xmlXPathFreeObject(n);
\r
490 } else { // Anything else => string
\r
491 valuePush(xpath_ctxt, aptr);
\r
492 xmlXPathStringFunction(xpath_ctxt, 1);
\r
493 args.Add(xmlXPathCastToString(valuePop(xpath_ctxt)));
\r
496 xmlXPathFreeObject(aptr);
\r
501 object ret = func(args.ToArray());
\r
503 // Convert the result back to an XPath object
\r
504 if (ret == null) // null => ""
\r
505 valuePush(xpath_ctxt, xmlXPathNewCString(""));
\r
506 else if (ret is bool) // Booleans
\r
507 valuePush(xpath_ctxt, xmlXPathNewBoolean((bool)ret ? 1 : 0));
\r
508 else if (ret is int || ret is long || ret is double || ret is float || ret is decimal)
\r
510 valuePush(xpath_ctxt, xmlXPathNewFloat((double)ret));
\r
511 else // Everything else => String
\r
512 valuePush(xpath_ctxt, xmlXPathNewCString(ret.ToString()));
\r
517 // Provides a delegate for calling a late-bound method of a type with a given name.
\r
518 // Determines method based on types of arguments.
\r
519 private class ReflectedExtensionFunction {
\r
524 public ReflectedExtensionFunction(System.Type type, object src, string methodname) { this.type = type; this.src = src; this.methodname = methodname; }
\r
526 public object Function(object[] args) {
\r
527 // Construct arg type array, and a stringified version in case of problem
\r
528 System.Type[] argtypes = new System.Type[args.Length];
\r
529 string argtypelist = null;
\r
530 for (int i = 0; i < args.Length; i++) {
\r
531 argtypes[i] = (args[i] == null ? typeof(object) : args[i].GetType() );
\r
533 if (argtypelist != null) argtypelist += ", ";
\r
534 argtypelist += argtypes[i].FullName;
\r
536 if (argtypelist == null) argtypelist = "";
\r
539 System.Reflection.MethodInfo mi = type.GetMethod(methodname, (src == null ? BF.Static : BF.Instance | BF.Static) | BF.Public, null, argtypes, null);
\r
542 if (mi == null) throw new XmlException("No applicable function for " + methodname + " takes (" + argtypelist + ")");
\r
544 if (!mi.IsStatic && src == null) throw new XmlException("Attempt to call static method without instantiated extension object.");
\r
547 return mi.Invoke(src, args);
\r
551 // Special Mono-specific class that allows static methods of a type to
\r
552 // be bound without needing an instance of that type. Useful for
\r
553 // registering System.Math functions, for example.
\r
554 // Usage: args.AddExtensionObject( new XslTransform.UseStaticMethods(typeof(thetype)) );
\r
555 public sealed class UseStaticMethods {
\r
556 public readonly System.Type Type;
\r
557 public UseStaticMethods(System.Type Type) { this.Type = Type; }
\r
562 #region Calls to external libraries
\r
564 [DllImport ("xslt")]
\r
565 static extern IntPtr xsltParseStylesheetFile (string filename);
\r
567 [DllImport ("xslt")]
\r
568 static extern IntPtr xsltParseStylesheetDoc (IntPtr docPtr);
\r
570 [DllImport ("xslt")]
\r
571 static extern IntPtr xsltApplyStylesheet (IntPtr stylePtr, IntPtr DocPtr, string[] argPtr);
\r
573 [DllImport ("xslt")]
\r
574 static extern int xsltSaveResultToString (ref IntPtr stringPtr, ref int stringLen,
\r
575 IntPtr docPtr, IntPtr stylePtr);
\r
577 [DllImport ("xslt")]
\r
578 static extern int xsltSaveResultToFilename (string URI, IntPtr doc, IntPtr styleSheet, int compression);
\r
580 [DllImport ("xslt")]
\r
581 static extern void xsltCleanupGlobals ();
\r
583 [DllImport ("xslt")]
\r
584 static extern void xsltFreeStylesheet (IntPtr cur);
\r
587 [DllImport ("xml2")]
\r
588 static extern IntPtr xmlNewDoc (string version);
\r
590 [DllImport ("xml2")]
\r
591 static extern int xmlSaveFile (string filename, IntPtr cur);
\r
593 [DllImport ("xml2")]
\r
594 static extern IntPtr xmlParseFile (string filename);
\r
596 [DllImport ("xml2")]
\r
597 static extern IntPtr xmlParseDoc (string document);
\r
599 [DllImport ("xml2")]
\r
600 static extern void xmlFreeDoc (IntPtr doc);
\r
602 [DllImport ("xml2")]
\r
603 static extern void xmlCleanupParser ();
\r
605 [DllImport ("xml2")]
\r
606 static extern void xmlDocDumpMemory (IntPtr doc, ref IntPtr mem, ref int size);
\r
608 [DllImport ("xml2")]
\r
609 static extern void xmlFree (IntPtr data);
\r
611 // Functions and structures for extension objects
\r
613 [DllImport ("xslt")]
\r
614 static extern IntPtr xsltNewTransformContext (IntPtr style, IntPtr doc);
\r
616 [DllImport ("xslt")]
\r
617 static extern void xsltFreeTransformContext (IntPtr context);
\r
619 [DllImport ("xslt")]
\r
620 static extern IntPtr xsltApplyStylesheetUser (IntPtr stylePtr, IntPtr DocPtr, string[] argPtr, string output, IntPtr profile, IntPtr context);
\r
622 [DllImport ("xslt")]
\r
623 static extern int xsltRegisterExtFunction (IntPtr context, string name, string uri, libxsltXPathFunction function);
\r
625 [DllImport ("xml2")]
\r
626 unsafe static extern xpathobject* valuePop (IntPtr context);
\r
628 [DllImport ("xml2")]
\r
629 unsafe static extern void valuePush (IntPtr context, xpathobject* data);
\r
631 [DllImport("xml2")]
\r
632 unsafe static extern void xmlXPathFreeObject(xpathobject* obj);
\r
634 [DllImport("xml2")]
\r
635 unsafe static extern xpathobject* xmlXPathNewCString(string str);
\r
637 [DllImport("xml2")]
\r
638 unsafe static extern xpathobject* xmlXPathNewFloat(double val);
\r
640 [DllImport("xml2")]
\r
641 unsafe static extern xpathobject* xmlXPathNewBoolean(int val);
\r
643 [DllImport("xml2")]
\r
644 unsafe static extern xpathobject* xmlXPathNewNodeSet(IntPtr nodeptr);
\r
646 [DllImport("xml2")]
\r
647 unsafe static extern int xmlXPathCastToBoolean(xpathobject* val);
\r
649 [DllImport("xml2")]
\r
650 unsafe static extern double xmlXPathCastToNumber(xpathobject* val);
\r
652 [DllImport("xml2")]
\r
653 unsafe static extern string xmlXPathCastToString(xpathobject* val);
\r
655 [DllImport("xml2")]
\r
656 static extern void xmlXPathStringFunction(IntPtr context, int nargs);
\r
658 private delegate void libxsltXPathFunction(IntPtr xpath_ctxt, int nargs);
\r
660 private struct xpathobject {
\r
662 public xmlnodelist* nodesetptr;
\r
664 private struct xmlnodelist {
\r
666 public int allocated;
\r
667 public IntPtr* nodes;
\r
672 // This classes just makes the base class use 'encoding="utf-8"'
\r
673 class UTF8StringWriter : StringWriter
\r
675 static Encoding encoding = new UTF8Encoding (false);
\r
677 public override Encoding Encoding {
\r