XslTransform.cs 15 KB

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