1 // System.Xml.Xsl.XslTransform
\r
4 // Tim Coleman <tim@timcoleman.com>
\r
5 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
\r
7 // (C) Copyright 2002 Tim Coleman
\r
8 // (c) 2003 Ximian Inc. (http://www.ximian.com)
\r
12 using System.Xml.XPath;
\r
15 using System.Runtime.InteropServices;
\r
17 namespace System.Xml.Xsl
\r
19 public sealed class XslTransform
\r
24 XmlResolver xmlResolver;
\r
29 #region Constructors
\r
30 public XslTransform ()
\r
32 stylesheet = IntPtr.Zero;
\r
39 public XmlResolver XmlResolver {
\r
40 set { xmlResolver = value; }
\r
49 FreeStylesheetIfNeeded ();
\r
52 void FreeStylesheetIfNeeded ()
\r
54 if (stylesheet != IntPtr.Zero) {
\r
55 xsltFreeStylesheet (stylesheet);
\r
56 stylesheet = IntPtr.Zero;
\r
60 // Loads the XSLT stylesheet contained in the IXPathNavigable.
\r
61 public void Load (IXPathNavigable stylesheet)
\r
63 Load (stylesheet.CreateNavigator ());
\r
66 // Loads the XSLT stylesheet specified by a URL.
\r
67 public void Load (string url)
\r
70 throw new ArgumentNullException ("url");
\r
72 FreeStylesheetIfNeeded ();
\r
73 stylesheet = xsltParseStylesheetFile (url);
\r
75 if (stylesheet == IntPtr.Zero)
\r
76 throw new XmlException ("Error creating stylesheet");
\r
79 static IntPtr GetStylesheetFromString (string xml)
\r
81 IntPtr result = IntPtr.Zero;
\r
83 IntPtr xmlDoc = xmlParseDoc (xml);
\r
85 if (xmlDoc == IntPtr.Zero) {
\r
87 throw new XmlException ("Error parsing stylesheet");
\r
90 result = xsltParseStylesheetDoc (xmlDoc);
\r
92 if (result == IntPtr.Zero)
\r
93 throw new XmlException ("Error creating stylesheet");
\r
98 // Loads the XSLT stylesheet contained in the XmlReader
\r
99 public void Load (XmlReader stylesheet)
\r
101 FreeStylesheetIfNeeded ();
\r
102 // Create a document for the stylesheet
\r
103 XmlDocument doc = new XmlDocument ();
\r
104 doc.Load (stylesheet);
\r
106 // Store the XML in a StringBuilder
\r
107 StringWriter sr = new UTF8StringWriter ();
\r
108 XmlTextWriter writer = new XmlTextWriter (sr);
\r
111 this.stylesheet = GetStylesheetFromString (sr.GetStringBuilder ().ToString ());
\r
113 if (this.stylesheet == IntPtr.Zero)
\r
114 throw new XmlException ("Error creating stylesheet");
\r
117 // Loads the XSLT stylesheet contained in the XPathNavigator
\r
118 public void Load (XPathNavigator stylesheet)
\r
120 FreeStylesheetIfNeeded ();
\r
121 StringWriter sr = new UTF8StringWriter ();
\r
122 Save (stylesheet, sr);
\r
123 this.stylesheet = GetStylesheetFromString (sr.GetStringBuilder ().ToString ());
\r
125 if (this.stylesheet == IntPtr.Zero)
\r
126 throw new XmlException ("Error creating stylesheet");
\r
129 [MonoTODO("use the resolver")]
\r
130 // Loads the XSLT stylesheet contained in the IXPathNavigable.
\r
131 public void Load (IXPathNavigable stylesheet, XmlResolver resolver)
\r
136 [MonoTODO("use the resolver")]
\r
137 // Loads the XSLT stylesheet specified by a URL.
\r
138 public void Load (string url, XmlResolver resolver)
\r
143 [MonoTODO("use the resolver")]
\r
144 // Loads the XSLT stylesheet contained in the XmlReader
\r
145 public void Load (XmlReader stylesheet, XmlResolver resolver)
\r
150 [MonoTODO("use the resolver")]
\r
151 // Loads the XSLT stylesheet contained in the XPathNavigator
\r
152 public void Load (XPathNavigator stylesheet, XmlResolver resolver)
\r
157 // Transforms the XML data in the IXPathNavigable using
\r
158 // the specified args and outputs the result to an XmlReader.
\r
159 public XmlReader Transform (IXPathNavigable input, XsltArgumentList args)
\r
162 throw new ArgumentNullException ("input");
\r
164 return Transform (input.CreateNavigator (), args);
\r
167 // Transforms the XML data in the input file and outputs
\r
168 // the result to an output file.
\r
169 public void Transform (string inputfile, string outputfile)
\r
171 IntPtr xmlDocument = IntPtr.Zero;
\r
172 IntPtr resultDocument = IntPtr.Zero;
\r
175 xmlDocument = xmlParseFile (inputfile);
\r
176 if (xmlDocument == IntPtr.Zero)
\r
177 throw new XmlException ("Error parsing input file");
\r
179 resultDocument = ApplyStylesheet (xmlDocument, null);
\r
181 * If I do this, the <?xml version=... is always present *
\r
182 if (-1 == xsltSaveResultToFilename (outputfile, resultDocument, stylesheet, 0))
\r
183 throw new XmlException ("Error xsltSaveResultToFilename");
\r
185 StreamWriter writer = new StreamWriter (File.OpenWrite (outputfile));
\r
186 writer.Write (GetStringFromDocument (resultDocument));
\r
189 if (xmlDocument != IntPtr.Zero)
\r
190 xmlFreeDoc (xmlDocument);
\r
192 if (resultDocument != IntPtr.Zero)
\r
193 xmlFreeDoc (resultDocument);
\r
199 IntPtr ApplyStylesheet (IntPtr doc, string[] argArr)
\r
201 if (stylesheet == IntPtr.Zero)
\r
202 throw new XmlException ("No style sheet!");
\r
204 IntPtr result = xsltApplyStylesheet (stylesheet, doc, argArr);
\r
205 if (result == IntPtr.Zero)
\r
206 throw new XmlException ("Error applying style sheet");
\r
211 static void Cleanup ()
\r
213 xsltCleanupGlobals ();
\r
214 xmlCleanupParser ();
\r
217 static string GetStringFromDocument (IntPtr doc)
\r
219 IntPtr mem = IntPtr.Zero;
\r
221 xmlDocDumpMemory (doc, ref mem, ref size);
\r
222 if (mem == IntPtr.Zero)
\r
223 throw new XmlException ("Error dumping document");
\r
225 string docStr = Marshal.PtrToStringAnsi (mem, size);
\r
226 // FIXME: Using xmlFree segfaults :-???
\r
228 Marshal.FreeHGlobal (mem);
\r
231 // Get rid of the <?xml...
\r
232 // FIXME: any other (faster) way that works?
\r
233 StringReader result = new StringReader (docStr);
\r
234 result.ReadLine (); // we want the semantics of line ending used here
\r
236 return result.ReadToEnd ();
\r
239 string ApplyStylesheetAndGetString (IntPtr doc, string[] argArr)
\r
241 IntPtr xmlOutput = ApplyStylesheet (doc, argArr);
\r
242 string strOutput = GetStringFromDocument (xmlOutput);
\r
243 xmlFreeDoc (xmlOutput);
\r
248 IntPtr GetDocumentFromNavigator (XPathNavigator nav)
\r
250 StringWriter sr = new UTF8StringWriter ();
\r
252 IntPtr xmlInput = xmlParseDoc (sr.GetStringBuilder ().ToString ());
\r
253 if (xmlInput == IntPtr.Zero)
\r
254 throw new XmlException ("Error getting XML from input");
\r
259 [MonoTODO("Node Set and Node Fragment Parameters and Extension Objects")]
\r
260 // Transforms the XML data in the XPathNavigator using
\r
261 // the specified args and outputs the result to an XmlReader.
\r
262 public XmlReader Transform (XPathNavigator input, XsltArgumentList args)
\r
264 IntPtr xmlInput = GetDocumentFromNavigator (input);
\r
265 string[] argArr = null;
\r
266 if (args != null) {
\r
267 argArr = new string[args.parameters.Count * 2 + 1];
\r
269 foreach (object key in args.parameters.Keys) {
\r
270 argArr [index++] = key.ToString();
\r
271 object value = args.parameters [key];
\r
272 if (value is Boolean)
\r
273 argArr [index++] = XmlConvert.ToString((bool) value); // FIXME: How to encode it for libxslt?
\r
274 else if (value is Double)
\r
275 argArr [index++] = XmlConvert.ToString((double) value); // FIXME: How to encode infinity's and Nan?
\r
277 argArr [index++] = "'" + value.ToString() + "'"; // FIXME: How to encode "'"?
\r
279 argArr[index] = null;
\r
281 string xslOutputString = ApplyStylesheetAndGetString (xmlInput, argArr);
\r
282 xmlFreeDoc (xmlInput);
\r
285 return new XmlTextReader (new StringReader (xslOutputString));
\r
288 // Transforms the XML data in the IXPathNavigable using
\r
289 // the specified args and outputs the result to a Stream.
\r
290 public void Transform (IXPathNavigable input, XsltArgumentList args, Stream output)
\r
293 throw new ArgumentNullException ("input");
\r
295 Transform (input.CreateNavigator (), args, new StreamWriter (output));
\r
298 // Transforms the XML data in the IXPathNavigable using
\r
299 // the specified args and outputs the result to a TextWriter.
\r
300 public void Transform (IXPathNavigable input, XsltArgumentList args, TextWriter output)
\r
303 throw new ArgumentNullException ("input");
\r
305 Transform (input.CreateNavigator (), args, output);
\r
308 // Transforms the XML data in the IXPathNavigable using
\r
309 // the specified args and outputs the result to an XmlWriter.
\r
310 public void Transform (IXPathNavigable input, XsltArgumentList args, XmlWriter output)
\r
313 throw new ArgumentNullException ("input");
\r
315 Transform (input.CreateNavigator (), args, output);
\r
318 // Transforms the XML data in the XPathNavigator using
\r
319 // the specified args and outputs the result to a Stream.
\r
320 public void Transform (XPathNavigator input, XsltArgumentList args, Stream output)
\r
322 Transform (input, args, new StreamWriter (output));
\r
325 // Transforms the XML data in the XPathNavigator using
\r
326 // the specified args and outputs the result to a TextWriter.
\r
327 [MonoTODO("Node Set and Node Fragment Parameters and Extension Objects")]
\r
328 public void Transform (XPathNavigator input, XsltArgumentList args, TextWriter output)
\r
331 throw new ArgumentNullException ("input");
\r
333 if (output == null)
\r
334 throw new ArgumentNullException ("output");
\r
336 IntPtr inputDoc = GetDocumentFromNavigator (input);
\r
337 string[] argArr = null;
\r
338 if (args != null) {
\r
339 argArr = new string[args.parameters.Count * 2 + 1];
\r
341 foreach (object key in args.parameters.Keys) {
\r
342 argArr [index++] = key.ToString();
\r
343 object value = args.parameters [key];
\r
344 if (value is Boolean)
\r
345 argArr [index++] = XmlConvert.ToString((bool) value); // FIXME: How to encode it for libxslt?
\r
346 else if (value is Double)
\r
347 argArr [index++] = XmlConvert.ToString((double) value); // FIXME: How to encode infinity's and Nan?
\r
349 argArr [index++] = "'" + value.ToString() + "'"; // FIXME: How to encode "'"?
\r
351 argArr[index] = null;
\r
353 string transform = ApplyStylesheetAndGetString (inputDoc, argArr);
\r
354 xmlFreeDoc (inputDoc);
\r
356 output.Write (transform);
\r
360 // Transforms the XML data in the XPathNavigator using
\r
361 // the specified args and outputs the result to an XmlWriter.
\r
362 public void Transform (XPathNavigator input, XsltArgumentList args, XmlWriter output)
\r
364 StringWriter writer = new UTF8StringWriter ();
\r
365 Transform (input, args, writer);
\r
366 output.WriteRaw (writer.GetStringBuilder ().ToString ());
\r
370 static void Save (XmlReader rdr, TextWriter baseWriter)
\r
372 XmlTextWriter writer = new XmlTextWriter (baseWriter);
\r
374 while (rdr.Read ()) {
\r
375 switch (rdr.NodeType) {
\r
377 case XmlNodeType.CDATA:
\r
378 writer.WriteCData (rdr.Value);
\r
381 case XmlNodeType.Comment:
\r
382 writer.WriteComment (rdr.Value);
\r
385 case XmlNodeType.DocumentType:
\r
386 writer.WriteDocType (rdr.Value, null, null, null);
\r
389 case XmlNodeType.Element:
\r
390 writer.WriteStartElement (rdr.Name, rdr.Value);
\r
392 while (rdr.MoveToNextAttribute ())
\r
393 writer.WriteAttributes (rdr, true);
\r
396 case XmlNodeType.EndElement:
\r
397 writer.WriteEndElement ();
\r
400 case XmlNodeType.ProcessingInstruction:
\r
401 writer.WriteProcessingInstruction (rdr.Name, rdr.Value);
\r
404 case XmlNodeType.Text:
\r
405 writer.WriteString (rdr.Value);
\r
408 case XmlNodeType.Whitespace:
\r
409 writer.WriteWhitespace (rdr.Value);
\r
412 case XmlNodeType.XmlDeclaration:
\r
413 writer.WriteStartDocument ();
\r
421 static void Save (XPathNavigator navigator, TextWriter writer)
\r
423 XmlTextWriter xmlWriter = new XmlTextWriter (writer);
\r
425 WriteTree (navigator, xmlWriter);
\r
426 xmlWriter.WriteEndDocument ();
\r
427 xmlWriter.Flush ();
\r
430 // Walks the XPathNavigator tree recursively
\r
431 static void WriteTree (XPathNavigator navigator, XmlTextWriter writer)
\r
433 WriteCurrentNode (navigator, writer);
\r
435 if (navigator.MoveToFirstAttribute ()) {
\r
437 WriteCurrentNode (navigator, writer);
\r
438 } while (navigator.MoveToNextAttribute ());
\r
440 navigator.MoveToParent ();
\r
443 if (navigator.MoveToFirstChild ()) {
\r
445 WriteTree (navigator, writer);
\r
446 } while (navigator.MoveToNext ());
\r
448 navigator.MoveToParent ();
\r
449 if (navigator.NodeType != XPathNodeType.Root)
\r
450 writer.WriteEndElement ();
\r
451 } else if (navigator.NodeType == XPathNodeType.Element) {
\r
452 writer.WriteEndElement ();
\r
456 // Format the output
\r
457 static void WriteCurrentNode (XPathNavigator navigator, XmlTextWriter writer)
\r
459 switch (navigator.NodeType) {
\r
460 case XPathNodeType.Root:
\r
461 writer.WriteStartDocument ();
\r
463 case XPathNodeType.Attribute:
\r
464 writer.WriteAttributeString (navigator.LocalName, navigator.Value);
\r
467 case XPathNodeType.Comment:
\r
468 writer.WriteComment (navigator.Value);
\r
471 case XPathNodeType.Element:
\r
472 writer.WriteStartElement (navigator.Name);
\r
475 case XPathNodeType.ProcessingInstruction:
\r
476 writer.WriteProcessingInstruction (navigator.Name, navigator.Value);
\r
479 case XPathNodeType.Text:
\r
480 writer.WriteString (navigator.Value);
\r
483 case XPathNodeType.SignificantWhitespace:
\r
484 case XPathNodeType.Whitespace:
\r
485 writer.WriteWhitespace (navigator.Value);
\r
492 #region Calls to external libraries
\r
494 [DllImport ("xslt")]
\r
495 static extern IntPtr xsltParseStylesheetFile (string filename);
\r
497 [DllImport ("xslt")]
\r
498 static extern IntPtr xsltParseStylesheetDoc (IntPtr docPtr);
\r
500 [DllImport ("xslt")]
\r
501 static extern IntPtr xsltApplyStylesheet (IntPtr stylePtr, IntPtr DocPtr, string[] argPtr);
\r
503 [DllImport ("xslt")]
\r
504 static extern int xsltSaveResultToFilename (string URI, IntPtr doc, IntPtr styleSheet, int compression);
\r
506 [DllImport ("xslt")]
\r
507 static extern void xsltCleanupGlobals ();
\r
509 [DllImport ("xslt")]
\r
510 static extern void xsltFreeStylesheet (IntPtr cur);
\r
513 [DllImport ("xml2")]
\r
514 static extern IntPtr xmlNewDoc (string version);
\r
516 [DllImport ("xml2")]
\r
517 static extern int xmlSaveFile (string filename, IntPtr cur);
\r
519 [DllImport ("xml2")]
\r
520 static extern IntPtr xmlParseFile (string filename);
\r
522 [DllImport ("xml2")]
\r
523 static extern IntPtr xmlParseDoc (string document);
\r
525 [DllImport ("xml2")]
\r
526 static extern void xmlFreeDoc (IntPtr doc);
\r
528 [DllImport ("xml2")]
\r
529 static extern void xmlCleanupParser ();
\r
531 [DllImport ("xml2")]
\r
532 static extern void xmlDocDumpMemory (IntPtr doc, ref IntPtr mem, ref int size);
\r
534 [DllImport ("xml2")]
\r
535 static extern void xmlFree (IntPtr data);
\r
539 // This classes just makes the base class use 'encoding="utf-8"'
\r
540 class UTF8StringWriter : StringWriter
\r
542 static Encoding encoding = new UTF8Encoding (false);
\r
544 public override Encoding Encoding {
\r