View Javadoc
1   /*
2    * #%L
3    * JRst :: Api
4    * %%
5    * Copyright (C) 2004 - 2014 CodeLutin, Chatellier Eric
6    * %%
7    * This program is free software: you can redistribute it and/or modify
8    * it under the terms of the GNU Lesser General Public License as 
9    * published by the Free Software Foundation, either version 3 of the 
10   * License, or (at your option) any later version.
11   * 
12   * This program is distributed in the hope that it will be useful,
13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   * GNU General Lesser Public License for more details.
16   * 
17   * You should have received a copy of the GNU General Lesser Public 
18   * License along with this program.  If not, see
19   * <http://www.gnu.org/licenses/lgpl-3.0.html>.
20   * #L%
21   */
22  package org.nuiton.jrst;
23  
24  import java.awt.GraphicsDevice;
25  import java.awt.GraphicsEnvironment;
26  import java.io.ByteArrayInputStream;
27  import java.io.ByteArrayOutputStream;
28  import java.io.File;
29  import java.io.FileNotFoundException;
30  import java.io.FileOutputStream;
31  import java.io.IOException;
32  import java.io.InputStreamReader;
33  import java.io.OutputStream;
34  import java.io.OutputStreamWriter;
35  import java.io.Reader;
36  import java.net.URL;
37  import java.util.HashMap;
38  import java.util.List;
39  import java.util.Locale;
40  import java.util.Map;
41  
42  import javax.xml.parsers.DocumentBuilder;
43  import javax.xml.parsers.DocumentBuilderFactory;
44  import javax.xml.transform.TransformerException;
45  
46  import org.apache.commons.io.FileUtils;
47  import org.apache.commons.logging.Log;
48  import org.apache.commons.logging.LogFactory;
49  import org.dom4j.Document;
50  import org.dom4j.DocumentException;
51  import org.dom4j.DocumentHelper;
52  import org.nuiton.config.ApplicationConfig;
53  import org.nuiton.i18n.I18n;
54  import org.nuiton.i18n.init.ClassPathI18nInitializer;
55  import org.nuiton.jrst.convertisor.DocUtils2RST;
56  import org.nuiton.jrst.convertisor.DocUtilsVisitor;
57  import org.nuiton.jrst.legacy.JRSTReader;
58  import org.nuiton.jrst.ui.JRSTView;
59  import org.nuiton.util.Resource;
60  import org.nuiton.util.StringUtil;
61  import org.python.util.PythonInterpreter;
62  import org.xhtmlrenderer.pdf.ITextRenderer;
63  
64  /**
65   * FIXME: 'JRST --help' doesn't work, but 'JRST --help toto' work :( FIXME:
66   * 'JRST -c' doesn't work, but 'JRST -c toto'
67   * <p/>
68   * Created: 3 nov. 06 20:56:00
69   *
70   * @author poussin
71   * @version $Revision$
72   *          <p/>
73   *          Last update: $Date$
74   *          by : $Author$
75   */
76  public class JRST {
77  
78      public static final String UTF_8 = "UTF-8";
79  
80      public static final String DOCUTILS_LAUNCHER = "__run__.py";
81  
82      public static final String IMPORT_SCRIPT = "import __run__";
83  
84      public static final String WINDOWS_NAME = "win";
85  
86      public static final String OS_NAME = "os.name";
87  
88      public static final String BANG = "!";
89  
90      public static final String FILE_URI_PREFIX = "file:";
91  
92      public enum Overwrite {
93          NEVER, IFNEWER, ALLTIME
94      }
95  
96      /** to use log facility, just put in your code: log.info("..."); */
97      protected static Log log = LogFactory.getLog(JRST.class);
98  
99      /** XSL Stylesheet to transform RST into HTML. */
100     protected static final String RST_2_XHTML = "/xsl/rst2xhtml.xsl";
101 
102     /** XSL Stylesheet to transform RST into HTML (but only inner body fragment). */
103     protected static final String XSL_RST_2_XHTML_INNER_BODY_XSL = "/xsl/rst2xhtmlInnerBody.xsl";
104 
105     /** XSL Stylesheet to transform RST into Xdoc. */
106     protected static final String RST_2_XDOC = "/xsl/rst2xdoc.xsl";
107 
108     /** XSL Stylesheet to transform RST into Docbook. */
109     protected static final String RST_2_DOCBOOK = "/xsl/dn2dbk.xsl";
110 
111     /** XSL Stylesheet to transform Docbook into xHTML. */
112     protected static final String DOCBOOK_2_XHTML = "/docbook/xhtml/docbook.xsl";
113 
114     /** XSL Stylesheet to transform Docbook into javahelp. */
115     protected static final String DOCBOOK_2_JAVAHELP = "/docbook/javahelp/javahelp.xsl";
116 
117     /** XSL Stylesheet to transform Docbook into htmlhelp. */
118     protected static final String DOCBOOK_2_HTMLHELP = "/docbook/htmlhelp/htmlhelp.xsl";
119 
120     /** XSL Stylesheet to transform Docbook into PDF. */
121     protected static final String DOCBOOK_2_FO = "/docbook/fo/docbook.xsl";
122 
123     public static final String PATTERN_TYPE = "xml|xhtml|docbook|html|htmlInnerBody|xdoc|fo|pdf";
124 
125     /** HTML output format type */
126     public static final String TYPE_HTML = "html";
127 
128     /** HTML output format type */
129     public static final String TYPE_HTML_INNER_BODY = "htmlInnerBody";
130 
131     /** XDOC output format type */
132     public static final String TYPE_XDOC = "xdoc";
133 
134     /** DOCBOOK output format type */
135     public static final String TYPE_DOCBOOK = "docbook";
136 
137     /** XHTML output format type */
138     public static final String TYPE_XHTML = "xhtml";
139 
140     /** JAVA HELP output format type */
141     public static final String TYPE_JAVAHELP = "javahelp";
142 
143     /** HTML HELP output format type */
144     public static final String TYPE_HTMLHELP = "htmlhelp";
145 
146     /** ODT output format type */
147     public static final String TYPE_ODT = "odt";
148 
149     /** FO output format type */
150     public static final String TYPE_FO = "fo";
151 
152     /** PDF output format type */
153     public static final String TYPE_PDF = "pdf";
154 
155     /** XML output format type */
156     public static final String TYPE_XML = "xml";
157 
158     /** key, Out type; value: chain of XSL file to provide wanted file for output */
159     protected static Map<String, String> stylesheets;
160 
161     /** Mime type associated with type */
162     protected static Map<String, String> mimeType;
163 
164     static {
165         stylesheets = new HashMap<String, String>();
166         stylesheets.put(TYPE_HTML, RST_2_XHTML);
167         stylesheets.put(TYPE_HTML_INNER_BODY, XSL_RST_2_XHTML_INNER_BODY_XSL);
168         stylesheets.put(TYPE_XDOC, RST_2_XDOC);
169         stylesheets.put(TYPE_DOCBOOK, RST_2_DOCBOOK);
170         stylesheets.put(TYPE_XHTML, RST_2_DOCBOOK + "," + DOCBOOK_2_XHTML);
171         stylesheets.put(TYPE_JAVAHELP, RST_2_DOCBOOK + "," + DOCBOOK_2_JAVAHELP);
172         stylesheets.put(TYPE_HTMLHELP, RST_2_DOCBOOK + "," + DOCBOOK_2_HTMLHELP);
173         stylesheets.put(TYPE_FO, RST_2_DOCBOOK + "," + DOCBOOK_2_FO);
174         stylesheets.put(TYPE_PDF, RST_2_XHTML);
175 
176         mimeType = new HashMap<String, String>();
177         mimeType.put(TYPE_HTML, "text/html");
178         mimeType.put(TYPE_HTML_INNER_BODY, "text/html");
179         mimeType.put(TYPE_XDOC, "text/xml");
180         mimeType.put(TYPE_DOCBOOK, "text/xml");
181         mimeType.put(TYPE_XHTML, "text/html");
182         mimeType.put(TYPE_JAVAHELP, "text/plain");
183         mimeType.put(TYPE_HTMLHELP, "text/html");
184         mimeType.put(TYPE_ODT, "application/vnd.oasis.opendocument.text");
185         mimeType.put(TYPE_FO, "text/xml");
186         mimeType.put(TYPE_PDF, "application/pdf");
187 
188     }
189 
190     /**
191      * Main method.
192      *
193      * @param args main args
194      * @throws Exception
195      */
196     public static void main(String... args) throws Exception {
197 
198         I18n.init(new ClassPathI18nInitializer(), Locale.UK);
199 
200         if (args.length == 0) {
201             askOption();
202         } else {
203 
204             ApplicationConfig config = JRSTConfig.getConfig(args);
205             config.doAction(0);
206 
207             // parse options
208             String xslList = JRSTConfigOption.XSL_FILE.getOption(config);
209             if (xslList == null) {
210                 xslList = JRSTConfigOption.OUT_TYPE.getOption(config);
211             }
212             List<String> unparsed = config.getUnparsed();
213             if (unparsed.isEmpty()) {
214                 JRSTConfig.help();
215             }
216             File inputFile = new File(config.getUnparsed().get(0));
217             File ouputFile = JRSTConfigOption.OUT_FILE.getOptionAsFile(config);
218             Overwrite overwrite = Overwrite.NEVER;
219             if (JRSTConfigOption.FORCE.getOptionAsBoolean(config)) {
220                 overwrite = Overwrite.ALLTIME;
221             }
222             boolean simpleGeneration = false;
223             if (JRSTConfigOption.SIMPLE.getOptionAsBoolean(config)) {
224                 simpleGeneration = true;
225             }
226 
227             generate(xslList, inputFile, ouputFile, overwrite, simpleGeneration);
228         }
229     }
230 
231     private static void askOption() throws SecurityException,
232             NoSuchMethodException, IOException {
233         try {
234             GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
235             GraphicsDevice[] gs = ge.getScreenDevices();
236             if (!(gs == null)) {
237                 if (gs.length > 0) {
238                     askOptionGraph();
239                 }
240             }
241         } catch (java.awt.HeadlessException e) {
242             log.error("Can't generate document", e);
243         }
244     }
245 
246     /**
247      * Graphical user interface
248      *
249      * @throws SecurityException
250      * @throws NoSuchMethodException
251      */
252     protected static void askOptionGraph() throws SecurityException,
253             NoSuchMethodException {
254         JRSTView jrstView = new JRSTView();
255         jrstView.pack();
256         jrstView.setVisible(true);
257     }
258 
259     /**
260      * Transforms a Restructured Text (ReST) file to another type ( html, xdoc, pdf, etc... )
261      *
262      * @param outputType The type of the output file ( html, xdoc, pdf, etc... )
263      * @param fileIn     The restructured text input file (rst)
264      * @param fileOut    The output file
265      * @param overwrite  The rule to overwrite file (NEVER, IFNEWER or ALLTIME )
266      * @throws Exception
267      */
268     public static void generate(String outputType, File fileIn,
269                                 File fileOut, Overwrite overwrite,
270                                 boolean simpleGeneration) throws Exception {
271         if (fileOut != null
272             && fileOut.exists()
273             && (overwrite == Overwrite.NEVER || (overwrite == Overwrite.IFNEWER && FileUtils
274                 .isFileNewer(fileIn, fileOut)))) {
275 
276             log.info("Don't generate file " + fileOut
277                      + ", because already exists");
278         } else {
279             Document doc;
280 
281             JRSTToXmlStrategy strategy;
282             if (simpleGeneration) {
283                 strategy = new JRSTToXmlStrategyJRSTReader();
284             } else {
285                 strategy = new JRSTToXmlStrategyDocutils();
286             }
287 
288             doc = strategy.generateRstToXml(fileIn, UTF_8);
289 
290             // Application of xsl stylesheets
291             doc = generateXml(doc, outputType);
292 
293             // generation PDF
294             if (outputType.equals("pdf")) {
295                 generatePdf(doc, fileIn, fileOut);
296             } else {
297                 generateFile(doc, fileOut);
298             }
299         }
300     }
301 
302     /**
303      * Transforms a Restructured Text (ReST) file to pdf
304      *
305      * @param fileIn    The restructured text input file (rst)
306      * @param fileOut   The output file
307      * @param overwrite The rule to overwrite file (NEVER, IFNEWER or ALLTIME )
308      * @param doc       document to generate
309      * @throws Exception
310      */
311     public static void generatePdf(File fileIn, File fileOut, Overwrite overwrite,
312                                    Document doc) throws Exception {
313         if (fileOut != null
314             && fileOut.exists()
315             && (overwrite == Overwrite.NEVER || (overwrite == Overwrite.IFNEWER && FileUtils
316                 .isFileNewer(fileIn, fileOut)))) {
317 
318             log.info("Don't generate file " + fileOut
319                      + ", because already exists");
320         } else {
321 
322             // Application of xsl stylesheets
323             doc = generateXml(doc, "pdf");
324 
325             // generation PDF
326             generatePdf(doc, fileIn, fileOut);
327 
328         }
329     }
330 
331     /**
332      * Transforms a restructured text file to a XML file using JRST parser (used with option --simple)
333      *
334      * @param fileIn   Input restructured text file (.rst)
335      * @param encoding Output file encoding
336      * @return A document which contains XML code
337      * @throws Exception
338      */
339     public static Document generateSimpleDoc(File fileIn, String encoding) throws Exception {
340         URL url = fileIn.toURI().toURL();
341         Reader in = new InputStreamReader(url.openStream(), encoding);
342 
343         // parse rst file
344         JRSTReader jrst = new JRSTReader();
345         return jrst.read(in);
346     }
347 
348     /**
349      * Transforms a restructured text file to a XML file using Jython interpreter to execute DocUtils scripts.
350      *
351      * @param in       Input restructured text file (.rst)
352      * @param encoding Output file encoding
353      * @return A document which contains XML code
354      * @throws Exception
355      */
356     public static Document generateDocutils(File in, String encoding) throws Exception {
357 
358         ByteArrayOutputStream out = null;
359 
360         try {
361             // Transformation to XML
362             out = new ByteArrayOutputStream();
363 
364             // Transformation of the __run__ URL into a path that python will use
365             // For example the URL is :
366             // jar:file:/home/user/.m2/repository/org/nuiton/jrst/docutils/1.6-SNAPSHOT/docutils-1.6-SNAPSHOT.jar!/__run__.py
367             // and it becomes :
368             // /home/user/.m2/repository/org/nuiton/jrst/docutils/1.6-SNAPSHOT/docutils-1.6-SNAPSHOT.jar/
369             URL resource = JRST.class.getResource("/" + DOCUTILS_LAUNCHER);
370             String docutilsPath = resource.getPath()
371                     .replaceAll(DOCUTILS_LAUNCHER, "");
372 
373             docutilsPath = docutilsPath.replaceAll(BANG, "");
374             docutilsPath = docutilsPath.replaceAll(FILE_URI_PREFIX, "");
375 
376             // Import of the main script to use docutils ( __run__ )
377             PythonInterpreter interp = new PythonInterpreter();
378             String commandImport = IMPORT_SCRIPT;
379             interp.exec(commandImport);
380 
381             // If the OS is windows, escapes the backslashs in the filepath
382             String filePath = in.getAbsolutePath();
383             String property = System.getProperty(OS_NAME).toLowerCase();
384             if (property.contains(WINDOWS_NAME)) {
385                 filePath = filePath.replaceAll("\\\\", "\\\\\\\\");
386             }
387 
388             // Sets an output stream in the python interpreter and executes the code
389             interp.setOut(out);
390 
391             // Execution of the docutils script to transform rst to xml
392             String commandExec = String.format("__run__.exec_docutils('%s', '%s', '%s')",
393                                                docutilsPath, TYPE_XML, filePath);
394             interp.exec(commandExec);
395 
396             // Cleans the python interpreter to avoid problems if they are multiple execution of this method
397             interp.cleanup();
398 
399             // Transforms the output stream to a document
400             String xmlString = new String(out.toByteArray(), encoding);
401 
402             Document doc = null;
403             try {
404                 doc = DocumentHelper.parseText(xmlString);
405             } catch (DocumentException e) {
406                 log.error("Error during the creation of the document", e);
407             }
408 
409             return doc;
410         } finally {
411             if (out != null) {
412                 out.close();
413             }
414         }
415     }
416 
417     /**
418      * Applies XSL stylesheet(s) to a XML document
419      *
420      * @param doc              A document which contains XML code
421      * @param xslListOrOutType String which describes transformations to apply to the XML document
422      * @return A document which contains XML transformed by XSL stylesheets
423      * @throws IOException
424      * @throws TransformerException
425      */
426     public static Document generateXml(Document doc, String xslListOrOutType) throws IOException, TransformerException {
427 
428         // search xsl file list to apply
429         String xslList = stylesheets.get(xslListOrOutType);
430         if (xslListOrOutType == null) {
431             xslList = xslListOrOutType;
432         }
433 
434         // apply xsl on rst xml document
435         JRSTGenerator gen = new JRSTGenerator();
436         String[] xsls = StringUtil.split(xslList, ",");
437         for (String xsl : xsls) {
438             URL stylesheet;
439             File file = new File(xsl);
440             if (file.exists()) {
441                 stylesheet = file.toURI().toURL();
442             } else {
443                 stylesheet = Resource.getURL(xsl);
444             }
445             if (stylesheet == null) {
446                 throw new FileNotFoundException("Can't find stylesheet: "
447                                                 + xsl);
448             }
449 
450             // add entity resolver
451             gen.setUriResolver(new JRSTResourceResolver(xsl));
452 
453             // do transformation
454             doc = gen.transform(doc, stylesheet);
455         }
456         return doc;
457     }
458 
459     /**
460      * Writes the XML content generated in a file
461      *
462      * @param doc     Document which contains XML code
463      * @param fileOut Output file
464      * @throws IOException
465      */
466     public static void generateFile(Document doc, File fileOut) throws IOException {
467 
468         // Out
469         OutputStreamWriter writer = null;
470 
471         try {
472             OutputStream outputStream = new FileOutputStream(fileOut);
473 
474             // If the output file type si not "pdf", we can write in the final file
475             writer = new OutputStreamWriter(outputStream, UTF_8);
476 
477             // write generated document
478             writer.write(doc.asXML());
479         } catch (Exception eee) {
480             log.error("Failed to write file", eee);
481         } finally {
482             if (writer != null) {
483                 writer.close();
484             }
485         }
486     }
487 
488     /**
489      * Generates PDF file with IText with an HTML document
490      *
491      * @param result  Document which contains HTML code
492      * @param fileIn  ReST file used to build resources path
493      * @param fileOut PDF Output file
494      * @throws Exception
495      */
496     public static void generatePdf(Document result, File fileIn, File fileOut) throws Exception {
497 
498         // Out
499         OutputStream outputStream = null;
500 
501         try {
502             outputStream = new FileOutputStream(fileOut);
503 
504             // Creation of the document builder
505             DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
506             builder.setEntityResolver(result.getEntityResolver());
507 
508             // We must redifine our own DocumentInputSource because of the visibility
509             ByteArrayInputStream inputStream = new ByteArrayInputStream(result.asXML().getBytes());
510             org.w3c.dom.Document doc = builder.parse(inputStream);
511 
512             ITextRenderer renderer = new ITextRenderer();
513 
514             // Settings to resolve paths with the JRST User Agent
515             String absolutePath = fileIn.getParentFile().getAbsolutePath();
516             JRSTUserAgent jrstUserAgent = new JRSTUserAgent(absolutePath);
517             jrstUserAgent.setBaseURL(absolutePath);
518             renderer.getSharedContext().setUserAgentCallback(jrstUserAgent);
519             jrstUserAgent.setSharedContext(renderer.getSharedContext());
520 
521             // Generation of the pdf file
522             renderer.setDocument(doc, null);
523             renderer.layout();
524             renderer.createPDF(outputStream);
525         } catch (Exception eee) {
526             log.error("Failed to write PDF", eee);
527         } finally {
528             if (outputStream != null) {
529                 outputStream.close();
530             }
531         }
532     }
533 
534     /**
535      * Method used to generate rst document
536      * <br/>
537      * <b>WARN : don't work !</b>
538      *
539      * @param doc docutils document to generate
540      * @return rst document
541      * @throws IOException
542      */
543     public static String generateRST(Document doc) throws IOException {
544         // Creation d'un visitor qui convertie de l'xml vers le rst
545         DocUtilsVisitor visitor = new DocUtils2RST();
546 
547         // Attacher le visitor au document
548         // il va parcourir tout les elements et reconstruire du rst
549         doc.accept(visitor);
550 
551         // Recuperation du resultat
552         String result = visitor.getResult();
553         // nettoyage du visiteur
554         visitor.clear();
555 
556         return result;
557     }
558 }