2003-05-11 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                 ~XslTransform ()\r
48                 {\r
49                         FreeStylesheetIfNeeded ();\r
50                 }\r
51 \r
52                 void FreeStylesheetIfNeeded ()\r
53                 {\r
54                         if (stylesheet != IntPtr.Zero) {\r
55                                 xsltFreeStylesheet (stylesheet);\r
56                                 stylesheet = IntPtr.Zero;\r
57                         }\r
58                 }\r
59                 \r
60                 // Loads the XSLT stylesheet contained in the IXPathNavigable.\r
61                 public void Load (IXPathNavigable stylesheet)\r
62                 {\r
63                         Load (stylesheet.CreateNavigator ());\r
64                 }\r
65 \r
66                 // Loads the XSLT stylesheet specified by a URL.\r
67                 public void Load (string url)\r
68                 {\r
69                         if (url == null)\r
70                                 throw new ArgumentNullException ("url");\r
71 \r
72                         FreeStylesheetIfNeeded ();\r
73                         stylesheet = xsltParseStylesheetFile (url);\r
74                         Cleanup ();\r
75                         if (stylesheet == IntPtr.Zero)\r
76                                 throw new XmlException ("Error creating stylesheet");\r
77                 }\r
78 \r
79                 static IntPtr GetStylesheetFromString (string xml)\r
80                 {\r
81                         IntPtr result = IntPtr.Zero;\r
82 \r
83                         IntPtr xmlDoc = xmlParseDoc (xml);\r
84 \r
85                         if (xmlDoc == IntPtr.Zero) {\r
86                                 Cleanup ();\r
87                                 throw new XmlException ("Error parsing stylesheet");\r
88                         }\r
89                                 \r
90                         result = xsltParseStylesheetDoc (xmlDoc);\r
91                         Cleanup ();\r
92                         if (result == IntPtr.Zero)\r
93                                 throw new XmlException ("Error creating stylesheet");\r
94 \r
95                         return result;\r
96                 }\r
97 \r
98                 // Loads the XSLT stylesheet contained in the XmlReader\r
99                 public void Load (XmlReader stylesheet)\r
100                 {\r
101                         FreeStylesheetIfNeeded ();\r
102                         // Create a document for the stylesheet\r
103                         XmlDocument doc = new XmlDocument ();\r
104                         doc.Load (stylesheet);\r
105                         \r
106                         // Store the XML in a StringBuilder\r
107                         StringWriter sr = new UTF8StringWriter ();\r
108                         XmlTextWriter writer = new XmlTextWriter (sr);\r
109                         doc.Save (writer);\r
110 \r
111                         this.stylesheet = GetStylesheetFromString (sr.GetStringBuilder ().ToString ());\r
112                         Cleanup ();\r
113                         if (this.stylesheet == IntPtr.Zero)\r
114                                 throw new XmlException ("Error creating stylesheet");\r
115                 }\r
116 \r
117                 // Loads the XSLT stylesheet contained in the XPathNavigator\r
118                 public void Load (XPathNavigator stylesheet)\r
119                 {\r
120                         FreeStylesheetIfNeeded ();\r
121                         StringWriter sr = new UTF8StringWriter ();\r
122                         Save (stylesheet, sr);\r
123                         this.stylesheet = GetStylesheetFromString (sr.GetStringBuilder ().ToString ());\r
124                         Cleanup ();\r
125                         if (this.stylesheet == IntPtr.Zero)\r
126                                 throw new XmlException ("Error creating stylesheet");\r
127                 }\r
128 \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
132                 {\r
133                         Load (stylesheet);\r
134                 }\r
135 \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
139                 {\r
140                         Load (url);\r
141                 }\r
142 \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
146                 {\r
147                         Load (stylesheet);\r
148                 }\r
149 \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
153                 {\r
154                         Load (stylesheet);\r
155                 }\r
156 \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
160                 {\r
161                         if (input == null)\r
162                                 throw new ArgumentNullException ("input");\r
163 \r
164                         return Transform (input.CreateNavigator (), args);\r
165                 }\r
166 \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
170                 {\r
171                         IntPtr xmlDocument = IntPtr.Zero;\r
172                         IntPtr resultDocument = IntPtr.Zero;\r
173 \r
174                         try {\r
175                                 xmlDocument = xmlParseFile (inputfile);\r
176                                 if (xmlDocument == IntPtr.Zero)\r
177                                         throw new XmlException ("Error parsing input file");\r
178 \r
179                                 resultDocument = ApplyStylesheet (xmlDocument, null);\r
180                                 /*\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
184                                 */\r
185                                 StreamWriter writer = new StreamWriter (File.OpenWrite (outputfile));\r
186                                 writer.Write (GetStringFromDocument (resultDocument));\r
187                                 writer.Close ();\r
188                         } finally {\r
189                                 if (xmlDocument != IntPtr.Zero)\r
190                                         xmlFreeDoc (xmlDocument);\r
191 \r
192                                 if (resultDocument != IntPtr.Zero)\r
193                                         xmlFreeDoc (resultDocument);\r
194 \r
195                                 Cleanup ();\r
196                         }\r
197                 }\r
198 \r
199                 IntPtr ApplyStylesheet (IntPtr doc, string[] argArr)\r
200                 {\r
201                         if (stylesheet == IntPtr.Zero)\r
202                                 throw new XmlException ("No style sheet!");\r
203 \r
204                         IntPtr result = xsltApplyStylesheet (stylesheet, doc, argArr);\r
205                         if (result == IntPtr.Zero)\r
206                                 throw new XmlException ("Error applying style sheet");\r
207 \r
208                         return result;\r
209                 }\r
210 \r
211                 static void Cleanup ()\r
212                 {\r
213                         xsltCleanupGlobals ();\r
214                         xmlCleanupParser ();\r
215                 }\r
216 \r
217                 static string GetStringFromDocument (IntPtr doc)\r
218                 {\r
219                         IntPtr mem = IntPtr.Zero;\r
220                         int size = 0;\r
221                         xmlDocDumpMemory (doc, ref mem, ref size);\r
222                         if (mem == IntPtr.Zero)\r
223                                 throw new XmlException ("Error dumping document");\r
224 \r
225                         string docStr = Marshal.PtrToStringAnsi (mem, size);\r
226                         // FIXME: Using xmlFree segfaults :-???\r
227                         //xmlFree (mem);\r
228                         Marshal.FreeHGlobal (mem);\r
229                         //\r
230 \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
235                         //\r
236                         return result.ReadToEnd ();\r
237                 }\r
238 \r
239                 string ApplyStylesheetAndGetString (IntPtr doc, string[] argArr)\r
240                 {\r
241                         IntPtr xmlOutput = ApplyStylesheet (doc, argArr);\r
242                         string strOutput = GetStringFromDocument (xmlOutput);\r
243                         xmlFreeDoc (xmlOutput);\r
244 \r
245                         return strOutput;\r
246                 }\r
247 \r
248                 IntPtr GetDocumentFromNavigator (XPathNavigator nav)\r
249                 {\r
250                         StringWriter sr = new UTF8StringWriter ();\r
251                         Save (nav, sr);\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
255 \r
256                         return xmlInput;\r
257                 }\r
258 \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
263                 {\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
268                                 int index = 0;\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
276                                         else\r
277                                                 argArr [index++] = "'" + value.ToString() + "'"; // FIXME: How to encode "'"?\r
278                                 }\r
279                                 argArr[index] = null;\r
280                         }\r
281                         string xslOutputString = ApplyStylesheetAndGetString (xmlInput, argArr);\r
282                         xmlFreeDoc (xmlInput);\r
283                         Cleanup ();\r
284 \r
285                         return new XmlTextReader (new StringReader (xslOutputString));\r
286                 }\r
287 \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
291                 {\r
292                         if (input == null)\r
293                                 throw new ArgumentNullException ("input");\r
294 \r
295                         Transform (input.CreateNavigator (), args, new StreamWriter (output));\r
296                 }\r
297 \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
301                 {\r
302                         if (input == null)\r
303                                 throw new ArgumentNullException ("input");\r
304 \r
305                         Transform (input.CreateNavigator (), args, output);\r
306                 }\r
307 \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
311                 {\r
312                         if (input == null)\r
313                                 throw new ArgumentNullException ("input");\r
314 \r
315                         Transform (input.CreateNavigator (), args, output);\r
316                 }\r
317 \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
321                 {\r
322                         Transform (input, args, new StreamWriter (output));\r
323                 }\r
324 \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
329                 {\r
330                         if (input == null)\r
331                                 throw new ArgumentNullException ("input");\r
332 \r
333                         if (output == null)\r
334                                 throw new ArgumentNullException ("output");\r
335 \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
340                                 int index = 0;\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
348                                         else\r
349                                                 argArr [index++] = "'" + value.ToString() + "'"; // FIXME: How to encode "'"?\r
350                                 }\r
351                                 argArr[index] = null;\r
352                         }\r
353                         string transform = ApplyStylesheetAndGetString (inputDoc, argArr);\r
354                         xmlFreeDoc (inputDoc);\r
355                         Cleanup ();\r
356                         output.Write (transform);\r
357                         output.Flush ();\r
358                 }\r
359 \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
363                 {\r
364                         StringWriter writer = new UTF8StringWriter ();\r
365                         Transform (input, args, writer);\r
366                         output.WriteRaw (writer.GetStringBuilder ().ToString ());\r
367                         output.Flush ();\r
368                 }\r
369 \r
370                 static void Save (XmlReader rdr, TextWriter baseWriter)\r
371                 {\r
372                         XmlTextWriter writer = new XmlTextWriter (baseWriter);\r
373                 \r
374                         while (rdr.Read ()) {\r
375                                 switch (rdr.NodeType) {\r
376                                 \r
377                                 case XmlNodeType.CDATA:\r
378                                         writer.WriteCData (rdr.Value);\r
379                                         break;\r
380                                 \r
381                                 case XmlNodeType.Comment:\r
382                                         writer.WriteComment (rdr.Value);\r
383                                         break;\r
384 \r
385                                 case XmlNodeType.DocumentType:\r
386                                         writer.WriteDocType (rdr.Value, null, null, null);\r
387                                         break;\r
388 \r
389                                 case XmlNodeType.Element:\r
390                                         writer.WriteStartElement (rdr.Name, rdr.Value);\r
391                                 \r
392                                         while (rdr.MoveToNextAttribute ())\r
393                                                 writer.WriteAttributes (rdr, true);\r
394                                         break;\r
395                         \r
396                                 case XmlNodeType.EndElement:\r
397                                         writer.WriteEndElement ();\r
398                                         break;\r
399 \r
400                                 case XmlNodeType.ProcessingInstruction:\r
401                                         writer.WriteProcessingInstruction (rdr.Name, rdr.Value);\r
402                                         break;\r
403 \r
404                                 case XmlNodeType.Text:\r
405                                         writer.WriteString (rdr.Value);\r
406                                         break;\r
407 \r
408                                 case XmlNodeType.Whitespace:\r
409                                         writer.WriteWhitespace (rdr.Value);\r
410                                         break;\r
411 \r
412                                 case XmlNodeType.XmlDeclaration:\r
413                                         writer.WriteStartDocument ();\r
414                                         break;\r
415                                 }\r
416                         }\r
417 \r
418                         writer.Close ();\r
419                 }\r
420 \r
421                 static void Save (XPathNavigator navigator, TextWriter writer)\r
422                 {\r
423                         XmlTextWriter xmlWriter = new XmlTextWriter (writer);\r
424 \r
425                         WriteTree (navigator, xmlWriter);\r
426                         xmlWriter.WriteEndDocument ();\r
427                         xmlWriter.Flush ();\r
428                 }\r
429 \r
430                 // Walks the XPathNavigator tree recursively \r
431                 static void WriteTree (XPathNavigator navigator, XmlTextWriter writer)\r
432                 {\r
433                         WriteCurrentNode (navigator, writer);\r
434 \r
435                         if (navigator.MoveToFirstAttribute ()) {\r
436                                 do {\r
437                                         WriteCurrentNode (navigator, writer);\r
438                                 } while (navigator.MoveToNextAttribute ());\r
439 \r
440                                 navigator.MoveToParent ();\r
441                         }\r
442 \r
443                         if (navigator.MoveToFirstChild ()) {\r
444                                 do {\r
445                                         WriteTree (navigator, writer);\r
446                                 } while (navigator.MoveToNext ());\r
447 \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
453                         }\r
454                 }\r
455 \r
456                 // Format the output  \r
457                 static void WriteCurrentNode (XPathNavigator navigator, XmlTextWriter writer)\r
458                 {\r
459                         switch (navigator.NodeType) {\r
460                         case XPathNodeType.Root:\r
461                                 writer.WriteStartDocument ();\r
462                                 break;\r
463                         case XPathNodeType.Attribute:\r
464                                 writer.WriteAttributeString (navigator.LocalName, navigator.Value);\r
465                                 break;\r
466 \r
467                         case XPathNodeType.Comment:\r
468                                 writer.WriteComment (navigator.Value);\r
469                                 break;\r
470 \r
471                         case XPathNodeType.Element:\r
472                                 writer.WriteStartElement (navigator.Name);\r
473                                 break;\r
474                         \r
475                         case XPathNodeType.ProcessingInstruction:\r
476                                 writer.WriteProcessingInstruction (navigator.Name, navigator.Value);\r
477                                 break;\r
478 \r
479                         case XPathNodeType.Text:\r
480                                 writer.WriteString (navigator.Value);\r
481                                 break;\r
482 \r
483                         case XPathNodeType.SignificantWhitespace:\r
484                         case XPathNodeType.Whitespace:\r
485                                 writer.WriteWhitespace (navigator.Value);\r
486                                 break;\r
487                         }\r
488                 }\r
489 \r
490                 #endregion\r
491 \r
492                 #region Calls to external libraries\r
493                 // libxslt\r
494                 [DllImport ("xslt")]\r
495                 static extern IntPtr xsltParseStylesheetFile (string filename);\r
496 \r
497                 [DllImport ("xslt")]\r
498                 static extern IntPtr xsltParseStylesheetDoc (IntPtr docPtr);\r
499 \r
500                 [DllImport ("xslt")]\r
501                 static extern IntPtr xsltApplyStylesheet (IntPtr stylePtr, IntPtr DocPtr, string[] argPtr);\r
502 \r
503                 [DllImport ("xslt")]\r
504                 static extern int xsltSaveResultToFilename (string URI, IntPtr doc, IntPtr styleSheet, int compression);\r
505 \r
506                 [DllImport ("xslt")]\r
507                 static extern void xsltCleanupGlobals ();\r
508 \r
509                 [DllImport ("xslt")]\r
510                 static extern void xsltFreeStylesheet (IntPtr cur);\r
511 \r
512                 // libxml2\r
513                 [DllImport ("xml2")]\r
514                 static extern IntPtr xmlNewDoc (string version);\r
515 \r
516                 [DllImport ("xml2")]\r
517                 static extern int xmlSaveFile (string filename, IntPtr cur);\r
518 \r
519                 [DllImport ("xml2")]\r
520                 static extern IntPtr xmlParseFile (string filename);\r
521 \r
522                 [DllImport ("xml2")]\r
523                 static extern IntPtr xmlParseDoc (string document);\r
524 \r
525                 [DllImport ("xml2")]\r
526                 static extern void xmlFreeDoc (IntPtr doc);\r
527 \r
528                 [DllImport ("xml2")]\r
529                 static extern void xmlCleanupParser ();\r
530 \r
531                 [DllImport ("xml2")]\r
532                 static extern void xmlDocDumpMemory (IntPtr doc, ref IntPtr mem, ref int size);\r
533 \r
534                 [DllImport ("xml2")]\r
535                 static extern void xmlFree (IntPtr data);\r
536 \r
537                 #endregion\r
538 \r
539                 // This classes just makes the base class use 'encoding="utf-8"'\r
540                 class UTF8StringWriter : StringWriter\r
541                 {\r
542                         static Encoding encoding = new UTF8Encoding (false);\r
543 \r
544                         public override Encoding Encoding {\r
545                                 get {\r
546                                         return encoding;\r
547                                 }\r
548                         }\r
549                 }\r
550         }\r
551 }\r