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 = null;
\r
261 if (args != null) {
\r
262 argArr = new string[args.parameters.Count * 2 + 1];
\r
264 foreach (object key in args.parameters.Keys) {
\r
265 argArr [index++] = key.ToString();
\r
266 object value = args.parameters [key];
\r
267 if (value is Boolean)
\r
268 argArr [index++] = XmlConvert.ToString((bool) value); // FIXME: How to encode it for libxslt?
\r
269 else if (value is Double)
\r
270 argArr [index++] = XmlConvert.ToString((double) value); // FIXME: How to encode infinity's and Nan?
\r
272 argArr [index++] = "'" + value.ToString() + "'"; // FIXME: How to encode "'"?
\r
274 argArr[index] = null;
\r
276 string xslOutputString = ApplyStylesheetAndGetString (xmlInput, argArr);
\r
277 xmlFreeDoc (xmlInput);
\r
280 return new XmlTextReader (new StringReader (xslOutputString));
\r
283 // Transforms the XML data in the IXPathNavigable using
\r
284 // the specified args and outputs the result to a Stream.
\r
285 public void Transform (IXPathNavigable input, XsltArgumentList args, Stream output)
\r
288 throw new ArgumentNullException ("input");
\r
290 Transform (input.CreateNavigator (), args, new StreamWriter (output));
\r
293 // Transforms the XML data in the IXPathNavigable using
\r
294 // the specified args and outputs the result to a TextWriter.
\r
295 public void Transform (IXPathNavigable input, XsltArgumentList args, TextWriter output)
\r
298 throw new ArgumentNullException ("input");
\r
300 Transform (input.CreateNavigator (), args, output);
\r
303 // Transforms the XML data in the IXPathNavigable using
\r
304 // the specified args and outputs the result to an XmlWriter.
\r
305 public void Transform (IXPathNavigable input, XsltArgumentList args, XmlWriter output)
\r
308 throw new ArgumentNullException ("input");
\r
310 Transform (input.CreateNavigator (), args, output);
\r
313 // Transforms the XML data in the XPathNavigator using
\r
314 // the specified args and outputs the result to a Stream.
\r
315 public void Transform (XPathNavigator input, XsltArgumentList args, Stream output)
\r
317 Transform (input, args, new StreamWriter (output));
\r
320 // Transforms the XML data in the XPathNavigator using
\r
321 // the specified args and outputs the result to a TextWriter.
\r
322 [MonoTODO("Node Set and Node Fragment Parameters and Extension Objects")]
\r
323 public void Transform (XPathNavigator input, XsltArgumentList args, TextWriter output)
\r
326 throw new ArgumentNullException ("input");
\r
328 if (output == null)
\r
329 throw new ArgumentNullException ("output");
\r
331 IntPtr inputDoc = GetDocumentFromNavigator (input);
\r
332 string[] argArr = null;
\r
333 if (args != null) {
\r
334 argArr = new string[args.parameters.Count * 2 + 1];
\r
336 foreach (object key in args.parameters.Keys) {
\r
337 argArr [index++] = key.ToString();
\r
338 object value = args.parameters [key];
\r
339 if (value is Boolean)
\r
340 argArr [index++] = XmlConvert.ToString((bool) value); // FIXME: How to encode it for libxslt?
\r
341 else if (value is Double)
\r
342 argArr [index++] = XmlConvert.ToString((double) value); // FIXME: How to encode infinity's and Nan?
\r
344 argArr [index++] = "'" + value.ToString() + "'"; // FIXME: How to encode "'"?
\r
346 argArr[index] = null;
\r
348 string transform = ApplyStylesheetAndGetString (inputDoc, argArr);
\r
349 xmlFreeDoc (inputDoc);
\r
351 output.Write (transform);
\r
354 // Transforms the XML data in the XPathNavigator using
\r
355 // the specified args and outputs the result to an XmlWriter.
\r
356 public void Transform (XPathNavigator input, XsltArgumentList args, XmlWriter output)
\r
358 StringWriter writer = new UTF8StringWriter ();
\r
359 Transform (input, args, writer);
\r
360 output.WriteRaw (writer.GetStringBuilder ().ToString ());
\r
363 static void Save (XmlReader rdr, TextWriter baseWriter)
\r
365 XmlTextWriter writer = new XmlTextWriter (baseWriter);
\r
367 while (rdr.Read ()) {
\r
368 switch (rdr.NodeType) {
\r
370 case XmlNodeType.CDATA:
\r
371 writer.WriteCData (rdr.Value);
\r
374 case XmlNodeType.Comment:
\r
375 writer.WriteComment (rdr.Value);
\r
378 case XmlNodeType.DocumentType:
\r
379 writer.WriteDocType (rdr.Value, null, null, null);
\r
382 case XmlNodeType.Element:
\r
383 writer.WriteStartElement (rdr.Name, rdr.Value);
\r
385 while (rdr.MoveToNextAttribute ())
\r
386 writer.WriteAttributes (rdr, true);
\r
389 case XmlNodeType.EndElement:
\r
390 writer.WriteEndElement ();
\r
393 case XmlNodeType.ProcessingInstruction:
\r
394 writer.WriteProcessingInstruction (rdr.Name, rdr.Value);
\r
397 case XmlNodeType.Text:
\r
398 writer.WriteString (rdr.Value);
\r
401 case XmlNodeType.Whitespace:
\r
402 writer.WriteWhitespace (rdr.Value);
\r
405 case XmlNodeType.XmlDeclaration:
\r
406 writer.WriteStartDocument ();
\r
414 static void Save (XPathNavigator navigator, TextWriter writer)
\r
416 XmlTextWriter xmlWriter = new XmlTextWriter (writer);
\r
418 WriteTree (navigator, xmlWriter);
\r
419 xmlWriter.WriteEndDocument ();
\r
420 xmlWriter.Flush ();
\r
423 // Walks the XPathNavigator tree recursively
\r
424 static void WriteTree (XPathNavigator navigator, XmlTextWriter writer)
\r
426 WriteCurrentNode (navigator, writer);
\r
428 if (navigator.MoveToFirstAttribute ()) {
\r
430 WriteCurrentNode (navigator, writer);
\r
431 } while (navigator.MoveToNextAttribute ());
\r
433 navigator.MoveToParent ();
\r
436 if (navigator.MoveToFirstChild ()) {
\r
438 WriteTree (navigator, writer);
\r
439 } while (navigator.MoveToNext ());
\r
441 navigator.MoveToParent ();
\r
442 if (navigator.NodeType != XPathNodeType.Root)
\r
443 writer.WriteEndElement ();
\r
444 } else if (navigator.NodeType == XPathNodeType.Element) {
\r
445 writer.WriteEndElement ();
\r
449 // Format the output
\r
450 static void WriteCurrentNode (XPathNavigator navigator, XmlTextWriter writer)
\r
452 switch (navigator.NodeType) {
\r
453 case XPathNodeType.Root:
\r
454 writer.WriteStartDocument ();
\r
456 case XPathNodeType.Attribute:
\r
457 writer.WriteAttributeString (navigator.LocalName, navigator.Value);
\r
460 case XPathNodeType.Comment:
\r
461 writer.WriteComment (navigator.Value);
\r
464 case XPathNodeType.Element:
\r
465 writer.WriteStartElement (navigator.Name);
\r
468 case XPathNodeType.ProcessingInstruction:
\r
469 writer.WriteProcessingInstruction (navigator.Name, navigator.Value);
\r
472 case XPathNodeType.Text:
\r
473 writer.WriteString (navigator.Value);
\r
476 case XPathNodeType.SignificantWhitespace:
\r
477 case XPathNodeType.Whitespace:
\r
478 writer.WriteWhitespace (navigator.Value);
\r
485 #region Calls to external libraries
\r
487 [DllImport ("xslt")]
\r
488 static extern IntPtr xsltParseStylesheetFile (string filename);
\r
490 [DllImport ("xslt")]
\r
491 static extern IntPtr xsltParseStylesheetDoc (IntPtr docPtr);
\r
493 [DllImport ("xslt")]
\r
494 static extern IntPtr xsltApplyStylesheet (IntPtr stylePtr, IntPtr DocPtr, string[] argPtr);
\r
496 [DllImport ("xslt")]
\r
497 static extern int xsltSaveResultToFilename (string URI, IntPtr doc, IntPtr styleSheet, int compression);
\r
499 [DllImport ("xslt")]
\r
500 static extern void xsltCleanupGlobals ();
\r
502 [DllImport ("xslt")]
\r
503 static extern void xsltFreeStylesheet (IntPtr cur);
\r
506 [DllImport ("xml2")]
\r
507 static extern IntPtr xmlNewDoc (string version);
\r
509 [DllImport ("xml2")]
\r
510 static extern int xmlSaveFile (string filename, IntPtr cur);
\r
512 [DllImport ("xml2")]
\r
513 static extern IntPtr xmlParseFile (string filename);
\r
515 [DllImport ("xml2")]
\r
516 static extern IntPtr xmlParseDoc (string document);
\r
518 [DllImport ("xml2")]
\r
519 static extern void xmlFreeDoc (IntPtr doc);
\r
521 [DllImport ("xml2")]
\r
522 static extern void xmlCleanupParser ();
\r
524 [DllImport ("xml2")]
\r
525 static extern void xmlDocDumpMemory (IntPtr doc, ref IntPtr mem, ref int size);
\r
527 [DllImport ("xml2")]
\r
528 static extern void xmlFree (IntPtr data);
\r
532 // This classes just makes the base class use 'encoding="utf-8"'
\r
533 class UTF8StringWriter : StringWriter
\r
535 static Encoding encoding = new UTF8Encoding (false);
\r
537 public override Encoding Encoding {
\r