**** Merged from MCS ****
[mono-project.git] / mcs / class / System.XML / System.Xml.Xsl / XslTransform.cs
blob1aead131cd88827b24322efa8e3f17af48c56ca1
1 // UnmanagedXslTransform
2 //
3 // Authors:
4 // Tim Coleman <tim@timcoleman.com>
5 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
6 // Ben Maurer (bmaurer@users.sourceforge.net)
7 //
8 // (C) Copyright 2002 Tim Coleman
9 // (c) 2003 Ximian Inc. (http://www.ximian.com)
10 // (C) Ben Maurer 2003
14 // Permission is hereby granted, free of charge, to any person obtaining
15 // a copy of this software and associated documentation files (the
16 // "Software"), to deal in the Software without restriction, including
17 // without limitation the rights to use, copy, modify, merge, publish,
18 // distribute, sublicense, and/or sell copies of the Software, and to
19 // permit persons to whom the Software is furnished to do so, subject to
20 // the following conditions:
21 //
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
24 //
25 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
26 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 // DO NOT MOVE THIS FILE. WE WANT HISTORY
35 using System;
36 using System.Collections;
37 using System.IO;
38 using System.Security.Policy;
39 using System.Text;
40 using System.Runtime.InteropServices;
41 using System.Xml.XPath;
43 using BF = System.Reflection.BindingFlags;
45 namespace System.Xml.Xsl
47 internal unsafe sealed class UnmanagedXslTransform : XslTransformImpl
50 #region Fields
52 IntPtr stylesheet;
53 Hashtable extensionObjectCache = new Hashtable();
55 #endregion
57 #region Constructors
58 public UnmanagedXslTransform ()
60 stylesheet = IntPtr.Zero;
63 #endregion
65 #region Methods
67 ~UnmanagedXslTransform ()
69 FreeStylesheetIfNeeded ();
72 void FreeStylesheetIfNeeded ()
74 if (stylesheet != IntPtr.Zero) {
75 xsltFreeStylesheet (stylesheet);
76 stylesheet = IntPtr.Zero;
80 public override void Load (string url, XmlResolver resolver)
82 FreeStylesheetIfNeeded ();
83 stylesheet = xsltParseStylesheetFile (url);
84 Cleanup ();
85 if (stylesheet == IntPtr.Zero)
86 throw new XmlException ("Error creating stylesheet");
89 public override void Load (XmlReader stylesheet, XmlResolver resolver, Evidence evidence)
91 FreeStylesheetIfNeeded ();
92 // Create a document for the stylesheet
93 XmlDocument doc = new XmlDocument ();
94 doc.Load (stylesheet);
96 // Store the XML in a StringBuilder
97 StringWriter sr = new UTF8StringWriter ();
98 XmlTextWriter writer = new XmlTextWriter (sr);
99 doc.Save (writer);
101 this.stylesheet = GetStylesheetFromString (sr.GetStringBuilder ().ToString ());
102 Cleanup ();
103 if (this.stylesheet == IntPtr.Zero)
104 throw new XmlException ("Error creating stylesheet");
107 public override void Load (XPathNavigator stylesheet, XmlResolver resolver, Evidence evidence)
109 FreeStylesheetIfNeeded ();
110 StringWriter sr = new UTF8StringWriter ();
111 Save (stylesheet, sr);
112 this.stylesheet = GetStylesheetFromString (sr.GetStringBuilder ().ToString ());
113 Cleanup ();
114 if (this.stylesheet == IntPtr.Zero)
115 throw new XmlException ("Error creating stylesheet");
118 static IntPtr GetStylesheetFromString (string xml)
120 IntPtr result = IntPtr.Zero;
122 IntPtr xmlDoc = xmlParseDoc (xml);
124 if (xmlDoc == IntPtr.Zero) {
125 Cleanup ();
126 throw new XmlException ("Error parsing stylesheet");
129 result = xsltParseStylesheetDoc (xmlDoc);
130 Cleanup ();
131 if (result == IntPtr.Zero)
132 throw new XmlException ("Error creating stylesheet");
134 return result;
137 IntPtr ApplyStylesheet (IntPtr doc, string[] argArr, Hashtable extobjects)
139 if (stylesheet == IntPtr.Zero)
140 throw new XmlException ("No style sheet!");
142 IntPtr result;
144 if (extobjects == null || extobjects.Count == 0) {
145 // If there are no extension objects, use the simple (old) method.
146 result = xsltApplyStylesheet (stylesheet, doc, argArr);
147 } else {
148 // If there are extension objects, create a context and register the functions.
150 IntPtr context = xsltNewTransformContext(stylesheet, doc);
152 if (context == IntPtr.Zero) throw new XmlException("Error creating transformation context.");
154 try {
155 foreach (string ns in extobjects.Keys) {
156 object ext = extobjects[ns];
158 if (extensionObjectCache.ContainsKey(ext)) {
159 foreach (ExtensionFunctionHolder ef in (ArrayList)extensionObjectCache[ext]) {
160 int ret = xsltRegisterExtFunction(context, ef.name, ef.ns, ef.func);
161 if (ret != 0) throw new XmlException("Could not reregister extension function " + ef.name + " in " + ef.ns);
164 } else {
165 object extsrc;
167 System.Type type;
168 System.Collections.IEnumerable methods;
170 // As an added bonus, if the extension object is a UseStaticMethods object
171 // (defined below), then add the static methods of the specified type.
172 if (ext is UseStaticMethods) {
173 type = ((UseStaticMethods)ext).Type;
174 methods = type.GetMethods(System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public);
175 extsrc = null;
176 } else {
177 extsrc = ext;
178 type = ext.GetType();
179 methods = type.GetMethods();
182 ArrayList functionstocache = new ArrayList();
184 Hashtable alreadyadded = new Hashtable ();
185 foreach (System.Reflection.MethodInfo mi in methods) {
186 if (alreadyadded.ContainsKey(mi.Name)) continue; // don't add twice
187 alreadyadded[mi.Name] = 1;
189 // Simple extension function delegate
190 ExtensionFunction func = new ExtensionFunction(new ReflectedExtensionFunction(type, extsrc, mi.Name).Function);
192 // Delegate for libxslt library call
193 libxsltXPathFunction libfunc = new libxsltXPathFunction(new ExtensionFunctionWrapper(func).Function);
195 int ret = xsltRegisterExtFunction(context, mi.Name, ns, libfunc);
196 if (ret != 0) throw new XmlException("Could not register extension function " + mi.DeclaringType.FullName + "." + mi.Name + " in " + ns);
198 ExtensionFunctionHolder efh;
199 efh.name = mi.Name;
200 efh.ns = ns;
201 efh.func = libfunc;
202 functionstocache.Add(efh);
205 extensionObjectCache[ext] = functionstocache;
211 result = xsltApplyStylesheetUser(stylesheet, doc, argArr, null, IntPtr.Zero, context);
212 } finally {
213 xsltFreeTransformContext(context);
218 if (result == IntPtr.Zero)
219 throw new XmlException ("Error applying style sheet");
221 return result;
224 static void Cleanup ()
226 //xsltCleanupGlobals ();
227 //xmlCleanupParser ();
230 static string GetStringFromDocument (IntPtr doc, IntPtr stylesheet)
232 IntPtr mem = IntPtr.Zero;
233 int size = 0;
235 int res = xsltSaveResultToString (ref mem, ref size, doc,
236 stylesheet);
237 if (res == -1)
238 throw new XmlException ("xsltSaveResultToString () failed.");
240 string docStr = Marshal.PtrToStringAnsi (mem, size);
241 Marshal.FreeHGlobal (mem);
242 return docStr;
245 string ApplyStylesheetAndGetString (IntPtr doc, string[] argArr, Hashtable extobjects)
247 IntPtr xmlOutput = ApplyStylesheet (doc,
248 argArr == null ? new string [0] : argArr,
249 extobjects == null ? new Hashtable () : extobjects);
250 string strOutput = GetStringFromDocument (xmlOutput, stylesheet);
251 xmlFreeDoc (xmlOutput);
253 return strOutput;
256 IntPtr GetDocumentFromNavigator (XPathNavigator nav)
258 StringWriter sr = new UTF8StringWriter ();
259 Save (nav, sr);
260 IntPtr xmlInput = xmlParseDoc (sr.GetStringBuilder ().ToString ());
261 if (xmlInput == IntPtr.Zero)
262 throw new XmlException ("Error getting XML from input");
264 return xmlInput;
267 public override void Transform (XPathNavigator input, XsltArgumentList args, XmlWriter output, XmlResolver resolver)
269 if (input == null)
270 throw new ArgumentNullException ("input");
272 if (output == null)
273 throw new ArgumentNullException ("output");
275 StringWriter writer = new UTF8StringWriter ();
277 IntPtr inputDoc = GetDocumentFromNavigator (input);
278 string[] argArr = null;
279 Hashtable extensionObjects = null;
280 if (args != null) {
281 extensionObjects = args.extensionObjects;
282 argArr = new string[args.parameters.Count * 2 + 1];
283 int index = 0;
284 foreach (object key in args.parameters.Keys) {
285 argArr [index++] = key.ToString();
286 object value = args.parameters [key];
287 if (value is Boolean)
288 argArr [index++] = XmlConvert.ToString((bool) value); // FIXME: How to encode it for libxslt?
289 else if (value is Double)
290 argArr [index++] = XmlConvert.ToString((double) value); // FIXME: How to encode infinity's and Nan?
291 else
292 argArr [index++] = "'" + value.ToString() + "'"; // FIXME: How to encode "'"?
294 argArr[index] = null;
296 string transform = ApplyStylesheetAndGetString (inputDoc, argArr, extensionObjects);
297 xmlFreeDoc (inputDoc);
298 Cleanup ();
299 writer.Write (transform);
300 writer.Flush ();
302 output.WriteRaw (writer.GetStringBuilder ().ToString ());
303 output.Flush ();
305 public override void Transform (XPathNavigator input, XsltArgumentList args, TextWriter output, XmlResolver resolver)
307 Transform(input, args, new XmlTextWriter(output), resolver);
310 public override void Transform(string inputfile, string outputfile, XmlResolver resolver)
312 IntPtr xmlDocument = IntPtr.Zero;
313 IntPtr resultDocument = IntPtr.Zero;
315 try {
316 xmlDocument = xmlParseFile (inputfile);
317 if (xmlDocument == IntPtr.Zero)
318 throw new XmlException ("Error parsing input file");
320 resultDocument = ApplyStylesheet (xmlDocument, null, null);
322 if (-1 == xsltSaveResultToFilename (outputfile, resultDocument, stylesheet, 0))
323 throw new XmlException ("Error in xsltSaveResultToFilename");
324 } finally {
325 if (xmlDocument != IntPtr.Zero)
326 xmlFreeDoc (xmlDocument);
328 if (resultDocument != IntPtr.Zero)
329 xmlFreeDoc (resultDocument);
331 Cleanup ();
337 static void Save (XmlReader rdr, TextWriter baseWriter)
339 XmlTextWriter writer = new XmlTextWriter (baseWriter);
341 while (rdr.Read ()) {
342 switch (rdr.NodeType) {
344 case XmlNodeType.CDATA:
345 writer.WriteCData (rdr.Value);
346 break;
348 case XmlNodeType.Comment:
349 writer.WriteComment (rdr.Value);
350 break;
352 case XmlNodeType.DocumentType:
353 writer.WriteDocType (rdr.Value, null, null, null);
354 break;
356 case XmlNodeType.Element:
357 writer.WriteStartElement (rdr.Name, rdr.Value);
359 while (rdr.MoveToNextAttribute ())
360 writer.WriteAttributes (rdr, true);
361 break;
363 case XmlNodeType.EndElement:
364 writer.WriteEndElement ();
365 break;
367 case XmlNodeType.ProcessingInstruction:
368 writer.WriteProcessingInstruction (rdr.Name, rdr.Value);
369 break;
371 case XmlNodeType.Text:
372 writer.WriteString (rdr.Value);
373 break;
375 case XmlNodeType.Whitespace:
376 writer.WriteWhitespace (rdr.Value);
377 break;
379 case XmlNodeType.XmlDeclaration:
380 writer.WriteStartDocument ();
381 break;
385 writer.Close ();
388 static void Save (XPathNavigator navigator, TextWriter writer)
390 XmlTextWriter xmlWriter = new XmlTextWriter (writer);
392 WriteTree (navigator, xmlWriter);
393 xmlWriter.WriteEndDocument ();
394 xmlWriter.Flush ();
397 // Walks the XPathNavigator tree recursively
398 static void WriteTree (XPathNavigator navigator, XmlTextWriter writer)
400 WriteCurrentNode (navigator, writer);
402 if (navigator.MoveToFirstNamespace (XPathNamespaceScope.Local)) {
403 do {
404 WriteCurrentNode (navigator, writer);
405 } while (navigator.MoveToNextNamespace (XPathNamespaceScope.Local));
407 navigator.MoveToParent ();
410 if (navigator.MoveToFirstAttribute ()) {
411 do {
412 WriteCurrentNode (navigator, writer);
413 } while (navigator.MoveToNextAttribute ());
415 navigator.MoveToParent ();
418 if (navigator.MoveToFirstChild ()) {
419 do {
420 WriteTree (navigator, writer);
421 } while (navigator.MoveToNext ());
423 navigator.MoveToParent ();
424 if (navigator.NodeType != XPathNodeType.Root)
425 writer.WriteEndElement ();
426 } else if (navigator.NodeType == XPathNodeType.Element) {
427 writer.WriteEndElement ();
431 // Format the output
432 static void WriteCurrentNode (XPathNavigator navigator, XmlTextWriter writer)
434 switch (navigator.NodeType) {
435 case XPathNodeType.Root:
436 writer.WriteStartDocument ();
437 break;
438 case XPathNodeType.Namespace:
439 if (navigator.Name == String.Empty)
440 writer.WriteAttributeString ("xmlns", navigator.Value);
441 else
442 writer.WriteAttributeString ("xmlns",
443 navigator.Name,
444 "http://www.w3.org/2000/xmlns/",
445 navigator.Value);
446 break;
447 case XPathNodeType.Attribute:
448 writer.WriteAttributeString (navigator.Name, navigator.Value);
449 break;
451 case XPathNodeType.Comment:
452 writer.WriteComment (navigator.Value);
453 break;
455 case XPathNodeType.Element:
456 writer.WriteStartElement (navigator.Name);
457 break;
459 case XPathNodeType.ProcessingInstruction:
460 writer.WriteProcessingInstruction (navigator.Name, navigator.Value);
461 break;
463 case XPathNodeType.Text:
464 writer.WriteString (navigator.Value);
465 break;
467 case XPathNodeType.SignificantWhitespace:
468 case XPathNodeType.Whitespace:
469 writer.WriteWhitespace (navigator.Value);
470 break;
474 // Extension Objects
476 internal delegate object ExtensionFunction(object[] args);
478 private struct ExtensionFunctionHolder {
479 public libxsltXPathFunction func;
480 public string ns, name;
483 // Wraps an ExtensionFunction into a function that is callable from the libxslt library.
484 private unsafe class ExtensionFunctionWrapper {
485 private readonly ExtensionFunction func;
487 public ExtensionFunctionWrapper(ExtensionFunction func) {
488 if ((object)func == null) throw new ArgumentNullException("func");
489 this.func = func;
492 public unsafe void Function(IntPtr xpath_ctxt, int nargs) {
493 // Convert XPath arguments into "managed" arguments
494 System.Collections.ArrayList args = new System.Collections.ArrayList();
495 for (int i = 0; i < nargs; i++) {
496 xpathobject* aptr = valuePop(xpath_ctxt);
497 if (aptr->type == 2) // Booleans
498 args.Add( xmlXPathCastToBoolean(aptr) == 0 ? false : true );
499 else if (aptr->type == 3) // Doubles
500 args.Add( xmlXPathCastToNumber(aptr));
501 else if (aptr->type == 4) // Strings
502 args.Add( xmlXPathCastToString(aptr));
503 else if (aptr->type == 1 && aptr->nodesetptr != null) { // Node Sets ==> ArrayList of strings
504 System.Collections.ArrayList a = new System.Collections.ArrayList();
505 for (int ni = 0; ni < aptr->nodesetptr->count; ni++) {
506 xpathobject *n = xmlXPathNewNodeSet(aptr->nodesetptr->nodes[ni]);
507 valuePush(xpath_ctxt, n);
508 xmlXPathStringFunction(xpath_ctxt, 1);
509 a.Add(xmlXPathCastToString(valuePop(xpath_ctxt)));
510 xmlXPathFreeObject(n);
512 args.Add(a);
513 } else { // Anything else => string
514 valuePush(xpath_ctxt, aptr);
515 xmlXPathStringFunction(xpath_ctxt, 1);
516 args.Add(xmlXPathCastToString(valuePop(xpath_ctxt)));
519 xmlXPathFreeObject(aptr);
522 args.Reverse();
524 object ret = func(args.ToArray());
526 // Convert the result back to an XPath object
527 if (ret == null) // null => ""
528 valuePush(xpath_ctxt, xmlXPathNewCString(""));
529 else if (ret is bool) // Booleans
530 valuePush(xpath_ctxt, xmlXPathNewBoolean((bool)ret ? 1 : 0));
531 else if (ret is int || ret is long || ret is double || ret is float || ret is decimal)
532 // Numbers
533 valuePush(xpath_ctxt, xmlXPathNewFloat((double)ret));
534 else // Everything else => String
535 valuePush(xpath_ctxt, xmlXPathNewCString(ret.ToString()));
540 // Provides a delegate for calling a late-bound method of a type with a given name.
541 // Determines method based on types of arguments.
542 private class ReflectedExtensionFunction {
543 System.Type type;
544 object src;
545 string methodname;
547 public ReflectedExtensionFunction(System.Type type, object src, string methodname) { this.type = type; this.src = src; this.methodname = methodname; }
549 public object Function(object[] args) {
550 // Construct arg type array, and a stringified version in case of problem
551 System.Type[] argtypes = new System.Type[args.Length];
552 string argtypelist = null;
553 for (int i = 0; i < args.Length; i++) {
554 argtypes[i] = (args[i] == null ? typeof(object) : args[i].GetType() );
556 if (argtypelist != null) argtypelist += ", ";
557 argtypelist += argtypes[i].FullName;
559 if (argtypelist == null) argtypelist = "";
561 // Find the method
562 System.Reflection.MethodInfo mi = type.GetMethod(methodname, (src == null ? BF.Static : BF.Instance | BF.Static) | BF.Public, null, argtypes, null);
564 // No method?
565 if (mi == null) throw new XmlException("No applicable function for " + methodname + " takes (" + argtypelist + ")");
567 if (!mi.IsStatic && src == null) throw new XmlException("Attempt to call static method without instantiated extension object.");
569 // Invoke
570 return mi.Invoke(src, args);
574 // Special Mono-specific class that allows static methods of a type to
575 // be bound without needing an instance of that type. Useful for
576 // registering System.Math functions, for example.
577 // Usage: args.AddExtensionObject( new XslTransform.UseStaticMethods(typeof(thetype)) );
578 public sealed class UseStaticMethods {
579 public readonly System.Type Type;
580 public UseStaticMethods(System.Type Type) { this.Type = Type; }
583 #endregion
585 #region Calls to external libraries
586 // libxslt
587 [DllImport ("xslt")]
588 static extern IntPtr xsltParseStylesheetFile (string filename);
590 [DllImport ("xslt")]
591 static extern IntPtr xsltParseStylesheetDoc (IntPtr docPtr);
593 [DllImport ("xslt")]
594 static extern IntPtr xsltApplyStylesheet (IntPtr stylePtr, IntPtr DocPtr, string[] argPtr);
596 [DllImport ("xslt")]
597 static extern int xsltSaveResultToString (ref IntPtr stringPtr, ref int stringLen,
598 IntPtr docPtr, IntPtr stylePtr);
600 [DllImport ("xslt")]
601 static extern int xsltSaveResultToFilename (string URI, IntPtr doc, IntPtr styleSheet, int compression);
603 [DllImport ("xslt")]
604 static extern void xsltCleanupGlobals ();
606 [DllImport ("xslt")]
607 static extern void xsltFreeStylesheet (IntPtr cur);
609 // libxml2
610 [DllImport ("xml2")]
611 static extern IntPtr xmlNewDoc (string version);
613 [DllImport ("xml2")]
614 static extern int xmlSaveFile (string filename, IntPtr cur);
616 [DllImport ("xml2")]
617 static extern IntPtr xmlParseFile (string filename);
619 [DllImport ("xml2")]
620 static extern IntPtr xmlParseDoc (string document);
622 [DllImport ("xml2")]
623 static extern void xmlFreeDoc (IntPtr doc);
625 [DllImport ("xml2")]
626 static extern void xmlCleanupParser ();
628 [DllImport ("xml2")]
629 static extern void xmlDocDumpMemory (IntPtr doc, ref IntPtr mem, ref int size);
631 [DllImport ("xml2")]
632 static extern void xmlFree (IntPtr data);
634 // Functions and structures for extension objects
636 [DllImport ("xslt")]
637 static extern IntPtr xsltNewTransformContext (IntPtr style, IntPtr doc);
639 [DllImport ("xslt")]
640 static extern void xsltFreeTransformContext (IntPtr context);
642 [DllImport ("xslt")]
643 static extern IntPtr xsltApplyStylesheetUser (IntPtr stylePtr, IntPtr DocPtr, string[] argPtr, string output, IntPtr profile, IntPtr context);
645 [DllImport ("xslt")]
646 static extern int xsltRegisterExtFunction (IntPtr context, string name, string uri, libxsltXPathFunction function);
648 [DllImport ("xml2")]
649 unsafe static extern xpathobject* valuePop (IntPtr context);
651 [DllImport ("xml2")]
652 unsafe static extern void valuePush (IntPtr context, xpathobject* data);
654 [DllImport("xml2")]
655 unsafe static extern void xmlXPathFreeObject(xpathobject* obj);
657 [DllImport("xml2")]
658 unsafe static extern xpathobject* xmlXPathNewCString(string str);
660 [DllImport("xml2")]
661 unsafe static extern xpathobject* xmlXPathNewFloat(double val);
663 [DllImport("xml2")]
664 unsafe static extern xpathobject* xmlXPathNewBoolean(int val);
666 [DllImport("xml2")]
667 unsafe static extern xpathobject* xmlXPathNewNodeSet(IntPtr nodeptr);
669 [DllImport("xml2")]
670 unsafe static extern int xmlXPathCastToBoolean(xpathobject* val);
672 [DllImport("xml2")]
673 unsafe static extern double xmlXPathCastToNumber(xpathobject* val);
675 [DllImport("xml2")]
676 unsafe static extern string xmlXPathCastToString(xpathobject* val);
678 [DllImport("xml2")]
679 static extern void xmlXPathStringFunction(IntPtr context, int nargs);
681 private delegate void libxsltXPathFunction(IntPtr xpath_ctxt, int nargs);
683 private struct xpathobject {
684 public int type;
685 public xmlnodelist* nodesetptr;
687 private struct xmlnodelist {
688 public int count;
689 public int allocated;
690 public IntPtr* nodes;
693 #endregion
695 // This classes just makes the base class use 'encoding="utf-8"'
696 class UTF8StringWriter : StringWriter
698 static Encoding encoding = new UTF8Encoding (false);
700 public override Encoding Encoding {
701 get {
702 return encoding;