2003-03-11 Alan Tam <Tam@SiuLung.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                         Cleanup ();\r
87                         if (result == IntPtr.Zero)\r
88                                 throw new XmlException ("Error creating stylesheet");\r
89 \r
90                         return result;\r
91                 }\r
92 \r
93                 // Loads the XSLT stylesheet contained in the XmlReader\r
94                 public void Load (XmlReader stylesheet)\r
95                 {\r
96                         FreeStylesheetIfNeeded ();\r
97                         // Create a document for the stylesheet\r
98                         XmlDocument doc = new XmlDocument ();\r
99                         doc.Load (stylesheet);\r
100                         \r
101                         // Store the XML in a StringBuilder\r
102                         StringWriter sr = new UTF8StringWriter ();\r
103                         XmlTextWriter writer = new XmlTextWriter (sr);\r
104                         doc.Save (writer);\r
105 \r
106                         this.stylesheet = GetStylesheetFromString (sr.GetStringBuilder ().ToString ());\r
107                         Cleanup ();\r
108                         if (this.stylesheet == IntPtr.Zero)\r
109                                 throw new XmlException ("Error creating stylesheet");\r
110                 }\r
111 \r
112                 // Loads the XSLT stylesheet contained in the XPathNavigator\r
113                 public void Load (XPathNavigator stylesheet)\r
114                 {\r
115                         FreeStylesheetIfNeeded ();\r
116                         StringWriter sr = new UTF8StringWriter ();\r
117                         Save (stylesheet, sr);\r
118                         this.stylesheet = GetStylesheetFromString (sr.GetStringBuilder ().ToString ());\r
119                         Cleanup ();\r
120                         if (this.stylesheet == IntPtr.Zero)\r
121                                 throw new XmlException ("Error creating stylesheet");\r
122                 }\r
123 \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
127                 {\r
128                         Load (stylesheet);\r
129                 }\r
130 \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
134                 {\r
135                         Load (url);\r
136                 }\r
137 \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
141                 {\r
142                         Load (stylesheet);\r
143                 }\r
144 \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
148                 {\r
149                         Load (stylesheet);\r
150                 }\r
151 \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
155                 {\r
156                         if (input == null)\r
157                                 throw new ArgumentNullException ("input");\r
158 \r
159                         return Transform (input.CreateNavigator (), args);\r
160                 }\r
161 \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
165                 {\r
166                         IntPtr xmlDocument = IntPtr.Zero;\r
167                         IntPtr resultDocument = IntPtr.Zero;\r
168 \r
169                         try {\r
170                                 xmlDocument = xmlParseFile (inputfile);\r
171                                 if (xmlDocument == IntPtr.Zero)\r
172                                         throw new XmlException ("Error parsing input file");\r
173 \r
174                                 resultDocument = ApplyStylesheet (xmlDocument, null);\r
175                                 /*\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
179                                 */\r
180                                 StreamWriter writer = new StreamWriter (File.OpenWrite (outputfile));\r
181                                 writer.Write (GetStringFromDocument (resultDocument));\r
182                                 writer.Close ();\r
183                         } finally {\r
184                                 if (xmlDocument != IntPtr.Zero)\r
185                                         xmlFreeDoc (xmlDocument);\r
186 \r
187                                 if (resultDocument != IntPtr.Zero)\r
188                                         xmlFreeDoc (resultDocument);\r
189 \r
190                                 Cleanup ();\r
191                         }\r
192                 }\r
193 \r
194                 IntPtr ApplyStylesheet (IntPtr doc, string[] argArr)\r
195                 {\r
196                         if (stylesheet == IntPtr.Zero)\r
197                                 throw new XmlException ("No style sheet!");\r
198 \r
199                         IntPtr result = xsltApplyStylesheet (stylesheet, doc, argArr);\r
200                         if (result == IntPtr.Zero)\r
201                                 throw new XmlException ("Error applying style sheet");\r
202 \r
203                         return result;\r
204                 }\r
205 \r
206                 static void Cleanup ()\r
207                 {\r
208                         xsltCleanupGlobals ();\r
209                         xmlCleanupParser ();\r
210                 }\r
211 \r
212                 static string GetStringFromDocument (IntPtr doc)\r
213                 {\r
214                         IntPtr mem = IntPtr.Zero;\r
215                         int size = 0;\r
216                         xmlDocDumpMemory (doc, ref mem, ref size);\r
217                         if (mem == IntPtr.Zero)\r
218                                 throw new XmlException ("Error dumping document");\r
219 \r
220                         string docStr = Marshal.PtrToStringAnsi (mem, size);\r
221                         // FIXME: Using xmlFree segfaults :-???\r
222                         //xmlFree (mem);\r
223                         Marshal.FreeHGlobal (mem);\r
224                         //\r
225 \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
230                         //\r
231                         return result.ReadToEnd ();\r
232                 }\r
233 \r
234                 string ApplyStylesheetAndGetString (IntPtr doc, string[] argArr)\r
235                 {\r
236                         IntPtr xmlOutput = ApplyStylesheet (doc, argArr);\r
237                         string strOutput = GetStringFromDocument (xmlOutput);\r
238                         xmlFreeDoc (xmlOutput);\r
239 \r
240                         return strOutput;\r
241                 }\r
242 \r
243                 IntPtr GetDocumentFromNavigator (XPathNavigator nav)\r
244                 {\r
245                         StringWriter sr = new UTF8StringWriter ();\r
246                         Save (nav, sr);\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
250 \r
251                         return xmlInput;\r
252                 }\r
253 \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
258                 {\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
263                                 int index = 0;\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
271                                         else\r
272                                                 argArr [index++] = "'" + value.ToString() + "'"; // FIXME: How to encode "'"?\r
273                                 }\r
274                                 argArr[index] = null;\r
275                         }\r
276                         string xslOutputString = ApplyStylesheetAndGetString (xmlInput, argArr);\r
277                         xmlFreeDoc (xmlInput);\r
278                         Cleanup ();\r
279 \r
280                         return new XmlTextReader (new StringReader (xslOutputString));\r
281                 }\r
282 \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
286                 {\r
287                         if (input == null)\r
288                                 throw new ArgumentNullException ("input");\r
289 \r
290                         Transform (input.CreateNavigator (), args, new StreamWriter (output));\r
291                 }\r
292 \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
296                 {\r
297                         if (input == null)\r
298                                 throw new ArgumentNullException ("input");\r
299 \r
300                         Transform (input.CreateNavigator (), args, output);\r
301                 }\r
302 \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
306                 {\r
307                         if (input == null)\r
308                                 throw new ArgumentNullException ("input");\r
309 \r
310                         Transform (input.CreateNavigator (), args, output);\r
311                 }\r
312 \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
316                 {\r
317                         Transform (input, args, new StreamWriter (output));\r
318                 }\r
319 \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
324                 {\r
325                         if (input == null)\r
326                                 throw new ArgumentNullException ("input");\r
327 \r
328                         if (output == null)\r
329                                 throw new ArgumentNullException ("output");\r
330 \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
335                                 int index = 0;\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
343                                         else\r
344                                                 argArr [index++] = "'" + value.ToString() + "'"; // FIXME: How to encode "'"?\r
345                                 }\r
346                                 argArr[index] = null;\r
347                         }\r
348                         string transform = ApplyStylesheetAndGetString (inputDoc, argArr);\r
349                         xmlFreeDoc (inputDoc);\r
350                         Cleanup ();\r
351                         output.Write (transform);\r
352                 }\r
353 \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
357                 {\r
358                         StringWriter writer = new UTF8StringWriter ();\r
359                         Transform (input, args, writer);\r
360                         output.WriteRaw (writer.GetStringBuilder ().ToString ());\r
361                 }\r
362 \r
363                 static void Save (XmlReader rdr, TextWriter baseWriter)\r
364                 {\r
365                         XmlTextWriter writer = new XmlTextWriter (baseWriter);\r
366                 \r
367                         while (rdr.Read ()) {\r
368                                 switch (rdr.NodeType) {\r
369                                 \r
370                                 case XmlNodeType.CDATA:\r
371                                         writer.WriteCData (rdr.Value);\r
372                                         break;\r
373                                 \r
374                                 case XmlNodeType.Comment:\r
375                                         writer.WriteComment (rdr.Value);\r
376                                         break;\r
377 \r
378                                 case XmlNodeType.DocumentType:\r
379                                         writer.WriteDocType (rdr.Value, null, null, null);\r
380                                         break;\r
381 \r
382                                 case XmlNodeType.Element:\r
383                                         writer.WriteStartElement (rdr.Name, rdr.Value);\r
384                                 \r
385                                         while (rdr.MoveToNextAttribute ())\r
386                                                 writer.WriteAttributes (rdr, true);\r
387                                         break;\r
388                         \r
389                                 case XmlNodeType.EndElement:\r
390                                         writer.WriteEndElement ();\r
391                                         break;\r
392 \r
393                                 case XmlNodeType.ProcessingInstruction:\r
394                                         writer.WriteProcessingInstruction (rdr.Name, rdr.Value);\r
395                                         break;\r
396 \r
397                                 case XmlNodeType.Text:\r
398                                         writer.WriteString (rdr.Value);\r
399                                         break;\r
400 \r
401                                 case XmlNodeType.Whitespace:\r
402                                         writer.WriteWhitespace (rdr.Value);\r
403                                         break;\r
404 \r
405                                 case XmlNodeType.XmlDeclaration:\r
406                                         writer.WriteStartDocument ();\r
407                                         break;\r
408                                 }\r
409                         }\r
410 \r
411                         writer.Close ();\r
412                 }\r
413 \r
414                 static void Save (XPathNavigator navigator, TextWriter writer)\r
415                 {\r
416                         XmlTextWriter xmlWriter = new XmlTextWriter (writer);\r
417 \r
418                         WriteTree (navigator, xmlWriter);\r
419                         xmlWriter.WriteEndDocument ();\r
420                         xmlWriter.Flush ();\r
421                 }\r
422 \r
423                 // Walks the XPathNavigator tree recursively \r
424                 static void WriteTree (XPathNavigator navigator, XmlTextWriter writer)\r
425                 {\r
426                         WriteCurrentNode (navigator, writer);\r
427 \r
428                         if (navigator.MoveToFirstAttribute ()) {\r
429                                 do {\r
430                                         WriteCurrentNode (navigator, writer);\r
431                                 } while (navigator.MoveToNextAttribute ());\r
432 \r
433                                 navigator.MoveToParent ();\r
434                         }\r
435 \r
436                         if (navigator.MoveToFirstChild ()) {\r
437                                 do {\r
438                                         WriteTree (navigator, writer);\r
439                                 } while (navigator.MoveToNext ());\r
440 \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
446                         }\r
447                 }\r
448 \r
449                 // Format the output  \r
450                 static void WriteCurrentNode (XPathNavigator navigator, XmlTextWriter writer)\r
451                 {\r
452                         switch (navigator.NodeType) {\r
453                         case XPathNodeType.Root:\r
454                                 writer.WriteStartDocument ();\r
455                                 break;\r
456                         case XPathNodeType.Attribute:\r
457                                 writer.WriteAttributeString (navigator.LocalName, navigator.Value);\r
458                                 break;\r
459 \r
460                         case XPathNodeType.Comment:\r
461                                 writer.WriteComment (navigator.Value);\r
462                                 break;\r
463 \r
464                         case XPathNodeType.Element:\r
465                                 writer.WriteStartElement (navigator.Name);\r
466                                 break;\r
467                         \r
468                         case XPathNodeType.ProcessingInstruction:\r
469                                 writer.WriteProcessingInstruction (navigator.Name, navigator.Value);\r
470                                 break;\r
471 \r
472                         case XPathNodeType.Text:\r
473                                 writer.WriteString (navigator.Value);\r
474                                 break;\r
475 \r
476                         case XPathNodeType.SignificantWhitespace:\r
477                         case XPathNodeType.Whitespace:\r
478                                 writer.WriteWhitespace (navigator.Value);\r
479                                 break;\r
480                         }\r
481                 }\r
482 \r
483                 #endregion\r
484 \r
485                 #region Calls to external libraries\r
486                 // libxslt\r
487                 [DllImport ("xslt")]\r
488                 static extern IntPtr xsltParseStylesheetFile (string filename);\r
489 \r
490                 [DllImport ("xslt")]\r
491                 static extern IntPtr xsltParseStylesheetDoc (IntPtr docPtr);\r
492 \r
493                 [DllImport ("xslt")]\r
494                 static extern IntPtr xsltApplyStylesheet (IntPtr stylePtr, IntPtr DocPtr, string[] argPtr);\r
495 \r
496                 [DllImport ("xslt")]\r
497                 static extern int xsltSaveResultToFilename (string URI, IntPtr doc, IntPtr styleSheet, int compression);\r
498 \r
499                 [DllImport ("xslt")]\r
500                 static extern void xsltCleanupGlobals ();\r
501 \r
502                 [DllImport ("xslt")]\r
503                 static extern void xsltFreeStylesheet (IntPtr cur);\r
504 \r
505                 // libxml2\r
506                 [DllImport ("xml2")]\r
507                 static extern IntPtr xmlNewDoc (string version);\r
508 \r
509                 [DllImport ("xml2")]\r
510                 static extern int xmlSaveFile (string filename, IntPtr cur);\r
511 \r
512                 [DllImport ("xml2")]\r
513                 static extern IntPtr xmlParseFile (string filename);\r
514 \r
515                 [DllImport ("xml2")]\r
516                 static extern IntPtr xmlParseDoc (string document);\r
517 \r
518                 [DllImport ("xml2")]\r
519                 static extern void xmlFreeDoc (IntPtr doc);\r
520 \r
521                 [DllImport ("xml2")]\r
522                 static extern void xmlCleanupParser ();\r
523 \r
524                 [DllImport ("xml2")]\r
525                 static extern void xmlDocDumpMemory (IntPtr doc, ref IntPtr mem, ref int size);\r
526 \r
527                 [DllImport ("xml2")]\r
528                 static extern void xmlFree (IntPtr data);\r
529 \r
530                 #endregion\r
531 \r
532                 // This classes just makes the base class use 'encoding="utf-8"'\r
533                 class UTF8StringWriter : StringWriter\r
534                 {\r
535                         static Encoding encoding = new UTF8Encoding (false);\r
536 \r
537                         public override Encoding Encoding {\r
538                                 get {\r
539                                         return encoding;\r
540                                 }\r
541                         }\r
542                 }\r
543         }\r
544 }\r