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
47 void FreeStylesheetIfNeeded ()
\r
49 if (stylesheet != IntPtr.Zero) {
\r
50 xsltFreeStylesheet (stylesheet);
\r
51 stylesheet = IntPtr.Zero;
\r
55 // Loads the XSLT stylesheet contained in the IXPathNavigable.
\r
56 public void Load (IXPathNavigable stylesheet)
\r
58 Load (stylesheet.CreateNavigator ());
\r
61 // Loads the XSLT stylesheet specified by a URL.
\r
62 public void Load (string url)
\r
65 throw new ArgumentNullException ("url");
\r
67 FreeStylesheetIfNeeded ();
\r
68 stylesheet = xsltParseStylesheetFile (url);
\r
70 if (stylesheet == IntPtr.Zero)
\r
71 throw new XmlException ("Error creating stylesheet");
\r
74 static IntPtr GetStylesheetFromString (string xml)
\r
76 IntPtr result = IntPtr.Zero;
\r
78 IntPtr xmlDoc = xmlParseDoc (xml);
\r
80 if (xmlDoc == IntPtr.Zero) {
\r
82 throw new XmlException ("Error parsing stylesheet");
\r
85 result = xsltParseStylesheetDoc (xmlDoc);
\r
87 if (result == IntPtr.Zero)
\r
88 throw new XmlException ("Error creating stylesheet");
\r
93 // Loads the XSLT stylesheet contained in the XmlReader
\r
94 public void Load (XmlReader stylesheet)
\r
96 FreeStylesheetIfNeeded ();
\r
97 // Create a document for the stylesheet
\r
98 XmlDocument doc = new XmlDocument ();
\r
99 doc.Load (stylesheet);
\r
101 // Store the XML in a StringBuilder
\r
102 StringWriter sr = new UTF8StringWriter ();
\r
103 XmlTextWriter writer = new XmlTextWriter (sr);
\r
106 this.stylesheet = GetStylesheetFromString (sr.GetStringBuilder ().ToString ());
\r
108 if (this.stylesheet == IntPtr.Zero)
\r
109 throw new XmlException ("Error creating stylesheet");
\r
112 // Loads the XSLT stylesheet contained in the XPathNavigator
\r
113 public void Load (XPathNavigator stylesheet)
\r
115 FreeStylesheetIfNeeded ();
\r
116 StringWriter sr = new UTF8StringWriter ();
\r
117 Save (stylesheet, sr);
\r
118 this.stylesheet = GetStylesheetFromString (sr.GetStringBuilder ().ToString ());
\r
120 if (this.stylesheet == IntPtr.Zero)
\r
121 throw new XmlException ("Error creating stylesheet");
\r
124 [MonoTODO("use the resolver")]
\r
125 // Loads the XSLT stylesheet contained in the IXPathNavigable.
\r
126 public void Load (IXPathNavigable stylesheet, XmlResolver resolver)
\r
131 [MonoTODO("use the resolver")]
\r
132 // Loads the XSLT stylesheet specified by a URL.
\r
133 public void Load (string url, XmlResolver resolver)
\r
138 [MonoTODO("use the resolver")]
\r
139 // Loads the XSLT stylesheet contained in the XmlReader
\r
140 public void Load (XmlReader stylesheet, XmlResolver resolver)
\r
145 [MonoTODO("use the resolver")]
\r
146 // Loads the XSLT stylesheet contained in the XPathNavigator
\r
147 public void Load (XPathNavigator stylesheet, XmlResolver resolver)
\r
152 // Transforms the XML data in the IXPathNavigable using
\r
153 // the specified args and outputs the result to an XmlReader.
\r
154 public XmlReader Transform (IXPathNavigable input, XsltArgumentList args)
\r
157 throw new ArgumentNullException ("input");
\r
159 return Transform (input.CreateNavigator (), args);
\r
162 // Transforms the XML data in the input file and outputs
\r
163 // the result to an output file.
\r
164 public void Transform (string inputfile, string outputfile)
\r
166 IntPtr xmlDocument = IntPtr.Zero;
\r
167 IntPtr resultDocument = IntPtr.Zero;
\r
170 xmlDocument = xmlParseFile (inputfile);
\r
171 if (xmlDocument == IntPtr.Zero)
\r
172 throw new XmlException ("Error parsing input file");
\r
174 resultDocument = ApplyStylesheet (xmlDocument, null);
\r
176 * If I do this, the <?xml version=... is always present *
\r
177 if (-1 == xsltSaveResultToFilename (outputfile, resultDocument, stylesheet, 0))
\r
178 throw new XmlException ("Error xsltSaveResultToFilename");
\r
180 StreamWriter writer = new StreamWriter (File.OpenWrite (outputfile));
\r
181 writer.Write (GetStringFromDocument (resultDocument));
\r
184 if (xmlDocument != IntPtr.Zero)
\r
185 xmlFreeDoc (xmlDocument);
\r
187 if (resultDocument != IntPtr.Zero)
\r
188 xmlFreeDoc (resultDocument);
\r
194 IntPtr ApplyStylesheet (IntPtr doc, string[] argArr)
\r
196 if (stylesheet == IntPtr.Zero)
\r
197 throw new XmlException ("No style sheet!");
\r
199 IntPtr result = xsltApplyStylesheet (stylesheet, doc, argArr);
\r
200 if (result == IntPtr.Zero)
\r
201 throw new XmlException ("Error applying style sheet");
\r
206 static void Cleanup ()
\r
208 xsltCleanupGlobals ();
\r
209 xmlCleanupParser ();
\r
212 static string GetStringFromDocument (IntPtr doc)
\r
214 IntPtr mem = IntPtr.Zero;
\r
216 xmlDocDumpMemory (doc, ref mem, ref size);
\r
217 if (mem == IntPtr.Zero)
\r
218 throw new XmlException ("Error dumping document");
\r
220 string docStr = Marshal.PtrToStringAnsi (mem, size);
\r
221 // FIXME: Using xmlFree segfaults :-???
\r
223 Marshal.FreeHGlobal (mem);
\r
226 // Get rid of the <?xml...
\r
227 // FIXME: any other (faster) way that works?
\r
228 StringReader result = new StringReader (docStr);
\r
229 result.ReadLine (); // we want the semantics of line ending used here
\r
231 return result.ReadToEnd ();
\r
234 string ApplyStylesheetAndGetString (IntPtr doc, string[] argArr)
\r
236 IntPtr xmlOutput = ApplyStylesheet (doc, argArr);
\r
237 string strOutput = GetStringFromDocument (xmlOutput);
\r
238 xmlFreeDoc (xmlOutput);
\r
243 IntPtr GetDocumentFromNavigator (XPathNavigator nav)
\r
245 StringWriter sr = new UTF8StringWriter ();
\r
247 IntPtr xmlInput = xmlParseDoc (sr.GetStringBuilder ().ToString ());
\r
248 if (xmlInput == IntPtr.Zero)
\r
249 throw new XmlException ("Error getting XML from input");
\r
254 [MonoTODO("Node Set and Node Fragment Parameters and Extension Objects")]
\r
255 // Transforms the XML data in the XPathNavigator using
\r
256 // the specified args and outputs the result to an XmlReader.
\r
257 public XmlReader Transform (XPathNavigator input, XsltArgumentList args)
\r
259 IntPtr xmlInput = GetDocumentFromNavigator (input);
\r
260 string[] argArr = new string[args.parameters.Count * 2 + 1];
\r
262 foreach (object key in args.parameters.Keys) {
\r
263 argArr [index++] = key.ToString();
\r
264 object value = args.parameters [key];
\r
265 if (value is Boolean)
\r
266 argArr [index++] = XmlConvert.ToString((bool) value); // FIXME: How to encode it for libxslt?
\r
267 else if (value is Double)
\r
268 argArr [index++] = XmlConvert.ToString((double) value); // FIXME: How to encode infinity's and Nan?
\r
270 argArr [index++] = "'" + value.ToString() + "'"; // FIXME: How to encode "'"?
\r
272 argArr[index] = null;
\r
273 string xslOutputString = ApplyStylesheetAndGetString (xmlInput, argArr);
\r
274 xmlFreeDoc (xmlInput);
\r
277 return new XmlTextReader (new StringReader (xslOutputString));
\r
280 // Transforms the XML data in the IXPathNavigable using
\r
281 // the specified args and outputs the result to a Stream.
\r
282 public void Transform (IXPathNavigable input, XsltArgumentList args, Stream output)
\r
285 throw new ArgumentNullException ("input");
\r
287 Transform (input.CreateNavigator (), args, new StreamWriter (output));
\r
290 // Transforms the XML data in the IXPathNavigable using
\r
291 // the specified args and outputs the result to a TextWriter.
\r
292 public void Transform (IXPathNavigable input, XsltArgumentList args, TextWriter output)
\r
295 throw new ArgumentNullException ("input");
\r
297 Transform (input.CreateNavigator (), args, output);
\r
300 // Transforms the XML data in the IXPathNavigable using
\r
301 // the specified args and outputs the result to an XmlWriter.
\r
302 public void Transform (IXPathNavigable input, XsltArgumentList args, XmlWriter output)
\r
305 throw new ArgumentNullException ("input");
\r
307 Transform (input.CreateNavigator (), args, output);
\r
310 // Transforms the XML data in the XPathNavigator using
\r
311 // the specified args and outputs the result to a Stream.
\r
312 public void Transform (XPathNavigator input, XsltArgumentList args, Stream output)
\r
314 Transform (input, args, new StreamWriter (output));
\r
317 // Transforms the XML data in the XPathNavigator using
\r
318 // the specified args and outputs the result to a TextWriter.
\r
319 [MonoTODO("Node Set and Node Fragment Parameters and Extension Objects")]
\r
320 public void Transform (XPathNavigator input, XsltArgumentList args, TextWriter output)
\r
323 throw new ArgumentNullException ("input");
\r
325 if (output == null)
\r
326 throw new ArgumentNullException ("output");
\r
328 IntPtr inputDoc = GetDocumentFromNavigator (input);
\r
329 string[] argArr = new string[args.parameters.Count * 2 + 1];
\r
331 foreach (object key in args.parameters.Keys) {
\r
332 argArr [index++] = key.ToString();
\r
333 object value = args.parameters [key];
\r
334 if (value is Boolean)
\r
335 argArr [index++] = XmlConvert.ToString((bool) value); // FIXME: How to encode it for libxslt?
\r
336 else if (value is Double)
\r
337 argArr [index++] = XmlConvert.ToString((double) value); // FIXME: How to encode infinity's and Nan?
\r
339 argArr [index++] = "'" + value.ToString() + "'"; // FIXME: How to encode "'"?
\r
341 argArr[index] = null;
\r
342 string transform = ApplyStylesheetAndGetString (inputDoc, argArr);
\r
343 xmlFreeDoc (inputDoc);
\r
345 output.Write (transform);
\r
348 // Transforms the XML data in the XPathNavigator using
\r
349 // the specified args and outputs the result to an XmlWriter.
\r
350 public void Transform (XPathNavigator input, XsltArgumentList args, XmlWriter output)
\r
352 StringWriter writer = new UTF8StringWriter ();
\r
353 Transform (input, args, writer);
\r
354 output.WriteRaw (writer.GetStringBuilder ().ToString ());
\r
357 static void Save (XmlReader rdr, TextWriter baseWriter)
\r
359 XmlTextWriter writer = new XmlTextWriter (baseWriter);
\r
361 while (rdr.Read ()) {
\r
362 switch (rdr.NodeType) {
\r
364 case XmlNodeType.CDATA:
\r
365 writer.WriteCData (rdr.Value);
\r
368 case XmlNodeType.Comment:
\r
369 writer.WriteComment (rdr.Value);
\r
372 case XmlNodeType.DocumentType:
\r
373 writer.WriteDocType (rdr.Value, null, null, null);
\r
376 case XmlNodeType.Element:
\r
377 writer.WriteStartElement (rdr.Name, rdr.Value);
\r
379 while (rdr.MoveToNextAttribute ())
\r
380 writer.WriteAttributes (rdr, true);
\r
383 case XmlNodeType.EndElement:
\r
384 writer.WriteEndElement ();
\r
387 case XmlNodeType.ProcessingInstruction:
\r
388 writer.WriteProcessingInstruction (rdr.Name, rdr.Value);
\r
391 case XmlNodeType.Text:
\r
392 writer.WriteString (rdr.Value);
\r
395 case XmlNodeType.Whitespace:
\r
396 writer.WriteWhitespace (rdr.Value);
\r
399 case XmlNodeType.XmlDeclaration:
\r
400 writer.WriteStartDocument ();
\r
408 static void Save (XPathNavigator navigator, TextWriter writer)
\r
410 XmlTextWriter xmlWriter = new XmlTextWriter (writer);
\r
412 WriteTree (navigator, xmlWriter);
\r
413 xmlWriter.WriteEndDocument ();
\r
414 xmlWriter.Flush ();
\r
417 // Walks the XPathNavigator tree recursively
\r
418 static void WriteTree (XPathNavigator navigator, XmlTextWriter writer)
\r
420 WriteCurrentNode (navigator, writer);
\r
422 if (navigator.MoveToFirstAttribute ()) {
\r
424 WriteCurrentNode (navigator, writer);
\r
425 } while (navigator.MoveToNextAttribute ());
\r
427 navigator.MoveToParent ();
\r
430 if (navigator.MoveToFirstChild ()) {
\r
432 WriteTree (navigator, writer);
\r
433 } while (navigator.MoveToNext ());
\r
435 navigator.MoveToParent ();
\r
436 if (navigator.NodeType != XPathNodeType.Root)
\r
437 writer.WriteEndElement ();
\r
438 } else if (navigator.NodeType == XPathNodeType.Element) {
\r
439 writer.WriteEndElement ();
\r
443 // Format the output
\r
444 static void WriteCurrentNode (XPathNavigator navigator, XmlTextWriter writer)
\r
446 switch (navigator.NodeType) {
\r
447 case XPathNodeType.Root:
\r
448 writer.WriteStartDocument ();
\r
450 case XPathNodeType.Attribute:
\r
451 writer.WriteAttributeString (navigator.LocalName, navigator.Value);
\r
454 case XPathNodeType.Comment:
\r
455 writer.WriteComment (navigator.Value);
\r
458 case XPathNodeType.Element:
\r
459 writer.WriteStartElement (navigator.Name);
\r
462 case XPathNodeType.ProcessingInstruction:
\r
463 writer.WriteProcessingInstruction (navigator.Name, navigator.Value);
\r
466 case XPathNodeType.Text:
\r
467 writer.WriteString (navigator.Value);
\r
470 case XPathNodeType.SignificantWhitespace:
\r
471 case XPathNodeType.Whitespace:
\r
472 writer.WriteWhitespace (navigator.Value);
\r
479 #region Calls to external libraries
\r
481 [DllImport ("xslt")]
\r
482 static extern IntPtr xsltParseStylesheetFile (string filename);
\r
484 [DllImport ("xslt")]
\r
485 static extern IntPtr xsltParseStylesheetDoc (IntPtr docPtr);
\r
487 [DllImport ("xslt")]
\r
488 static extern IntPtr xsltApplyStylesheet (IntPtr stylePtr, IntPtr DocPtr, string[] argPtr);
\r
490 [DllImport ("xslt")]
\r
491 static extern int xsltSaveResultToFilename (string URI, IntPtr doc, IntPtr styleSheet, int compression);
\r
493 [DllImport ("xslt")]
\r
494 static extern void xsltCleanupGlobals ();
\r
496 [DllImport ("xslt")]
\r
497 static extern void xsltFreeStylesheet (IntPtr cur);
\r
500 [DllImport ("xml2")]
\r
501 static extern IntPtr xmlNewDoc (string version);
\r
503 [DllImport ("xml2")]
\r
504 static extern int xmlSaveFile (string filename, IntPtr cur);
\r
506 [DllImport ("xml2")]
\r
507 static extern IntPtr xmlParseFile (string filename);
\r
509 [DllImport ("xml2")]
\r
510 static extern IntPtr xmlParseDoc (string document);
\r
512 [DllImport ("xml2")]
\r
513 static extern void xmlFreeDoc (IntPtr doc);
\r
515 [DllImport ("xml2")]
\r
516 static extern void xmlCleanupParser ();
\r
518 [DllImport ("xml2")]
\r
519 static extern void xmlDocDumpMemory (IntPtr doc, ref IntPtr mem, ref int size);
\r
521 [DllImport ("xml2")]
\r
522 static extern void xmlFree (IntPtr data);
\r
526 // This classes just makes the base class use 'encoding="utf-8"'
\r
527 class UTF8StringWriter : StringWriter
\r
529 static Encoding encoding = new UTF8Encoding (false);
\r
531 public override Encoding Encoding {
\r