1 // UnmanagedXslTransform
4 // Tim Coleman <tim@timcoleman.com>
5 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
6 // Ben Maurer (bmaurer@users.sourceforge.net)
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:
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
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
36 using System
.Collections
;
38 using System
.Security
.Policy
;
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
53 Hashtable extensionObjectCache
= new Hashtable();
58 public UnmanagedXslTransform ()
60 stylesheet
= IntPtr
.Zero
;
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
);
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
);
101 this.stylesheet
= GetStylesheetFromString (sr
.GetStringBuilder ().ToString ());
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 ());
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
) {
126 throw new XmlException ("Error parsing stylesheet");
129 result
= xsltParseStylesheetDoc (xmlDoc
);
131 if (result
== IntPtr
.Zero
)
132 throw new XmlException ("Error creating stylesheet");
137 IntPtr
ApplyStylesheet (IntPtr doc
, string[] argArr
, Hashtable extobjects
)
139 if (stylesheet
== IntPtr
.Zero
)
140 throw new XmlException ("No style sheet!");
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
);
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.");
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
);
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
);
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
;
202 functionstocache
.Add(efh
);
205 extensionObjectCache
[ext
] = functionstocache
;
211 result
= xsltApplyStylesheetUser(stylesheet
, doc
, argArr
, null, IntPtr
.Zero
, context
);
213 xsltFreeTransformContext(context
);
218 if (result
== IntPtr
.Zero
)
219 throw new XmlException ("Error applying style sheet");
224 static void Cleanup ()
226 //xsltCleanupGlobals ();
227 //xmlCleanupParser ();
230 static string GetStringFromDocument (IntPtr doc
, IntPtr stylesheet
)
232 IntPtr mem
= IntPtr
.Zero
;
235 int res
= xsltSaveResultToString (ref mem
, ref size
, doc
,
238 throw new XmlException ("xsltSaveResultToString () failed.");
240 string docStr
= Marshal
.PtrToStringAnsi (mem
, size
);
241 Marshal
.FreeHGlobal (mem
);
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
);
256 IntPtr
GetDocumentFromNavigator (XPathNavigator nav
)
258 StringWriter sr
= new UTF8StringWriter ();
260 IntPtr xmlInput
= xmlParseDoc (sr
.GetStringBuilder ().ToString ());
261 if (xmlInput
== IntPtr
.Zero
)
262 throw new XmlException ("Error getting XML from input");
267 public override void Transform (XPathNavigator input
, XsltArgumentList args
, XmlWriter output
, XmlResolver resolver
)
270 throw new ArgumentNullException ("input");
273 throw new ArgumentNullException ("output");
275 StringWriter writer
= new UTF8StringWriter ();
277 IntPtr inputDoc
= GetDocumentFromNavigator (input
);
278 string[] argArr
= null;
279 Hashtable extensionObjects
= null;
281 extensionObjects
= args
.extensionObjects
;
282 argArr
= new string[args
.parameters
.Count
* 2 + 1];
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?
292 argArr
[index
++] = "'" + value.ToString() + "'"; // FIXME: How to encode "'"?
294 argArr
[index
] = null;
296 string transform
= ApplyStylesheetAndGetString (inputDoc
, argArr
, extensionObjects
);
297 xmlFreeDoc (inputDoc
);
299 writer
.Write (transform
);
302 output
.WriteRaw (writer
.GetStringBuilder ().ToString ());
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
;
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");
325 if (xmlDocument
!= IntPtr
.Zero
)
326 xmlFreeDoc (xmlDocument
);
328 if (resultDocument
!= IntPtr
.Zero
)
329 xmlFreeDoc (resultDocument
);
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
);
348 case XmlNodeType
.Comment
:
349 writer
.WriteComment (rdr
.Value
);
352 case XmlNodeType
.DocumentType
:
353 writer
.WriteDocType (rdr
.Value
, null, null, null);
356 case XmlNodeType
.Element
:
357 writer
.WriteStartElement (rdr
.Name
, rdr
.Value
);
359 while (rdr
.MoveToNextAttribute ())
360 writer
.WriteAttributes (rdr
, true);
363 case XmlNodeType
.EndElement
:
364 writer
.WriteEndElement ();
367 case XmlNodeType
.ProcessingInstruction
:
368 writer
.WriteProcessingInstruction (rdr
.Name
, rdr
.Value
);
371 case XmlNodeType
.Text
:
372 writer
.WriteString (rdr
.Value
);
375 case XmlNodeType
.Whitespace
:
376 writer
.WriteWhitespace (rdr
.Value
);
379 case XmlNodeType
.XmlDeclaration
:
380 writer
.WriteStartDocument ();
388 static void Save (XPathNavigator navigator
, TextWriter writer
)
390 XmlTextWriter xmlWriter
= new XmlTextWriter (writer
);
392 WriteTree (navigator
, xmlWriter
);
393 xmlWriter
.WriteEndDocument ();
397 // Walks the XPathNavigator tree recursively
398 static void WriteTree (XPathNavigator navigator
, XmlTextWriter writer
)
400 WriteCurrentNode (navigator
, writer
);
402 if (navigator
.MoveToFirstNamespace (XPathNamespaceScope
.Local
)) {
404 WriteCurrentNode (navigator
, writer
);
405 } while (navigator
.MoveToNextNamespace (XPathNamespaceScope
.Local
));
407 navigator
.MoveToParent ();
410 if (navigator
.MoveToFirstAttribute ()) {
412 WriteCurrentNode (navigator
, writer
);
413 } while (navigator
.MoveToNextAttribute ());
415 navigator
.MoveToParent ();
418 if (navigator
.MoveToFirstChild ()) {
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 ();
432 static void WriteCurrentNode (XPathNavigator navigator
, XmlTextWriter writer
)
434 switch (navigator
.NodeType
) {
435 case XPathNodeType
.Root
:
436 writer
.WriteStartDocument ();
438 case XPathNodeType
.Namespace
:
439 if (navigator
.Name
== String
.Empty
)
440 writer
.WriteAttributeString ("xmlns", navigator
.Value
);
442 writer
.WriteAttributeString ("xmlns",
444 "http://www.w3.org/2000/xmlns/",
447 case XPathNodeType
.Attribute
:
448 writer
.WriteAttributeString (navigator
.Name
, navigator
.Value
);
451 case XPathNodeType
.Comment
:
452 writer
.WriteComment (navigator
.Value
);
455 case XPathNodeType
.Element
:
456 writer
.WriteStartElement (navigator
.Name
);
459 case XPathNodeType
.ProcessingInstruction
:
460 writer
.WriteProcessingInstruction (navigator
.Name
, navigator
.Value
);
463 case XPathNodeType
.Text
:
464 writer
.WriteString (navigator
.Value
);
467 case XPathNodeType
.SignificantWhitespace
:
468 case XPathNodeType
.Whitespace
:
469 writer
.WriteWhitespace (navigator
.Value
);
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");
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
);
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
);
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)
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
{
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
= "";
562 System
.Reflection
.MethodInfo mi
= type
.GetMethod(methodname
, (src
== null ? BF
.Static
: BF
.Instance
| BF
.Static
) | BF
.Public
, null, argtypes
, null);
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.");
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; }
585 #region Calls to external libraries
588 static extern IntPtr
xsltParseStylesheetFile (string filename
);
591 static extern IntPtr
xsltParseStylesheetDoc (IntPtr docPtr
);
594 static extern IntPtr
xsltApplyStylesheet (IntPtr stylePtr
, IntPtr DocPtr
, string[] argPtr
);
597 static extern int xsltSaveResultToString (ref IntPtr stringPtr
, ref int stringLen
,
598 IntPtr docPtr
, IntPtr stylePtr
);
601 static extern int xsltSaveResultToFilename (string URI
, IntPtr doc
, IntPtr styleSheet
, int compression
);
604 static extern void xsltCleanupGlobals ();
607 static extern void xsltFreeStylesheet (IntPtr cur
);
611 static extern IntPtr
xmlNewDoc (string version
);
614 static extern int xmlSaveFile (string filename
, IntPtr cur
);
617 static extern IntPtr
xmlParseFile (string filename
);
620 static extern IntPtr
xmlParseDoc (string document
);
623 static extern void xmlFreeDoc (IntPtr doc
);
626 static extern void xmlCleanupParser ();
629 static extern void xmlDocDumpMemory (IntPtr doc
, ref IntPtr mem
, ref int size
);
632 static extern void xmlFree (IntPtr data
);
634 // Functions and structures for extension objects
637 static extern IntPtr
xsltNewTransformContext (IntPtr style
, IntPtr doc
);
640 static extern void xsltFreeTransformContext (IntPtr context
);
643 static extern IntPtr
xsltApplyStylesheetUser (IntPtr stylePtr
, IntPtr DocPtr
, string[] argPtr
, string output
, IntPtr profile
, IntPtr context
);
646 static extern int xsltRegisterExtFunction (IntPtr context
, string name
, string uri
, libxsltXPathFunction function
);
649 unsafe static extern xpathobject
* valuePop (IntPtr context
);
652 unsafe static extern void valuePush (IntPtr context
, xpathobject
* data
);
655 unsafe static extern void xmlXPathFreeObject(xpathobject
* obj
);
658 unsafe static extern xpathobject
* xmlXPathNewCString(string str
);
661 unsafe static extern xpathobject
* xmlXPathNewFloat(double val
);
664 unsafe static extern xpathobject
* xmlXPathNewBoolean(int val
);
667 unsafe static extern xpathobject
* xmlXPathNewNodeSet(IntPtr nodeptr
);
670 unsafe static extern int xmlXPathCastToBoolean(xpathobject
* val
);
673 unsafe static extern double xmlXPathCastToNumber(xpathobject
* val
);
676 unsafe static extern string xmlXPathCastToString(xpathobject
* val
);
679 static extern void xmlXPathStringFunction(IntPtr context
, int nargs
);
681 private delegate void libxsltXPathFunction(IntPtr xpath_ctxt
, int nargs
);
683 private struct xpathobject
{
685 public xmlnodelist
* nodesetptr
;
687 private struct xmlnodelist
{
689 public int allocated
;
690 public IntPtr
* nodes
;
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
{