001/* Utilities that manipulate strings using XSLT. 002 003 Copyright (c) 2002-2018 The Regents of the University of California. 004 All rights reserved. 005 Permission is hereby granted, without written agreement and without 006 license or royalty fees, to use, copy, modify, and distribute this 007 software and its documentation for any purpose, provided that the above 008 copyright notice and the following two paragraphs appear in all copies 009 of this software. 010 011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 015 SUCH DAMAGE. 016 017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 022 ENHANCEMENTS, OR MODIFICATIONS. 023 024 PT_COPYRIGHT_VERSION_2 025 COPYRIGHTENDKEY 026 027 */ 028package ptolemy.util; 029 030import java.io.ByteArrayOutputStream; 031import java.io.FileWriter; 032import java.io.IOException; 033import java.io.OutputStream; 034import java.net.URL; 035import java.util.Iterator; 036import java.util.LinkedList; 037import java.util.List; 038 039import javax.xml.parsers.DocumentBuilder; 040import javax.xml.parsers.DocumentBuilderFactory; 041import javax.xml.parsers.ParserConfigurationException; 042import javax.xml.transform.ErrorListener; 043import javax.xml.transform.Transformer; 044import javax.xml.transform.TransformerException; 045import javax.xml.transform.TransformerFactory; 046import javax.xml.transform.dom.DOMResult; 047import javax.xml.transform.dom.DOMSource; 048import javax.xml.transform.stream.StreamResult; 049import javax.xml.transform.stream.StreamSource; 050 051import org.w3c.dom.Document; 052import org.xml.sax.InputSource; 053import org.xml.sax.SAXException; 054 055/////////////////////////////////////////////////////////////////// 056//// XSLTUtilities 057 058/** 059 A collection of utilities for manipulating strings using XSLT. 060 These utilities do not depend on any other ptolemy.* packages. 061 062 <p>This file uses Saxon, the XSLT and XQuery Processor 063 <a href="http://saxon.sourceforge.net/#in_browser" target="_top">http://saxon.sourceforge.net</a>. 064 065 <p>Between Java 1.4.x and Java 1.5, Xalan was removed from the jar 066 files that are shipped. Since Caltrop uses Saxon anyway, we now 067 use Saxon here as well. 068 069 070 @author Christopher Hylands, Haiyang Zheng 071 @version $Id$ 072 @since Ptolemy II 2.1 073 @Pt.ProposedRating Green (eal) 074 Pending Java 1.5 changes 075 @Pt.AcceptedRating Yellow (cxh) 076 */ 077public class XSLTUtilities { 078 /** Instances of this class cannot be created. 079 */ 080 private XSLTUtilities() { 081 } 082 083 /////////////////////////////////////////////////////////////////// 084 //// public methods //// 085 086 /** Apply XSL transforms to an input file and generate an output file. 087 * <p>Example use: 088 * <pre> 089 * java -classpath $PTII ptolemy.util.XSLTUtilities 090 * $PTII/ptolemy/hsif/demo/SwimmingPool/SwimmingPool.xml \ 091 * $PTII/ptolemy/hsif/xsl/GlobalVariablePreprocessor.xsl \ 092 * exportMoMLDTD \ 093 * /tmp/SwimmingPool_1.xml 094 * </pre> 095 096 * @param args At least three arguments: 097 * <ul> 098 * <li> The first argument is the input file name.</li> 099 * <li> The second through n-1 arguments are the named xsl files.</li> 100 * <li> The final argument is the output file name.</li> 101 * </ul> 102 * @exception Exception If there are problems with the transform. 103 */ 104 public static void main(String[] args) throws Exception { 105 106 String lastArg = args[args.length - 1]; 107 String outputFileName = lastArg; 108 int numberOfTransformers = args.length - 2; 109 if (lastArg.compareTo("exportMoMLDTD") == 0) { 110 _exportDTD = true; 111 outputFileName = args[args.length - 2]; 112 numberOfTransformers = args.length - 3; 113 } else { 114 _exportDTD = false; 115 } 116 117 if (_exportDTD && args.length < 4 || !_exportDTD && args.length < 3) { 118 System.err.println("Usage: java -classpath $PTII " 119 + "ptolemy.util.XSLTUtilities inputFile " 120 + "xslFile1 [xslFile2 . . .] outputFile " 121 + "[exportMoMLDTD]"); 122 StringUtilities.exit(2); 123 } 124 125 // Make sure we can write the output first 126 FileWriter fileWriter = null; 127 128 try { 129 fileWriter = new FileWriter(outputFileName); 130 131 Document inputDocument = parse(args[0]); 132 133 List transforms = new LinkedList(); 134 135 for (int i = 1; i < numberOfTransformers + 1; i++) { 136 transforms.add(args[i]); 137 } 138 139 Document outputDocument = XSLTUtilities.transform(inputDocument, 140 transforms); 141 _writeOutput(XSLTUtilities.toString(outputDocument), fileWriter); 142 } finally { 143 if (fileWriter != null) { 144 fileWriter.close(); 145 } 146 } 147 } 148 149 /** Parse an XML document using Saxon. 150 * @param filename The file name of the xml file to be read in 151 * The filename is passed to org.xml.sax.InputSource(String), 152 * so it may be a file name or a URL. 153 * @return the parsed document. 154 * @exception ParserConfigurationException If there is a problem 155 * creating the DocumentBuilder. 156 * @exception IOException If the filename could not be parsed. 157 */ 158 public static Document parse(String filename) 159 throws ParserConfigurationException, IOException { 160 // FIXME: Throw something other than Exception 161 // System.setProperty("javax.xml.parsers.DocumentBuilderFactory", 162 // "net.sf.saxon.om.DocumentBuilderFactoryImpl"); 163 164 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 165 DocumentBuilder builder = factory.newDocumentBuilder(); 166 167 // We use InputSource here so that we can specify the filename 168 // argument as a jar url so that HSIFToMoML works under Web Start. 169 try { 170 return builder.parse(new InputSource(filename)); 171 } catch (SAXException ex) { 172 // Rethrow this with the filename included. 173 IOException exception = new IOException( 174 "Failed to parse '" + filename + "'"); 175 exception.initCause(ex); 176 throw exception; 177 } 178 } 179 180 /** Set the flag indicating whether to export DTD specification when 181 * transforming XML files. By default, the transformer does not export 182 * DTD. 183 * @param exportDTD True for export DTD, false for not. 184 */ 185 public static void setExportDTD(boolean exportDTD) { 186 _exportDTD = exportDTD; 187 } 188 189 /** Given a Document, generate a String. 190 * @param document The document to be converted to a string. 191 * @return A string representation of the Document. 192 * @exception TransformerException If there is a 193 * a problem creating a new Transformer or parser. 194 * @exception IOException If there is a problem closing the output 195 * stream. 196 */ 197 public static String toString(Document document) 198 throws TransformerException, IOException { 199 // FIXME: Joern's sample code had this in it, but if we 200 // include it, then we get Provider not found errors 201 //String defaultDBFI = 202 // System.getProperty("javax.xml.parsers.DocumentBuilderFactory"); 203 //System.setProperty("javax.xml.parsers.DocumentBuilderFactory", 204 // defaultDBFI == null ? "" : defaultDBFI); 205 OutputStream outputStream = null; 206 207 try { 208 outputStream = new ByteArrayOutputStream(); 209 210 StreamResult result = new StreamResult(outputStream); 211 TransformerFactory transformerFactory = TransformerFactory 212 .newInstance(); 213 Transformer serializer = transformerFactory.newTransformer(); 214 serializer.transform(new DOMSource(document), result); 215 return outputStream.toString(); 216 } finally { 217 if (outputStream != null) { 218 outputStream.close(); 219 } 220 } 221 } 222 223 /** Transform a document. 224 * @param inputDocument The Document to be transformed 225 * @param xslFileName The file name of the xsl file to be used. 226 * If the file cannot be found, then we look up the file in the classpath. 227 * @return a transformed document 228 * @exception TransformerException If there is a problem with the 229 * transform. 230 * @exception IOException If there is a problem finding the 231 * transform file. 232 */ 233 public static Document transform(Document inputDocument, String xslFileName) 234 throws TransformerException, IOException { 235 TransformerFactory transformerFactory = TransformerFactory 236 .newInstance(); 237 // Use an ErrorListener so as to avoid error output on stderr 238 // which causes problems with the test harness 239 transformerFactory.setErrorListener(new ErrorListener() { 240 /** Receive notification of a recoverable error. */ 241 @Override 242 public void error(TransformerException exception) 243 throws TransformerException { 244 throw exception; 245 } 246 247 /** Receive notification of a non-recoverable error. */ 248 @Override 249 public void fatalError(TransformerException exception) 250 throws TransformerException { 251 throw exception; 252 } 253 254 /** Receive notification of a warning. */ 255 @Override 256 public void warning(TransformerException exception) { 257 System.err.println("ptolemy.util.XSLTUtilities.transform()" 258 + ": Warning: " + exception); 259 } 260 }); 261 Transformer transformer = null; 262 263 // Set a valid transformer. 264 try { 265 transformer = transformerFactory 266 .newTransformer(new StreamSource(xslFileName)); 267 } catch (javax.xml.transform.TransformerConfigurationException ex) { 268 try { 269 // We might be in the Swing Event thread, so 270 // Thread.currentThread().getContextClassLoader() 271 // .getResource(entry) probably will not work. 272 Class refClass = Class.forName("ptolemy.util.XSLTUtilities"); 273 URL entryURL = refClass.getClassLoader() 274 .getResource(xslFileName); 275 276 if (entryURL != null) { 277 transformer = transformerFactory.newTransformer( 278 new StreamSource(entryURL.toString())); 279 } else { 280 IOException exception = new IOException( 281 "Failed to open '" + xslFileName + "'"); 282 exception.initCause(ex); 283 throw exception; 284 } 285 } catch (Exception ex2) { 286 IOException exception = new IOException( 287 "Failed to open \"" + xslFileName + "\".\n" 288 + "Searching the classpath threw:\n" + ex2); 289 exception.initCause(ex); 290 throw exception; 291 } 292 } 293 294 DOMResult result = new DOMResult(); 295 transformer.transform(new DOMSource(inputDocument), result); 296 return (Document) result.getNode(); 297 } 298 299 /** Transform a document by applying a list of transforms. 300 * @param inputDocument The Document to be transformed 301 * @param xslFileNames A list of Strings naming the 302 * xsl files to be applied sequentially. 303 * @return a transformed document 304 * @exception TransformerException If there is a 305 * a problem creating a new Transformer or parser. 306 * @exception IOException If there is a problem closing the output 307 * stream. 308 */ 309 public static Document transform(Document inputDocument, List xslFileNames) 310 throws TransformerException, IOException { 311 Iterator fileNames = xslFileNames.iterator(); 312 313 while (fileNames.hasNext()) { 314 String fileName = (String) fileNames.next(); 315 inputDocument = transform(inputDocument, fileName); 316 } 317 318 return inputDocument; 319 } 320 321 /** Transform a file by applying a list of XSL transforms. 322 * @param input The XML to be transformed 323 * @param fileWriter A FileWriter that will write to the MoML 324 * file. The caller of this method is responsible for closing 325 * the the FileWriter. 326 * @param xslFileNames A list of Strings naming the 327 * xsl files to be applied sequentially. 328 * @exception ParserConfigurationException If there is a problem 329 * creating the DocumentBuilder. 330 * @exception TransformerException If there is a 331 * a problem with the transform. 332 * @exception IOException If there is a problem 333 * finding a transform file or applying a transform. 334 */ 335 public static void transform(String input, FileWriter fileWriter, 336 List xslFileNames) throws ParserConfigurationException, 337 TransformerException, IOException { 338 // This method takes a FileWriter so that the user can 339 // ensure that the FileWriter exists and is writable before going 340 // through the trouble of doing the conversion. 341 Document inputDocument = null; 342 343 try { 344 inputDocument = XSLTUtilities.parse(input); 345 } catch (IOException ex) { 346 // net.sf.saxon.om.DocumentBuilderImpl.parse() 347 // can throw a javax.xml.transform.TransformerException 348 // which extends Exception, but has IOException as a cause, 349 // so we must catch Exception here, not IOException. 350 // Try it as a jar url 351 try { 352 URL jarURL = ClassUtilities.jarURLEntryResource(input); 353 354 if (jarURL == null) { 355 throw new IOException("'" + input + "' was not a jar " 356 + "URL, or was not found"); 357 } 358 359 inputDocument = XSLTUtilities.parse(jarURL.toString()); 360 } catch (IOException ex2) { 361 // Rethrow the original exception 362 throw ex; 363 } 364 } 365 366 Document outputDocument = XSLTUtilities.transform(inputDocument, 367 xslFileNames); 368 369 _writeOutput(XSLTUtilities.toString(outputDocument), fileWriter); 370 371 // Let the caller close the fileWriter. 372 //fileWriter.close(); 373 } 374 375 /////////////////////////////////////////////////////////////////// 376 //// private methods //// 377 378 private static void _writeOutput(String outputString, FileWriter fileWriter) 379 throws IOException { 380 if (_exportDTD) { 381 // XSLT discards the DTD decalration. 382 // The following code inserts the specified DTD decalrations. 383 // Write the following into the filewriter first: 384 // <?xml version="1.0" encoding="UTF-8"?>, 385 // which appears just before the comment, <!-- 386 int positionToInsertDTD = outputString.indexOf("<!--"); 387 fileWriter.write(outputString, 0, positionToInsertDTD); 388 // FIXME: So far, only MoML DTD can be exported. If the support 389 // of more DTDs is necessray, modify the main() method and 390 // setExportDTD() method to allow configuration of DTD. 391 fileWriter.write( 392 "\r\n<!DOCTYPE entity PUBLIC \"-//UC Berkeley//DTD MoML 1//EN\" " 393 + "\r\n\"http://ptolemy.eecs.berkeley.edu/xml/dtd/MoML_1.dtd\">"); 394 fileWriter.write(outputString.substring(positionToInsertDTD)); 395 } else { 396 fileWriter.write(outputString); 397 } 398 } 399 400 /////////////////////////////////////////////////////////////////// 401 //// private variables //// 402 403 private static boolean _exportDTD = false; 404}