001/**************************************************************************** 002 * Copyright/Copyleft: 003 * 004 * For this source the LGPL Lesser General Public License, 005 * published by the Free Software Foundation is valid. 006 * It means: 007 * 1) You can use this source without any restriction for any desired purpose. 008 * 2) You can redistribute copies of this source to everybody. 009 * 3) Every user of this source, also the user of redistribute copies 010 * with or without payment, must accept this license for further using. 011 * 4) But the LPGL is not appropriate for a whole software product, 012 * if this source is only a part of them. It means, the user 013 * must publish this part of source, 014 * but don't need to publish the whole source of the own product. 015 * 5) You can study and modify (improve) this source 016 * for own using or for redistribution, but you have to license the 017 * modified sources likewise under this LGPL Lesser General Public License. 018 * You mustn't delete this Copyright/Copyleft inscription in this source file. 019 * 020 * @author Hartmut Schorrig, Germany, Pinzberg 021 * 022 ****************************************************************************/ 023package org.vishia.xml; 024 025 026import java.io.File; 027import java.io.FileNotFoundException; 028import java.io.IOException; 029import java.text.ParseException; 030import java.util.LinkedList; 031import java.util.List; 032import java.util.Properties; 033 034import javax.xml.parsers.ParserConfigurationException; 035import javax.xml.transform.Source; 036import javax.xml.transform.Transformer; 037import javax.xml.transform.TransformerException; 038import javax.xml.transform.TransformerFactory; 039import javax.xml.transform.stream.StreamSource; 040 041 042//import net.sf.saxon.om.NodeInfo; 043 044//import org.xml.sax.SAXException; 045 046//import com.sun.xml.internal.stream.XMLOutputFactoryImpl; 047 048import org.vishia.mainCmd.MainCmd; 049import org.vishia.mainCmd.MainCmdLoggingStream; 050import org.vishia.mainCmd.MainCmdLogging_ifc; 051import org.vishia.mainCmd.Report; 052import org.vishia.mainCmd.MainCmd_ifc; 053import org.vishia.xmlSimple.XmlException; 054import org.vishia.xmlSimple.Xsltpre; 055 056 057/**vishia XSLT Translator. 058 * <br><br> 059 * This translator calls the java-standard XSLT-engine defined with 060 * javax.xml.transform.TransformerFactory. The cmdLine-option <code>-xslt:TRANSFORMER</code> 061 * determines, which Transformer is used. The default-value is <code>net.sf.saxon.TransformerFactoryImpl</code>, 062 * therefore this Transformer is recommended to use. It should be provided by the adequate jar-File 063 * specified in the java's-CLASSPATH. 064 * <br><br> 065 * The vishia-XSLT prepares some inputs before calling the outside given Transformer: 066 * <br><br> 067 * <b>Multiple inputs: </b> 068 * <br> 069 * More as one XML-Inputfile is able to use as input. All input file-contents are disposed after a 070 * internal created <code>< root></code> root-Element. It means, the translation file have to be regard 071 * this additional <code>< root></code> root-Element. Write: 072 * <pre> 073 * < xsl:apply-templates match="/root" > 074 * .... 075 * </xsl:apply-templates > 076 * </pre> 077 * instead the often used 078 * <pre> 079 * < xsl:apply-templates match="/" > 080 * .... 081 * </xsl:apply-templates > 082 * </pre> 083 * 084 * <br><br> 085 * <b>Replacing of spaces of input:</b> 086 * <br> 087 * The XML file representation may contain any white spaces (more as one space, line feed, indentation) 088 * between the XML-elements. It is a method of beautification of the appearance of the file content. 089 * The white spaces may be included by any XML-writer independent of the meaning of the content. 090 * But if the input should present a text with formatted inner elements (maybe XHTML), the white spaces 091 * may be interpreted as really text characters. The class {@link org.vishia.xml.XmlExtensions} contains 092 * a static method {@link org.vishia.xml.XmlExtensions.readXmlFileTrimWhiteSpace(File)} which is used here 093 * if an input is designated with the cmdLine option <code>-j:FILE</code>. 094 * <br><br> 095 * <b>Expansion of wikistyle: </b> 096 * <br> 097 * This feature is not implemented yet. The conversion of {@link org.vishia.xmlSimple.WikistyleTextToSimpleXml} 098 * is implemented instead in the ZBNF2Xml-conversion (class {@link org.vishia.zbnf.ZbnfXmlOutput} 099 * and in the class {@link org.vishia.xml.docuGen.CorrectHref} for documentation generation. 100 * It was contained here in the past. It isn't supported yet, because the conversion routine 101 * is changed using the {@link org.vishia.xmlSimple.XmlNode}, which is not directly compatible with JDOM. 102 * An adaption is existing in the {@link org.vishia.xml.XmlNodeJdom}, but it isn't used here yet. 103 * <br><br> 104 * <b>Beautification of output: </b> 105 * <br> 106 * This feature was implemented here in the past before using the SAXON-XSLT. But the SAXON writes the file 107 * in an adequate style itself. 108 * <br><br> 109 * A beautification should generate indentation automatically. But it should regard textual contents: 110 * If any element contains a textual content, its whole content should be written in one line 111 * to prevent additional white spaces which may be confused the textual content. 112 * Such an XML-writer is implemented in {@link {@link org.vishia.xml.XmlExtensions.writeXmlBeautificatedTextFile(Element, File, Charset)}}. 113 * This writer uses JDOM as input. This is the older implementation. 114 * A newer implementation of this feature is present in {@link org.vishia.xmlSimple.SimpleXmlOutputter}. 115 */ 116 117public class Xslt 118{ 119 120 121 /**Version, history and license. 122 * <ul> 123 * <li>2014-06-01 Hartmut new {@link Xslt#Xslt(ClassLoader, String, String)} and {@link #transform()} 124 * to invoke from Java context with a different ClassLoader, used in JZcmd 125 * <li>2013-06-30 Hartmut new: exec() for calling from inside Java 126 * <li>2009-12-29 Hartmut new: The transformer class is now able to define via param -xslt:, so the SAXON isn't compiled fix. 127 * <li>2009-12-29 Hartmut new: A Xslp-file is translated to the xsl-format, param -p:, the xsl is stored if param -t: is given.^ 128 * <li>2009-12-29 Hartmut new: params for translation may be defined, using name=value as param of invocation. 129 * <li>2009-12-29 Hartmut new: The output format isn't fixed to XML, at may be text too. Depending on entry in XSL-File. 130 * <li>2009-31-03 Hartmut: Cmd line argument separator : possible, outputs for help improved. 131 * <li>2006-05-00 Hartmut: creation 132 * </ul> 133 * 134 * <b>Copyright/Copyleft</b>: 135 * For this source the LGPL Lesser General Public License, 136 * published by the Free Software Foundation is valid. 137 * It means: 138 * <ol> 139 * <li> You can use this source without any restriction for any desired purpose. 140 * <li> You can redistribute copies of this source to everybody. 141 * <li> Every user of this source, also the user of redistribute copies 142 * with or without payment, must accept this license for further using. 143 * <li> But the LPGL is not appropriate for a whole software product, 144 * if this source is only a part of them. It means, the user 145 * must publish this part of source, 146 * but don't need to publish the whole source of the own product. 147 * <li> You can study and modify (improve) this source 148 * for own using or for redistribution, but you have to license the 149 * modified sources likewise under this LGPL Lesser General Public License. 150 * You mustn't delete this Copyright/Copyleft inscription in this source file. 151 * </ol> 152 * If you are intent to use this sources without publishing its usage, you can get 153 * a second license subscribing a special contract with the author. 154 * 155 * @author Hartmut Schorrig = hartmut.schorrig@vishia.de 156 * 157 * 158 */ 159 //@SuppressWarnings("hiding") 160 static final public String sVersion = "2014-06-01"; 161 162 163 164 /**Cmdline-argument, set on -y option. Outputfile to output something.*/ 165 protected String sFileOut = null; 166 167 168 /**CmdLine-argument set on -t option: XSLT-File for Transformer. Either it is the primary file, 169 * or, if {@link sFileXslp} is given, it is the name of the output file. 170 */ 171 protected String sFileXslt = null; 172 173 /**CmdLine-argument set on -p option: XSLT-File pre-converted with {@link org.vishia.xmlSimple.Xsltpre}. 174 * If it is null, the {@link sFileXslt is used as input. }. 175 * 176 * NOTE: If the XSLT-File given in {@link sFileXslt}- 177 * element is newer as this given file, the first one will not overwritten by this given file. This feature 178 * may be able to use for fine adjustments of content while debugging a script. 179 */ 180 protected String sFileXslp = null; 181 182 183 protected class Parameter 184 { String name; String value; 185 186 public Parameter(String name, String value) 187 { this.name = name; this.value = value; 188 } 189 } 190 191 protected List<Parameter> params = new LinkedList<Parameter>(); 192 193 194 /**Class which is used by the javax.xml.transform.TransformerFactory 195 * It is set with System.setProperty("javax.xml.transform.TransformerFactory", ...); 196 */ 197 protected String sTransformer = "net.sf.saxon.TransformerFactoryImpl"; 198 199 /**Set on command line option. If true, after xslt translation 200 * the output tree is evaluate for elements with attribute expandWikistyle or WikiFormat. 201 * This elements are expanded. 202 */ 203 protected boolean bWikiFormat = false; 204 205 /**Instance to process input files. This instance holds informations about input files with several reading conditions 206 * and contains a xml parser call. 207 */ 208 protected XmlMReader xmlMReader; 209 210 private TransformerFactory tfactory; 211 212 /*---------------------------------------------------------------------------------------------*/ 213 /** main started from java. 214 * <br><br> 215 * <b>Command line options:</b> 216 * <br><br> 217 * <pre> 218 "invoke { -[i|j|k]:INPUT } [-t:XSLT] [-p:XSLP] -y:OUTPUT [-xslt:TRANSFORMER]"); 219 "-i:INPUT-XML-file"); 220 "-j:INPUT-XML-file, Whitespaces will replaced with 1 space"); 221 "-t:XSLT: xsl-script XML2-compatible, it is output if -p:XSLP is given"); 222 "-p:XSLP: Script pretranslated with Xsltpre, than -t:XSLT will be created if older."); 223 " If no option -t:XSLT is given, XSLT will be created parallel with .xsl as extension."); 224 "-y:OUTPUT-file"); 225 "-xslt:TRANSFORMER Set the class for Transformer-Implementation. Default is " + sTransformer); 226 </pre> 227 * 228 * */ 229 public static void main(String [] sArgs) 230 { 231 exec(sArgs, true); 232 } 233 234 /**Invocation from another java program without exit the JVM 235 * @param sArgs same like {@link #main(String[])} 236 * @return "" or an error String 237 */ 238 public static String exec(String[] sArgs){ return exec(sArgs, false); } 239 240 241 private static String exec(String [] args, boolean shouldExitVM) 242 { Xslt main = new Xslt(); //the main instance 243 CmdLine mainCmdLine = main.new CmdLine(args); //the instance to parse arguments and others. 244 main.console = mainCmdLine; //it may be also another instance based on MainCmd_ifc 245 boolean bOk = true; 246 try{ 247 @SuppressWarnings("unchecked") 248 Class<XmlMReader> classXmlReader = (Class<XmlMReader>)Class.forName("org.vishia.xml.XmlMReaderJdomSaxon"); 249 main.xmlMReader = classXmlReader.newInstance(); 250 } catch(Exception exc){ 251 main.console.setExitErrorLevel(MainCmd_ifc.exitWithArgumentError); 252 bOk = false; 253 } 254 if(bOk){ 255 main.xmlMReader.setReport(main.console); 256 try{ mainCmdLine.parseArguments(); } 257 catch(Exception exception) 258 { main.console.setExitErrorLevel(MainCmd_ifc.exitWithArgumentError); 259 bOk = false; 260 } 261 } 262 if(bOk) 263 { /** The execution class knows the Xslt Main class in form of the MainCmd super class 264 to hold the contact to the command line execution. 265 */ 266 try{ 267 main.console.reportln(Report.fineInfo, "vishia-XSLT with " + main.sTransformer); 268 System.setProperty("javax.xml.transform.TransformerFactory", main.sTransformer); 269 //The SAXON TransformerFactory is instantiated because above System.setProperty() 270 main.tfactory = TransformerFactory.newInstance(); 271 main.transform(); 272 } 273 catch(Exception exception) 274 { //catch the last level of error. No error is reported direct on command line! 275 main.console.report("Uncatched Exception on main level:", exception); 276 main.console.setExitErrorLevel(MainCmd_ifc.exitWithErrors); 277 } 278 } 279 if(shouldExitVM){ mainCmdLine.exit();} 280 return mainCmdLine.getExitErrorLevel() == 0 ? "" : "Xslt error=" + mainCmdLine.getExitErrorLevel(); 281 } 282 283 284 285 /**The inner class CmdLine helps to evaluate the command line arguments 286 * and show help messages on command line. 287 */ 288 class CmdLine extends MainCmd 289 { 290 291 292 /*---------------------------------------------------------------------------------------------*/ 293 /** Constructor of the main class. 294 The command line arguments are parsed here. After them the execute class is created as composition of Xslt. 295 */ 296 private CmdLine(String[] args) 297 { super(args); 298 //:TODO: user, add your help info! 299 //super.addHelpInfo(getAboutInfo()); 300 super.addAboutInfo("XSLT-Translator"); 301 super.addAboutInfo("made by Hartmut Schorrig, 2005..2009-03-31"); 302 super.addHelpInfo("* saxon9.jar, saxon9-jdom.jar, jdom.jar required internally."); 303 super.addHelpInfo("* Multiple input files are able too, all XML-inputs are disposed as child of a <root>"); 304 super.addHelpInfo("* Enhancments of wiki-format texts to XHTML implicitly"); 305 super.addHelpInfo("* Prepared XSL-Script possible, see org.vishia.xmlSimple.Xsltpre"); 306 super.addHelpInfo("invoke { -[i|j|k]:INPUT } [-t:XSLT] [-p:XSLP] -y:OUTPUT [-xslt:TRANSFORMER] {PARAM}"); 307 super.addHelpInfo("-i:INPUT-XML-file"); 308 super.addHelpInfo("-j:INPUT-XML-file, Whitespaces will replaced with 1 space"); 309 //super.addHelpInfo("-k:INPUT-XML-file, before processing expand wiki-Format"); 310 super.addHelpInfo("-t:XSLT: xsl-script XML2-compatible, it is output if -p:XSLP is given"); 311 super.addHelpInfo("-p:XSLP: Script pretranslated with Xsltpre, than -t:XSLT will be created if older."); 312 super.addHelpInfo(" If no option -t:XSLT is given, XSLT will be created parallel with .xsl as extension."); 313 super.addHelpInfo("-y:OUTPUT-file"); 314 super.addHelpInfo("-xslt:TRANSFORMER Set the class for Transformer-Implementation. Default is " + sTransformer); 315 super.addHelpInfo("PARAM: written in form NAME=VALE or {URI}NAME=VALUE. They are available in the XSLT-script as $NAME."); 316 super.addStandardHelpInfo(); 317 318 } 319 320 321 322 323 324 325 /*---------------------------------------------------------------------------------------------*/ 326 /** Tests one argument. This method is invoked from parseArgument. It is abstract in the superclass MainCmd 327 and must be overwritten from the user. 328 @param argc String of the actual parsed argument from cmd line 329 @param nArg number of the argument in order of the command line, the first argument is number 1. 330 @return true is okay, 331 false if the argument doesn't match. The parseArgument method in MainCmd throws an exception, 332 the application should be aborted. 333 */ 334 @Override 335 protected boolean testArgument(String arg, int nArg) 336 { boolean bOk = true; //set to false if the argc is not passed 337 int posArg = (arg.length()>=2 && arg.charAt(2)==':') ? 3 : 2; //with or without : 338 339 if( arg.startsWith("-i")) { xmlMReader.addInputFile(getArgument(posArg)); } 340 else if(arg.startsWith("-j")) { xmlMReader.addInputFile(getArgument(posArg),XmlMReader.mReplaceWhiteSpaceWith1Space); } 341 else if(arg.startsWith("-k")) { xmlMReader.addInputFile(getArgument(posArg),XmlMReader.mExpandWikiFormat); } 342 else if(arg.startsWith("-t")) { sFileXslt = getArgument(posArg); } 343 else if(arg.startsWith("-p")) { sFileXslp = getArgument(posArg); } 344 else if(arg.startsWith("-y")) { sFileOut = getArgument(posArg); } 345 else if(arg.startsWith("-xslt:")) { sTransformer = getArgument(6); } 346 //else if(arg.startsWith("-wikiformat")){ bWikiFormat = true; } 347 else if(arg.startsWith("-w+")) {} //TODO output beautification or not. It depends on the transformer. 348 else if(arg.startsWith("-w-")) {} //TODO output beautification or not. 349 else 350 { int posSep = arg.indexOf('='); 351 if(posSep >0){ 352 String sName = arg.substring(0, posSep); 353 String sValue = arg.substring(posSep+1); 354 params.add(new Parameter(sName, sValue)); 355 } else { 356 bOk=false; 357 } 358 } 359 360 return bOk; 361 } 362 363 /** Invoked from parseArguments if no argument is given. In the default implementation a help info is written 364 * and the application is terminated. The user should overwrite this method if the call without comand line arguments 365 * is meaningfull. 366 * @throws ParseException 367 * 368 */ 369 @Override 370 protected void callWithoutArguments() throws ParseException 371 { //:TODO: overwrite with empty method - if the calling without arguments 372 //having equal rights than the calling with arguments - no special action. 373 super.callWithoutArguments(); //it needn't be overwritten if it is unnecessary 374 } 375 376 377 378 379 /*---------------------------------------------------------------------------------------------*/ 380 /**Checks the cmdline arguments relation together. 381 If there is an inconsistents, a message should be written. It may be also a warning. 382 @return true if successfull, false if failed. 383 */ 384 @Override 385 protected boolean checkArguments() 386 { boolean bOk = true; 387 388 //if(sFileIn.size()==0) { bOk = false; writeError("ERROR argument -i without content.");} 389 390 if(sFileOut == null) { writeWarning("argument -y: no outputfile is given");} 391 else if(sFileOut.length()==0) { bOk = false; writeError("argument -y: without content"); } 392 393 if(sFileXslt == null) { writeWarning("argument -t: no XSLT-file is given"); } 394 else if(sFileXslt.length()==0) { bOk = false; writeError("argument -t: without content"); } 395 396 if(!bOk) setExitErrorLevel(exitWithArgumentError); 397 398 return bOk; 399 400 } 401 }//class CmdLine 402 403 /** Aggregation to the Console implementation class.*/ 404 MainCmdLogging_ifc console; 405 406 407 408 409 /**Used in {@link #main(String[])} 410 * 411 */ 412 private Xslt(){} 413 414 415 416 417 /**Instantiation for Using in a Java/JZcmd-context. 418 * It allows to use a special reader for multi xml files and any XSL translator. 419 * The translator is a standard Java XSLT transformer wich's interfaces are defined in ,,javax.xml.transform,,. 420 * @param javacp The Loader where the reader and translator should be found, 421 * null if they are in the same Java classpath as this. 422 * @param sXmlReader "package.path.Class" of instanceof {@link XmlMReader}. That is a multi XML reader. 423 * @param sXmlTranslator "package.path.Class" of instanceof {@link TransformerFactory}. 424 * @throws ClassNotFoundException 425 * @throws IllegalAccessException 426 * @throws InstantiationException 427 */ 428 public Xslt(ClassLoader javacpArg, String sXmlReader, String sXmlTranslator) throws ClassNotFoundException, InstantiationException, IllegalAccessException{ 429 final ClassLoader javacp = javacpArg == null ? this.getClass().getClassLoader() : javacpArg; 430 @SuppressWarnings("unchecked") 431 Class<XmlMReader> classReader = (Class<XmlMReader>) javacp.loadClass(sXmlReader); 432 xmlMReader = classReader.newInstance(); 433 tfactory = TransformerFactory.newInstance(sXmlTranslator, javacp); 434 console = MainCmd.getLogging_ifc(); 435 if(console == null){ 436 console = new MainCmdLoggingStream(System.out, 3); 437 } 438 } 439 440 441 442 /**Adds an input file with standard handling. 443 * @param sFile 444 */ 445 public void addInputfile(String sFile){ xmlMReader.addInputFile(sFile); } 446 447 /**Adds an input file wich's white spaces are replaced by 1 space. 448 * @param sFile 449 */ 450 public void addInputfileReplaceWith1Space(String sFile){ 451 xmlMReader.addInputFile(sFile,XmlMReader.mReplaceWhiteSpaceWith1Space); 452 } 453 454 /**Adds an input file wich's content is expand as Wikiformat if the tag TODO. 455 * @param sFile 456 */ 457 public void addInputfileExpandWikiformat(String sFile){ 458 xmlMReader.addInputFile(sFile,XmlMReader.mExpandWikiFormat); 459 } 460 461 462 public void setXsltfile(String sFile) { sFileXslt = sFile; } 463 464 465 public void setXslpfile(String sFile) { sFileXslp = sFile; } 466 467 public void setOutputfile(String sFile) { sFileOut = sFile; } 468 469 470 471 472 473 /**Reads the input files and executes the transformation. 474 * If it is called outside of the main, 475 * <ul> 476 * <li>the input files should be set with {@link #addInputfile(String)}, 477 * <li>the transformation file should be set either with {@link #setXsltfile(String)} or {@link #setXslpfile(String)} 478 * <li>and the output file should be set with {@link #setOutfile(String)}. 479 * </ul> 480 * @throws ParserConfigurationException 481 * @throws IOException 482 * @throws SAXException 483 * @throws FileNotFoundException 484 * @throws TransformerException 485 * @throws XmlException 486 */ 487 public void transform() 488 throws ParserConfigurationException, FileNotFoundException, IOException, TransformerException, XmlException 489 { 490 //net.sf.saxon.om.DocumentInfo doc; 491 final Source doc; 492 493 File fileXsl; 494 if(sFileXslp != null){ 495 /**Generate a adequate xsl-file: */ 496 fileXsl = genXslFromXslp(); 497 } else { 498 fileXsl = new File(sFileXslt); 499 } 500 501 doc = xmlMReader.readInputs(tfactory); 502 503 //outputSimple(docRoot, new File("test1.out")); 504 505 Source xslt = new StreamSource(fileXsl); 506 Transformer transformer = tfactory.newTransformer(xslt); 507 Properties oprops = new Properties(); 508 509 for(Parameter param: params){ 510 transformer.setParameter(param.name, param.value); 511 } 512 513 514 //oprops.put("method", "xml"); 515 //transformer.setOutputProperties(oprops); 516 517 XmlMTransformer transformerExec = new XmlMTransformer(doc, transformer); 518 519 if(true) 520 { File fileOut = new File(sFileOut); 521 { //this executes the saxon transformation. 522 transformerExec.transformToFile(fileOut); 523 console.writeInfoln("output written to:" + fileOut.getAbsolutePath()); 524 } 525 } 526 /* 527 else if(false) 528 { 529 NodeInfo transformedTree = transformerExec.transformToTinyTree(); 530 WikiFormat wikiFormat = new WikiFormat(); 531 wikiFormat.process(transformedTree); 532 } 533 else if(false) 534 { org.jdom.Element xmlAfterTransform = transformerExec.transformToJdom(tfactory); 535 ConverterWikistyleTextToXml wikiFormat = new ConverterWikistyleTextToXml(); 536 wikiFormat.testXmlTreeAndConvert(xmlAfterTransform); 537 } 538 */ 539 //transformer.transform(doc, new StreamResult(new FileOutputStream(sFileOut))); 540 //transformer.transform(new DOMSource(doc), 541 // new StreamResult(System.out)); 542 543 console.writeInfoln(""); //new line after all. 544 } 545 546 547 548 549 550 /**Converts the Xslt-file using {@link org.vishia.xmlSimple.Xsltpre} 551 * @return 552 */ 553 private File genXslFromXslp() 554 { 555 if(sFileXslt == null){ 556 int posLastDot = sFileXslp.lastIndexOf('.'); 557 if(posLastDot < 0 || sFileXslp.endsWith(".xsl")){ 558 sFileXslt = sFileXslp + ".xsl"; //append the extension, 559 } else { 560 sFileXslt = sFileXslp.substring(0, posLastDot) + ".xsl"; 561 } 562 } 563 File fileXsl = new File(sFileXslt); //without 'p' 564 File fileXslp = new File(sFileXslp); 565 Xsltpre xsltpre = new Xsltpre(fileXslp, fileXsl); 566 xsltpre.execute(); 567 568 return fileXsl; 569 } 570 571 572 573 574 575} 576 577 578 579