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