2003-03-10 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 = new string[args.parameters.Count * 2 + 1];\r
261                         int index = 0;\r
262                         foreach (object key in args.parameters.Keys) {\r
263                                 argArr [index++] = key.ToString();\r
264                                 object value = args.parameters [key];\r
265                                 if (value is Boolean)\r
266                                         argArr [index++] = XmlConvert.ToString((bool) value); // FIXME: How to encode it for libxslt?\r
267                                 else if (value is Double)\r
268                                         argArr [index++] = XmlConvert.ToString((double) value); // FIXME: How to encode infinity's and Nan?\r
269                                 else\r
270                                         argArr [index++] = "'" + value.ToString() + "'"; // FIXME: How to encode "'"?\r
271                         }\r
272                         argArr[index] = null;\r
273                         string xslOutputString = ApplyStylesheetAndGetString (xmlInput, argArr);\r
274                         xmlFreeDoc (xmlInput);\r
275                         Cleanup ();\r
276 \r
277                         return new XmlTextReader (new StringReader (xslOutputString));\r
278                 }\r
279 \r
280                 // Transforms the XML data in the IXPathNavigable using\r
281                 // the specified args and outputs the result to a Stream.\r
282                 public void Transform (IXPathNavigable input, XsltArgumentList args, Stream output)\r
283                 {\r
284                         if (input == null)\r
285                                 throw new ArgumentNullException ("input");\r
286 \r
287                         Transform (input.CreateNavigator (), args, new StreamWriter (output));\r
288                 }\r
289 \r
290                 // Transforms the XML data in the IXPathNavigable using\r
291                 // the specified args and outputs the result to a TextWriter.\r
292                 public void Transform (IXPathNavigable input, XsltArgumentList args, TextWriter output)\r
293                 {\r
294                         if (input == null)\r
295                                 throw new ArgumentNullException ("input");\r
296 \r
297                         Transform (input.CreateNavigator (), args, output);\r
298                 }\r
299 \r
300                 // Transforms the XML data in the IXPathNavigable using\r
301                 // the specified args and outputs the result to an XmlWriter.\r
302                 public void Transform (IXPathNavigable input, XsltArgumentList args, XmlWriter output)\r
303                 {\r
304                         if (input == null)\r
305                                 throw new ArgumentNullException ("input");\r
306 \r
307                         Transform (input.CreateNavigator (), args, output);\r
308                 }\r
309 \r
310                 // Transforms the XML data in the XPathNavigator using\r
311                 // the specified args and outputs the result to a Stream.\r
312                 public void Transform (XPathNavigator input, XsltArgumentList args, Stream output)\r
313                 {\r
314                         Transform (input, args, new StreamWriter (output));\r
315                 }\r
316 \r
317                 // Transforms the XML data in the XPathNavigator using\r
318                 // the specified args and outputs the result to a TextWriter.\r
319                 [MonoTODO("Node Set and Node Fragment Parameters and Extension Objects")]\r
320                 public void Transform (XPathNavigator input, XsltArgumentList args, TextWriter output)\r
321                 {\r
322                         if (input == null)\r
323                                 throw new ArgumentNullException ("input");\r
324 \r
325                         if (output == null)\r
326                                 throw new ArgumentNullException ("output");\r
327 \r
328                         IntPtr inputDoc = GetDocumentFromNavigator (input);\r
329                         string[] argArr = new string[args.parameters.Count * 2 + 1];\r
330                         int index = 0;\r
331                         foreach (object key in args.parameters.Keys) {\r
332                                 argArr [index++] = key.ToString();\r
333                                 object value = args.parameters [key];\r
334                                 if (value is Boolean)\r
335                                         argArr [index++] = XmlConvert.ToString((bool) value); // FIXME: How to encode it for libxslt?\r
336                                 else if (value is Double)\r
337                                         argArr [index++] = XmlConvert.ToString((double) value); // FIXME: How to encode infinity's and Nan?\r
338                                 else\r
339                                         argArr [index++] = "'" + value.ToString() + "'"; // FIXME: How to encode "'"?\r
340                         }\r
341                         argArr[index] = null;\r
342                         string transform = ApplyStylesheetAndGetString (inputDoc, argArr);\r
343                         xmlFreeDoc (inputDoc);\r
344                         Cleanup ();\r
345                         output.Write (transform);\r
346                 }\r
347 \r
348                 // Transforms the XML data in the XPathNavigator using\r
349                 // the specified args and outputs the result to an XmlWriter.\r
350                 public void Transform (XPathNavigator input, XsltArgumentList args, XmlWriter output)\r
351                 {\r
352                         StringWriter writer = new UTF8StringWriter ();\r
353                         Transform (input, args, writer);\r
354                         output.WriteRaw (writer.GetStringBuilder ().ToString ());\r
355                 }\r
356 \r
357                 static void Save (XmlReader rdr, TextWriter baseWriter)\r
358                 {\r
359                         XmlTextWriter writer = new XmlTextWriter (baseWriter);\r
360                 \r
361                         while (rdr.Read ()) {\r
362                                 switch (rdr.NodeType) {\r
363                                 \r
364                                 case XmlNodeType.CDATA:\r
365                                         writer.WriteCData (rdr.Value);\r
366                                         break;\r
367                                 \r
368                                 case XmlNodeType.Comment:\r
369                                         writer.WriteComment (rdr.Value);\r
370                                         break;\r
371 \r
372                                 case XmlNodeType.DocumentType:\r
373                                         writer.WriteDocType (rdr.Value, null, null, null);\r
374                                         break;\r
375 \r
376                                 case XmlNodeType.Element:\r
377                                         writer.WriteStartElement (rdr.Name, rdr.Value);\r
378                                 \r
379                                         while (rdr.MoveToNextAttribute ())\r
380                                                 writer.WriteAttributes (rdr, true);\r
381                                         break;\r
382                         \r
383                                 case XmlNodeType.EndElement:\r
384                                         writer.WriteEndElement ();\r
385                                         break;\r
386 \r
387                                 case XmlNodeType.ProcessingInstruction:\r
388                                         writer.WriteProcessingInstruction (rdr.Name, rdr.Value);\r
389                                         break;\r
390 \r
391                                 case XmlNodeType.Text:\r
392                                         writer.WriteString (rdr.Value);\r
393                                         break;\r
394 \r
395                                 case XmlNodeType.Whitespace:\r
396                                         writer.WriteWhitespace (rdr.Value);\r
397                                         break;\r
398 \r
399                                 case XmlNodeType.XmlDeclaration:\r
400                                         writer.WriteStartDocument ();\r
401                                         break;\r
402                                 }\r
403                         }\r
404 \r
405                         writer.Close ();\r
406                 }\r
407 \r
408                 static void Save (XPathNavigator navigator, TextWriter writer)\r
409                 {\r
410                         XmlTextWriter xmlWriter = new XmlTextWriter (writer);\r
411 \r
412                         WriteTree (navigator, xmlWriter);\r
413                         xmlWriter.WriteEndDocument ();\r
414                         xmlWriter.Flush ();\r
415                 }\r
416 \r
417                 // Walks the XPathNavigator tree recursively \r
418                 static void WriteTree (XPathNavigator navigator, XmlTextWriter writer)\r
419                 {\r
420                         WriteCurrentNode (navigator, writer);\r
421 \r
422                         if (navigator.MoveToFirstAttribute ()) {\r
423                                 do {\r
424                                         WriteCurrentNode (navigator, writer);\r
425                                 } while (navigator.MoveToNextAttribute ());\r
426 \r
427                                 navigator.MoveToParent ();\r
428                         }\r
429 \r
430                         if (navigator.MoveToFirstChild ()) {\r
431                                 do {\r
432                                         WriteTree (navigator, writer);\r
433                                 } while (navigator.MoveToNext ());\r
434 \r
435                                 navigator.MoveToParent ();\r
436                                 if (navigator.NodeType != XPathNodeType.Root)\r
437                                         writer.WriteEndElement ();\r
438                         } else if (navigator.NodeType == XPathNodeType.Element) {\r
439                                 writer.WriteEndElement ();\r
440                         }\r
441                 }\r
442 \r
443                 // Format the output  \r
444                 static void WriteCurrentNode (XPathNavigator navigator, XmlTextWriter writer)\r
445                 {\r
446                         switch (navigator.NodeType) {\r
447                         case XPathNodeType.Root:\r
448                                 writer.WriteStartDocument ();\r
449                                 break;\r
450                         case XPathNodeType.Attribute:\r
451                                 writer.WriteAttributeString (navigator.LocalName, navigator.Value);\r
452                                 break;\r
453 \r
454                         case XPathNodeType.Comment:\r
455                                 writer.WriteComment (navigator.Value);\r
456                                 break;\r
457 \r
458                         case XPathNodeType.Element:\r
459                                 writer.WriteStartElement (navigator.Name);\r
460                                 break;\r
461                         \r
462                         case XPathNodeType.ProcessingInstruction:\r
463                                 writer.WriteProcessingInstruction (navigator.Name, navigator.Value);\r
464                                 break;\r
465 \r
466                         case XPathNodeType.Text:\r
467                                 writer.WriteString (navigator.Value);\r
468                                 break;\r
469 \r
470                         case XPathNodeType.SignificantWhitespace:\r
471                         case XPathNodeType.Whitespace:\r
472                                 writer.WriteWhitespace (navigator.Value);\r
473                                 break;\r
474                         }\r
475                 }\r
476 \r
477                 #endregion\r
478 \r
479                 #region Calls to external libraries\r
480                 // libxslt\r
481                 [DllImport ("xslt")]\r
482                 static extern IntPtr xsltParseStylesheetFile (string filename);\r
483 \r
484                 [DllImport ("xslt")]\r
485                 static extern IntPtr xsltParseStylesheetDoc (IntPtr docPtr);\r
486 \r
487                 [DllImport ("xslt")]\r
488                 static extern IntPtr xsltApplyStylesheet (IntPtr stylePtr, IntPtr DocPtr, string[] argPtr);\r
489 \r
490                 [DllImport ("xslt")]\r
491                 static extern int xsltSaveResultToFilename (string URI, IntPtr doc, IntPtr styleSheet, int compression);\r
492 \r
493                 [DllImport ("xslt")]\r
494                 static extern void xsltCleanupGlobals ();\r
495 \r
496                 [DllImport ("xslt")]\r
497                 static extern void xsltFreeStylesheet (IntPtr cur);\r
498 \r
499                 // libxml2\r
500                 [DllImport ("xml2")]\r
501                 static extern IntPtr xmlNewDoc (string version);\r
502 \r
503                 [DllImport ("xml2")]\r
504                 static extern int xmlSaveFile (string filename, IntPtr cur);\r
505 \r
506                 [DllImport ("xml2")]\r
507                 static extern IntPtr xmlParseFile (string filename);\r
508 \r
509                 [DllImport ("xml2")]\r
510                 static extern IntPtr xmlParseDoc (string document);\r
511 \r
512                 [DllImport ("xml2")]\r
513                 static extern void xmlFreeDoc (IntPtr doc);\r
514 \r
515                 [DllImport ("xml2")]\r
516                 static extern void xmlCleanupParser ();\r
517 \r
518                 [DllImport ("xml2")]\r
519                 static extern void xmlDocDumpMemory (IntPtr doc, ref IntPtr mem, ref int size);\r
520 \r
521                 [DllImport ("xml2")]\r
522                 static extern void xmlFree (IntPtr data);\r
523 \r
524                 #endregion\r
525 \r
526                 // This classes just makes the base class use 'encoding="utf-8"'\r
527                 class UTF8StringWriter : StringWriter\r
528                 {\r
529                         static Encoding encoding = new UTF8Encoding (false);\r
530 \r
531                         public override Encoding Encoding {\r
532                                 get {\r
533                                         return encoding;\r
534                                 }\r
535                         }\r
536                 }\r
537         }\r
538 }\r