2003-01-22 Gonzalo Paniagua Javier <gonzalo@ximian.com>
[mono.git] / mcs / class / System.XML / System.Xml.Xsl / XslTransform.cs
1 // System.Xml.Xsl.XslTransform\r
2 //\r
3 // Authors:\r
4 //      Tim Coleman <tim@timcoleman.com>\r
5 //      Gonzalo Paniagua Javier (gonzalo@ximian.com)\r
6 //\r
7 // (C) Copyright 2002 Tim Coleman\r
8 // (c) 2003 Ximian Inc. (http://www.ximian.com)\r
9 //\r
10 \r
11 using System;\r
12 using System.Xml.XPath;\r
13 using System.IO;\r
14 using System.Text;\r
15 using System.Runtime.InteropServices;\r
16 \r
17 namespace System.Xml.Xsl\r
18 {\r
19         public sealed class XslTransform\r
20         {\r
21 \r
22                 #region Fields\r
23 \r
24                 XmlResolver xmlResolver;\r
25                 IntPtr stylesheet;\r
26 \r
27                 #endregion\r
28 \r
29                 #region Constructors\r
30                 public XslTransform ()\r
31                 {\r
32                         stylesheet = IntPtr.Zero;\r
33                 }\r
34 \r
35                 #endregion\r
36 \r
37                 #region Properties\r
38 \r
39                 public XmlResolver XmlResolver {\r
40                         set { xmlResolver = value; }\r
41                 }\r
42 \r
43                 #endregion\r
44 \r
45                 #region Methods\r
46 \r
47                 void FreeStylesheetIfNeeded ()\r
48                 {\r
49                         if (stylesheet != IntPtr.Zero) {\r
50                                 xsltFreeStylesheet (stylesheet);\r
51                                 stylesheet = IntPtr.Zero;\r
52                         }\r
53                 }\r
54                 \r
55                 // Loads the XSLT stylesheet contained in the IXPathNavigable.\r
56                 public void Load (IXPathNavigable stylesheet)\r
57                 {\r
58                         Load (stylesheet.CreateNavigator ());\r
59                 }\r
60 \r
61                 // Loads the XSLT stylesheet specified by a URL.\r
62                 public void Load (string url)\r
63                 {\r
64                         if (url == null)\r
65                                 throw new ArgumentNullException ("url");\r
66 \r
67                         FreeStylesheetIfNeeded ();\r
68                         stylesheet = xsltParseStylesheetFile (url);\r
69                         Cleanup ();\r
70                         if (stylesheet == IntPtr.Zero)\r
71                                 throw new XmlException ("Error creating stylesheet");\r
72                 }\r
73 \r
74                 static IntPtr GetStylesheetFromString (string xml)\r
75                 {\r
76                         IntPtr result = IntPtr.Zero;\r
77 \r
78                         IntPtr xmlDoc = xmlParseDoc (xml);\r
79 \r
80                         if (xmlDoc == IntPtr.Zero) {\r
81                                 Cleanup ();\r
82                                 throw new XmlException ("Error parsing stylesheet");\r
83                         }\r
84                                 \r
85                         result = xsltParseStylesheetDoc (xmlDoc);\r
86                         xmlFreeDoc (xmlDoc);\r
87                         Cleanup ();\r
88                         if (result == IntPtr.Zero)\r
89                                 throw new XmlException ("Error creating stylesheet");\r
90 \r
91                         return result;\r
92                 }\r
93 \r
94                 // Loads the XSLT stylesheet contained in the XmlReader\r
95                 public void Load (XmlReader stylesheet)\r
96                 {\r
97                         FreeStylesheetIfNeeded ();\r
98                         // Create a document for the stylesheet\r
99                         XmlDocument doc = new XmlDocument ();\r
100                         doc.Load (stylesheet);\r
101                         \r
102                         // Store the XML in a StringBuilder\r
103                         StringWriter sr = new UTF8StringWriter ();\r
104                         XmlTextWriter writer = new XmlTextWriter (sr);\r
105                         doc.Save (writer);\r
106 \r
107                         this.stylesheet = GetStylesheetFromString (sr.GetStringBuilder ().ToString ());\r
108                 }\r
109 \r
110                 // Loads the XSLT stylesheet contained in the XPathNavigator\r
111                 public void Load (XPathNavigator stylesheet)\r
112                 {\r
113                         FreeStylesheetIfNeeded ();\r
114                         StringWriter sr = new UTF8StringWriter ();\r
115                         Save (stylesheet, sr);\r
116                         this.stylesheet = GetStylesheetFromString (sr.GetStringBuilder ().ToString ());\r
117                 }\r
118 \r
119                 [MonoTODO("use the resolver")]\r
120                 // Loads the XSLT stylesheet contained in the IXPathNavigable.\r
121                 public void Load (IXPathNavigable stylesheet, XmlResolver resolver)\r
122                 {\r
123                         Load (stylesheet);\r
124                 }\r
125 \r
126                 [MonoTODO("use the resolver")]\r
127                 // Loads the XSLT stylesheet specified by a URL.\r
128                 public void Load (string url, XmlResolver resolver)\r
129                 {\r
130                         Load (url);\r
131                 }\r
132 \r
133                 [MonoTODO("use the resolver")]\r
134                 // Loads the XSLT stylesheet contained in the XmlReader\r
135                 public void Load (XmlReader stylesheet, XmlResolver resolver)\r
136                 {\r
137                         Load (stylesheet);\r
138                 }\r
139 \r
140                 [MonoTODO("use the resolver")]\r
141                 // Loads the XSLT stylesheet contained in the XPathNavigator\r
142                 public void Load (XPathNavigator stylesheet, XmlResolver resolver)\r
143                 {\r
144                         Load (stylesheet);\r
145                 }\r
146 \r
147                 // Transforms the XML data in the IXPathNavigable using\r
148                 // the specified args and outputs the result to an XmlReader.\r
149                 public XmlReader Transform (IXPathNavigable input, XsltArgumentList args)\r
150                 {\r
151                         if (input == null)\r
152                                 throw new ArgumentNullException ("input");\r
153 \r
154                         return Transform (input.CreateNavigator (), args);\r
155                 }\r
156 \r
157                 // Transforms the XML data in the input file and outputs\r
158                 // the result to an output file.\r
159                 public void Transform (string inputfile, string outputfile)\r
160                 {\r
161                         IntPtr xmlDocument = IntPtr.Zero;\r
162                         IntPtr resultDocument = IntPtr.Zero;\r
163 \r
164                         try {\r
165                                 xmlDocument = xmlParseFile (inputfile);\r
166                                 if (xmlDocument == IntPtr.Zero)\r
167                                         throw new XmlException ("Error parsing input file");\r
168 \r
169                                 resultDocument = ApplyStylesheet (xmlDocument);\r
170                                 /*\r
171                                 * If I do this, the <?xml version=... is always present *\r
172                                 if (-1 == xsltSaveResultToFilename (outputfile, resultDocument, stylesheet, 0))\r
173                                         throw new XmlException ("Error xsltSaveResultToFilename");\r
174                                 */\r
175                                 StreamWriter writer = new StreamWriter (File.OpenWrite (outputfile));\r
176                                 writer.Write (GetStringFromDocument (resultDocument));\r
177                                 writer.Close ();\r
178                         } finally {\r
179                                 if (xmlDocument != IntPtr.Zero);\r
180                                         xmlFreeDoc (xmlDocument);\r
181 \r
182                                 if (resultDocument != IntPtr.Zero);\r
183                                         xmlFreeDoc (resultDocument);\r
184 \r
185                                 Cleanup ();\r
186                         }\r
187                 }\r
188 \r
189                 IntPtr ApplyStylesheet (IntPtr doc)\r
190                 {\r
191                         if (stylesheet == IntPtr.Zero)\r
192                                 throw new XmlException ("No style sheet!");\r
193 \r
194                         IntPtr result = xsltApplyStylesheet (stylesheet, doc, IntPtr.Zero);\r
195                         if (result == IntPtr.Zero)\r
196                                 throw new XmlException ("Error applying style sheet");\r
197 \r
198                         return result;\r
199                 }\r
200 \r
201                 static void Cleanup ()\r
202                 {\r
203                         xsltCleanupGlobals ();\r
204                         xmlCleanupParser ();\r
205                 }\r
206 \r
207                 static string GetStringFromDocument (IntPtr doc)\r
208                 {\r
209                         IntPtr mem = IntPtr.Zero;\r
210                         int size = 0;\r
211                         xmlDocDumpMemory (doc, ref mem, ref size);\r
212                         if (mem == IntPtr.Zero)\r
213                                 throw new XmlException ("Error dumping document");\r
214 \r
215                         string docStr = Marshal.PtrToStringAnsi (mem, size);\r
216                         // FIXME: Using xmlFree segfaults :-???\r
217                         //xmlFree (mem);\r
218                         Marshal.FreeHGlobal (mem);\r
219                         //\r
220 \r
221                         // Get rid of the <?xml...\r
222                         // FIXME: any other (faster) way that works?\r
223                         StringReader result = new StringReader (docStr);\r
224                         result.ReadLine (); // we want the semantics of line ending used here\r
225                         //\r
226                         return result.ReadToEnd ();\r
227                 }\r
228 \r
229                 string ApplyStylesheetAndGetString (IntPtr doc)\r
230                 {\r
231                         IntPtr xmlOutput = ApplyStylesheet (doc);\r
232                         string strOutput = GetStringFromDocument (xmlOutput);\r
233                         xmlFreeDoc (xmlOutput);\r
234 \r
235                         return strOutput;\r
236                 }\r
237 \r
238                 IntPtr GetDocumentFromNavigator (XPathNavigator nav)\r
239                 {\r
240                         StringWriter sr = new UTF8StringWriter ();\r
241                         Save (nav, sr);\r
242                         IntPtr xmlInput = xmlParseDoc (sr.GetStringBuilder ().ToString ());\r
243                         if (xmlInput == IntPtr.Zero)\r
244                                 throw new XmlException ("Error getting XML from input");\r
245 \r
246                         return xmlInput;\r
247                 }\r
248 \r
249                 [MonoTODO("args")]\r
250                 // Transforms the XML data in the XPathNavigator using\r
251                 // the specified args and outputs the result to an XmlReader.\r
252                 public XmlReader Transform (XPathNavigator input, XsltArgumentList args)\r
253                 {\r
254                         IntPtr xmlInput = GetDocumentFromNavigator (input);\r
255                         string xslOutputString = ApplyStylesheetAndGetString (xmlInput);\r
256                         xmlFreeDoc (xmlInput);\r
257                         Cleanup ();\r
258 \r
259                         return new XmlTextReader (xslOutputString);\r
260                 }\r
261 \r
262                 // Transforms the XML data in the IXPathNavigable using\r
263                 // the specified args and outputs the result to a Stream.\r
264                 public void Transform (IXPathNavigable input, XsltArgumentList args, Stream output)\r
265                 {\r
266                         if (input == null)\r
267                                 throw new ArgumentNullException ("input");\r
268 \r
269                         Transform (input.CreateNavigator (), args, new StreamWriter (output));\r
270                 }\r
271 \r
272                 // Transforms the XML data in the IXPathNavigable using\r
273                 // the specified args and outputs the result to a TextWriter.\r
274                 public void Transform (IXPathNavigable input, XsltArgumentList args, TextWriter output)\r
275                 {\r
276                         if (input == null)\r
277                                 throw new ArgumentNullException ("input");\r
278 \r
279                         Transform (input.CreateNavigator (), args, output);\r
280                 }\r
281 \r
282                 // Transforms the XML data in the IXPathNavigable using\r
283                 // the specified args and outputs the result to an XmlWriter.\r
284                 public void Transform (IXPathNavigable input, XsltArgumentList args, XmlWriter output)\r
285                 {\r
286                         StringWriter writer = new UTF8StringWriter ();\r
287                         Transform (input, args, writer);\r
288                         output.WriteRaw (writer.GetStringBuilder ().ToString ());\r
289                 }\r
290 \r
291                 // Transforms the XML data in the XPathNavigator using\r
292                 // the specified args and outputs the result to a Stream.\r
293                 public void Transform (XPathNavigator input, XsltArgumentList args, Stream output)\r
294                 {\r
295                         Transform (input, args, new StreamWriter (output));\r
296                 }\r
297 \r
298                 // Transforms the XML data in the XPathNavigator using\r
299                 // the specified args and outputs the result to a TextWriter.\r
300                 public void Transform (XPathNavigator input, XsltArgumentList args, TextWriter output)\r
301                 {\r
302                         if (input == null)\r
303                                 throw new ArgumentNullException ("input");\r
304 \r
305                         if (output == null)\r
306                                 throw new ArgumentNullException ("output");\r
307 \r
308                         IntPtr inputDoc = GetDocumentFromNavigator (input);\r
309                         string transform = ApplyStylesheetAndGetString (inputDoc);\r
310                         xmlFreeDoc (inputDoc);\r
311                         Cleanup ();\r
312                         output.Write (transform);\r
313                 }\r
314 \r
315                 // Transforms the XML data in the XPathNavigator using\r
316                 // the specified args and outputs the result to an XmlWriter.\r
317                 public void Transform (XPathNavigator input, XsltArgumentList args, XmlWriter output)\r
318                 {\r
319                         StringWriter writer = new UTF8StringWriter ();\r
320                         Transform (input, args, writer);\r
321                         output.WriteRaw (writer.GetStringBuilder ().ToString ());\r
322                 }\r
323 \r
324                 static void Save (XmlReader rdr, TextWriter baseWriter)\r
325                 {\r
326                         XmlTextWriter writer = new XmlTextWriter (baseWriter);\r
327                 \r
328                         while (rdr.Read ()) {\r
329                                 switch (rdr.NodeType) {\r
330                                 \r
331                                 case XmlNodeType.CDATA:\r
332                                         writer.WriteCData (rdr.Value);\r
333                                         break;\r
334                                 \r
335                                 case XmlNodeType.Comment:\r
336                                         writer.WriteComment (rdr.Value);\r
337                                         break;\r
338 \r
339                                 case XmlNodeType.DocumentType:\r
340                                         writer.WriteDocType (rdr.Value, null, null, null);\r
341                                         break;\r
342 \r
343                                 case XmlNodeType.Element:\r
344                                         writer.WriteStartElement (rdr.Name, rdr.Value);\r
345                                 \r
346                                         while (rdr.MoveToNextAttribute ())\r
347                                                 writer.WriteAttributes (rdr, true);\r
348                                         break;\r
349                         \r
350                                 case XmlNodeType.EndElement:\r
351                                         writer.WriteEndElement ();\r
352                                         break;\r
353 \r
354                                 case XmlNodeType.ProcessingInstruction:\r
355                                         writer.WriteProcessingInstruction (rdr.Name, rdr.Value);\r
356                                         break;\r
357 \r
358                                 case XmlNodeType.Text:\r
359                                         writer.WriteString (rdr.Value);\r
360                                         break;\r
361 \r
362                                 case XmlNodeType.Whitespace:\r
363                                         writer.WriteWhitespace (rdr.Value);\r
364                                         break;\r
365 \r
366                                 case XmlNodeType.XmlDeclaration:\r
367                                         writer.WriteStartDocument ();\r
368                                         break;\r
369                                 }\r
370                         }\r
371 \r
372                         writer.Close ();\r
373                 }\r
374 \r
375                 static void Save (XPathNavigator navigator, TextWriter writer)\r
376                 {\r
377                         XmlTextWriter xmlWriter = new XmlTextWriter (writer);\r
378                         XPathNodeType type = XPathNodeType.All;\r
379 \r
380                         WriteTree (navigator, xmlWriter, type);\r
381                         xmlWriter.WriteEndDocument ();\r
382                         xmlWriter.Flush ();\r
383                 }\r
384 \r
385                 // Walks the XPathNavigator tree recursively \r
386                 static void WriteTree (XPathNavigator navigator, XmlTextWriter writer, XPathNodeType type)\r
387                 {\r
388                         WriteCurrentNode (navigator, writer, ref type);\r
389 \r
390                         if (navigator.MoveToFirstAttribute ()) {\r
391                                 do {\r
392                                         WriteCurrentNode (navigator, writer, ref type);\r
393                                 } while (navigator.MoveToNextAttribute ());\r
394 \r
395                                 navigator.MoveToParent ();\r
396                         }\r
397 \r
398                         if (navigator.MoveToFirstChild ()) {\r
399                                 do {\r
400                                         WriteTree (navigator, writer, type);\r
401                                 } while (navigator.MoveToNext ());\r
402 \r
403                                 navigator.MoveToParent ();\r
404                                 if (navigator.NodeType != XPathNodeType.Root)\r
405                                         writer.WriteEndElement ();\r
406                         }\r
407                 }\r
408 \r
409                 // Format the output  \r
410                 static void WriteCurrentNode (XPathNavigator navigator, XmlTextWriter writer, ref XPathNodeType current_type)\r
411                 {\r
412                         switch (navigator.NodeType) {\r
413                         case XPathNodeType.Root:\r
414                                 current_type = XPathNodeType.Root;\r
415                                 writer.WriteStartDocument ();\r
416                                 break;\r
417                         case XPathNodeType.Attribute:\r
418                                 current_type = XPathNodeType.Attribute;\r
419                                 writer.WriteAttributeString (navigator.LocalName, navigator.Value);\r
420                                 break;\r
421 \r
422                         case XPathNodeType.Comment:\r
423                                 writer.WriteComment (navigator.Value);\r
424                                 break;\r
425 \r
426                         case XPathNodeType.Element:\r
427                                 current_type = XPathNodeType.Element;\r
428                                 writer.WriteStartElement (navigator.Name);\r
429                                 break;\r
430                         \r
431                         case XPathNodeType.ProcessingInstruction:\r
432                                 writer.WriteProcessingInstruction (navigator.Name, navigator.Value);\r
433                                 break;\r
434 \r
435                         case XPathNodeType.Text:\r
436                                 writer.WriteString (navigator.Value);\r
437                                 break;\r
438 \r
439                         case XPathNodeType.SignificantWhitespace:\r
440                         case XPathNodeType.Whitespace:\r
441                                 writer.WriteWhitespace (navigator.Value);\r
442                                 break;\r
443                         }\r
444                 }\r
445 \r
446                 #endregion\r
447 \r
448                 #region Calls to external libraries\r
449                 // libxslt\r
450                 [DllImport ("libxslt")]\r
451                 static extern IntPtr xsltParseStylesheetFile (string filename);\r
452 \r
453                 [DllImport ("libxslt")]\r
454                 static extern IntPtr xsltParseStylesheetDoc (IntPtr docPtr);\r
455 \r
456                 [DllImport ("libxslt")]\r
457                 static extern IntPtr xsltApplyStylesheet (IntPtr stylePtr, IntPtr DocPtr, IntPtr notused);\r
458 \r
459                 [DllImport ("libxslt")]\r
460                 static extern int xsltSaveResultToFilename (string URI, IntPtr doc, IntPtr styleSheet, int compression);\r
461 \r
462                 [DllImport ("libxslt")]\r
463                 static extern void xsltCleanupGlobals ();\r
464 \r
465                 [DllImport ("libxslt")]\r
466                 static extern void xsltFreeStylesheet (IntPtr cur);\r
467 \r
468                 // libxml2\r
469                 [DllImport ("libxml2")]\r
470                 static extern IntPtr xmlNewDoc (string version);\r
471 \r
472                 [DllImport ("libxml2")]\r
473                 static extern int xmlSaveFile (string filename, IntPtr cur);\r
474 \r
475                 [DllImport ("libxml2")]\r
476                 static extern IntPtr xmlParseFile (string filename);\r
477 \r
478                 [DllImport ("libxml2")]\r
479                 static extern IntPtr xmlParseDoc (string document);\r
480 \r
481                 [DllImport ("libxml2", EntryPoint="xmlParseDoc")]\r
482                 static extern IntPtr xmlParseDocUTF16 ([MarshalAs(UnmanagedType.LPWStr)] string document);\r
483 \r
484                 [DllImport ("libxml2")]\r
485                 static extern void xmlFreeDoc (IntPtr doc);\r
486 \r
487                 [DllImport ("libxml2")]\r
488                 static extern void xmlCleanupParser ();\r
489 \r
490                 [DllImport ("libxml2")]\r
491                 static extern void xmlDocDumpMemory (IntPtr doc, ref IntPtr mem, ref int size);\r
492 \r
493                 [DllImport ("libxml2")]\r
494                 static extern void xmlFree (IntPtr data);\r
495 \r
496                 #endregion\r
497 \r
498                 // This class just makes XmlTextWriter use 'encoding="utf-8"'\r
499                 class UTF8StringWriter : StringWriter\r
500                 {\r
501                         static Encoding encoding = new UTF8Encoding (false);\r
502 \r
503                         public override Encoding Encoding {\r
504                                 get {\r
505                                         return encoding;\r
506                                 }\r
507                         }\r
508                 }\r
509         }\r
510 }\r