View Javadoc
1   /*
2    * #%L
3    * JRst :: Api
4    * %%
5    * Copyright (C) 2004 - 2010 CodeLutin
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  
23  
24  package org.nuiton.jrst;
25  
26  import java.io.IOException;
27  import java.io.Writer;
28  import java.net.URL;
29  import java.util.LinkedList;
30  import javax.xml.transform.Transformer;
31  import javax.xml.transform.TransformerException;
32  import javax.xml.transform.TransformerFactory;
33  import javax.xml.transform.URIResolver;
34  import javax.xml.transform.sax.SAXResult;
35  import javax.xml.transform.stream.StreamResult;
36  import javax.xml.transform.stream.StreamSource;
37  import org.apache.commons.logging.Log;
38  import org.apache.commons.logging.LogFactory;
39  import org.dom4j.Document;
40  import org.dom4j.Element;
41  import org.dom4j.Node;
42  import org.dom4j.Text;
43  import org.dom4j.io.DocumentResult;
44  import org.dom4j.io.DocumentSource;
45  import org.nuiton.jrst.legacy.DocumentHandler;
46  import org.nuiton.jrst.legacy.DocumentWalker;
47  import org.nuiton.util.FasterCachedResourceResolver;
48  import org.xml.sax.ContentHandler;
49  
50  import static org.nuiton.jrst.legacy.ReStructuredText.ADDRESS;
51  import static org.nuiton.jrst.legacy.ReStructuredText.AUTHOR;
52  import static org.nuiton.jrst.legacy.ReStructuredText.AUTHORS;
53  import static org.nuiton.jrst.legacy.ReStructuredText.BULLET_LIST;
54  import static org.nuiton.jrst.legacy.ReStructuredText.CLASSIFIER;
55  import static org.nuiton.jrst.legacy.ReStructuredText.CONTACT;
56  import static org.nuiton.jrst.legacy.ReStructuredText.COPYRIGHT;
57  import static org.nuiton.jrst.legacy.ReStructuredText.DATE;
58  import static org.nuiton.jrst.legacy.ReStructuredText.DEFINITION;
59  import static org.nuiton.jrst.legacy.ReStructuredText.DEFINITION_LIST;
60  import static org.nuiton.jrst.legacy.ReStructuredText.DOCINFO;
61  import static org.nuiton.jrst.legacy.ReStructuredText.EMPHASIS;
62  import static org.nuiton.jrst.legacy.ReStructuredText.ENUMERATED_LIST;
63  import static org.nuiton.jrst.legacy.ReStructuredText.FIELD_BODY;
64  import static org.nuiton.jrst.legacy.ReStructuredText.FIELD_LIST;
65  import static org.nuiton.jrst.legacy.ReStructuredText.FIELD_NAME;
66  import static org.nuiton.jrst.legacy.ReStructuredText.LIST_ITEM;
67  import static org.nuiton.jrst.legacy.ReStructuredText.LITERAL;
68  import static org.nuiton.jrst.legacy.ReStructuredText.LITERAL_BLOCK;
69  import static org.nuiton.jrst.legacy.ReStructuredText.ORGANIZATION;
70  import static org.nuiton.jrst.legacy.ReStructuredText.PARAGRAPH;
71  import static org.nuiton.jrst.legacy.ReStructuredText.REVISION;
72  import static org.nuiton.jrst.legacy.ReStructuredText.SECTION;
73  import static org.nuiton.jrst.legacy.ReStructuredText.STATUS;
74  import static org.nuiton.jrst.legacy.ReStructuredText.STRONG;
75  import static org.nuiton.jrst.legacy.ReStructuredText.TABLE;
76  import static org.nuiton.jrst.legacy.ReStructuredText.TITLE;
77  import static org.nuiton.jrst.legacy.ReStructuredText.TITLE_CHAR;
78  import static org.nuiton.jrst.legacy.ReStructuredText.TRANSITION;
79  import static org.nuiton.jrst.legacy.ReStructuredText.VERSION;
80  
81  /**
82   * Cette classe contient plusieurs methodes pour générer, soit en utilisant une
83   * feuille de style {@link #generate(Document, URL, Writer)}, soit un
84   * {@link ContentHandler} avec {@link #generate(Document, ContentHandler)}, soit
85   * {@link org.nuiton.jrst.legacy.DocumentHandler} avec {@link #generate(Document, ContentHandler)} ou
86   * bien regénérer du RST avec {@link #generate(Document)} dans les deux derniers
87   * cas il faut passer un {@link Writer} en parametre du constructeur.
88   * <p>
89   * On peut aussi transformer le {@link Document} en un autre {@link Document}
90   * avec {@link #transform(Document, URL)}
91   *
92   * Created: 30 oct. 06 00:14:18
93   *
94   * @author poussin
95   * @version $Revision$
96   *
97   * Last update: $Date$
98   * by : $Author$
99   */
100 public class JRSTGenerator implements DocumentHandler {
101 
102     static boolean DEBUG = true;
103 
104     static private Log log = LogFactory.getLog(JRSTGenerator.class);
105 
106     protected Writer out;
107     protected int sectionLevel;
108     protected int indent;
109 
110     protected String listType = "bullet|enumerated|...";
111     protected int enumStart = 1;
112 
113     protected URIResolver uriResolver;
114 
115     public JRSTGenerator() {
116     }
117 
118     public JRSTGenerator(Writer out) {
119         this.out = out;
120     }
121 
122     /**
123      * @return the uriResolver
124      */
125     public URIResolver getUriResolver() {
126         return uriResolver;
127     }
128 
129     /**
130      * @param uriResolver
131      *            the uriResolver to set
132      */
133     public void setUriResolver(URIResolver uriResolver) {
134         this.uriResolver = uriResolver;
135     }
136 
137     /**
138      * Generate using this class as handler, this generate RST text to out
139      * passed in constructor
140      *
141      * @param doc document to transform
142      * @throws IOException
143      */
144     public void generate(Document doc) throws IOException {
145         generate(doc, this);
146     }
147 
148     public void generate(Document doc, DocumentHandler handler) {
149         DocumentWalker walker = new DocumentWalker(handler);
150         walker.walk(doc);
151     }
152 
153     /**
154      * Generate using handler passed in argument
155      * 
156      * @param doc document
157      * @param handler saxon content handler
158      * @throws IOException
159      * @throws TransformerException
160      */
161     public void generate(Document doc, ContentHandler handler)
162             throws IOException, TransformerException {
163         // load the transformer using JAXP
164         TransformerFactory factory = TransformerFactory.newInstance();
165         if (uriResolver != null) {
166             factory.setURIResolver(uriResolver);
167         }
168         Transformer transformer = factory.newTransformer();
169 
170         // now lets style the given document
171         DocumentSource source = new DocumentSource(doc);
172         SAXResult result = new SAXResult(handler);
173         transformer.transform(source, result);
174     }
175 
176     /**
177      * Used writer passed in construction class
178      *
179      * @param doc document to transform
180      * @param stylesheet to apply
181      * @throws IOException
182      * @throws TransformerException
183      */
184     public void generate(Document doc, URL stylesheet) throws IOException,
185             TransformerException {
186         generate(doc, stylesheet, out);
187     }
188 
189     /**
190      * Generate out from document using stylesheet
191      *
192      * @param doc document to transform
193      * @param stylesheet to apply
194      * @param out output
195      * @throws IOException
196      * @throws TransformerException
197      */
198     public void generate(Document doc, URL stylesheet, Writer out)
199             throws IOException, TransformerException {
200         // load the transformer using JAXP
201         TransformerFactory factory = TransformerFactory.newInstance();
202         if (uriResolver != null) {
203             factory.setURIResolver(uriResolver);
204         } else {
205             factory.setURIResolver(new FasterCachedResourceResolver(stylesheet.toString()));
206         }
207         Transformer transformer = factory.newTransformer(new StreamSource(
208                 stylesheet.openStream()));
209 
210         // now lets style the given document
211         DocumentSource source = new DocumentSource(doc);
212         StreamResult result = new StreamResult(out);
213         transformer.transform(source, result);
214 
215         out.flush();
216     }
217 
218     /**
219      * Transform doc in another XML document.
220      * 
221      * @param doc document to transform
222      * @param stylesheet to apply
223      * @return the transformed document
224      * @throws TransformerException
225      * @throws IOException
226      */
227     public Document transform(Document doc, URL stylesheet)
228             throws TransformerException, IOException {
229 
230         if (log.isInfoEnabled()) {
231             log.debug("Transform document using : " + stylesheet);
232         }
233 
234         // load the transformer using JAXP
235         TransformerFactory factory = TransformerFactory.newInstance();
236         if (uriResolver != null) {
237             factory.setURIResolver(uriResolver);
238         } else {
239             factory.setURIResolver(new FasterCachedResourceResolver(stylesheet.toString()));
240         }
241 
242         Transformer transformer = factory.newTransformer(new StreamSource(
243                 stylesheet.openStream()));
244 
245         // now lets style the given document
246         DocumentSource source = new DocumentSource(doc);
247         DocumentResult result = new DocumentResult();
248         transformer.transform(source, result);
249 
250         // return the transformed document
251         Document transformedDoc = result.getDocument();
252 
253         return transformedDoc;
254     }
255 
256     protected String string(String s, int number) {
257         String result = "";
258         for (int i = 0; i < number; i++) {
259             result += s;
260         }
261         return result;
262     }
263 
264     protected String enumtype(int i, String type) {
265         String result = null;
266         if ("arabic".equals(type)) {
267             result = String.valueOf(i);
268         } else if ("loweralpha".equals(type)) {
269             result = String.valueOf((char) ((int) 'a' + i));
270         } else if ("upperalpha".equals(type)) {
271             result = String.valueOf((char) ((int) 'A' + i));
272         } else if ("lowerroman".equals(type) || "upperroman".equals(type)) {
273             String[] c = new String[] { "i", "v", "x", "l", "c", "d", "m" };
274             int[] d = new int[] { 1, 5, 10, 50, 100, 500, 1000 };
275             result = "";
276             for (int a = 0; a < c.length; a++) {
277                 result = string(c[a], i / d[a]) + result;
278                 i = i % d[a];
279             }
280             if ("upperroman".equals(type)) {
281                 result = result.toUpperCase();
282             }
283         }
284         return result;
285     }
286 
287     /**
288      * Determine la longueur du text dans l'element
289      * &lt;emphasis<&gt;toto&lt;/emphasis<&gt; qui donne *toto* retournera 6
290      * 
291      * @param e element
292      * @return la longueur du text dans l'element.
293      */
294     @SuppressWarnings("unchecked")
295     protected int inlineLength(Element e) {
296         int result = 0;
297         LinkedList<Node> elems = new LinkedList<Node>();
298         elems.addAll(e.content());
299         while (elems.peek() != null) {
300             Node elem = elems.poll();
301             switch (elem.getNodeType()) {
302             case Node.ELEMENT_NODE:
303                 elems.addAll(((Element) elem).content());
304                 if (EMPHASIS.equals(elem.getName())) {
305                     result += 2;
306                 } else if (STRONG.equals(elem.getName())) {
307                     result += 4;
308                 } else if (LITERAL.equals(elem.getName())) {
309                     result += 4;
310                 } // perhaps do footnote_refence, ...
311                 break;
312             case Node.TEXT_NODE:
313                 result += elem.getText().length();
314                 break;
315             }
316         }
317         return result;
318     }
319 
320     /*
321      * (non-Javadoc)
322      * 
323      * @see org.nuiton.jrst.legacy.DocumentHandler#startDocument(org.dom4j.Document)
324      */
325     @Override
326     public void startDocument(Document doc) {
327     }
328 
329     /*
330      * (non-Javadoc)
331      * 
332      * @see org.nuiton.jrst.legacy.DocumentHandler#endDocument(org.dom4j.Document)
333      */
334     @Override
335     public void endDocument(Document doc) {
336     }
337 
338     /*
339      * (non-Javadoc)
340      * 
341      * @see org.nuiton.jrst.legacy.DocumentHandler#endElement(org.dom4j.Element)
342      */
343     @Override
344     public void endElement(Element e) {
345         boolean needNewLine = false;
346 
347         if (SECTION.equals(e.getName())) {
348             sectionLevel--;
349             needNewLine = true;
350         } else if (PARAGRAPH.equals(e.getName())) {
351             newLine();
352             needNewLine = true;
353         } else if (TITLE.equals(e.getName())) {
354             newLine();
355             if (sectionLevel == 0) {
356                 write(string("=", inlineLength(e)));
357             } else {
358                 String c = TITLE_CHAR.substring(sectionLevel, sectionLevel + 1);
359                 write(string(c, inlineLength(e)));
360             }
361             newLine();
362             needNewLine = true;
363         } else if (EMPHASIS.equals(e.getName())) {
364             write("*");
365         } else if (STRONG.equals(e.getName())) {
366             write("**");
367         } else if (LITERAL.equals(e.getName())) {
368             write("``");
369         } else if (DOCINFO.equals(e.getName())) {
370             needNewLine = true;
371         } else if (LITERAL_BLOCK.equals(e.getName())) {
372             indent--;
373             newLine();
374             needNewLine = true;
375         } else if (TABLE.equals(e.getName())) {
376             // TODO now we take table as LITERAL_BLOCK, but in near
377             // futur we must parse correctly TABLE (show JRSTReader and
378             // JRSTLexer too)
379             newLine();
380             needNewLine = true;
381         } else if (BULLET_LIST.equals(e.getName())) {
382             needNewLine = true;
383         } else if (ENUMERATED_LIST.equals(e.getName())) {
384             needNewLine = true;
385         } else if (FIELD_LIST.equals(e.getName())) {
386             needNewLine = true;
387         } else if (FIELD_BODY.equals(e.getName())) {
388             indent--;
389         } else if (DEFINITION_LIST.equals(e.getName())) {
390             needNewLine = true;
391         } else if (DEFINITION.equals(e.getName())) {
392             indent--;
393         } else if (LIST_ITEM.equals(e.getName())) {
394             indent--;
395         } else if (AUTHOR.equals(e.getName())) {
396             newLine();
397             indent--;
398         } else if (AUTHORS.equals(e.getName())) {
399             newLine();
400             indent--;
401         } else if (ORGANIZATION.equals(e.getName())) {
402             newLine();
403             indent--;
404         } else if (ADDRESS.equals(e.getName())) {
405             newLine();
406             indent--;
407         } else if (CONTACT.equals(e.getName())) {
408             newLine();
409             indent--;
410         } else if (VERSION.equals(e.getName())) {
411             newLine();
412             indent--;
413         } else if (REVISION.equals(e.getName())) {
414             newLine();
415             indent--;
416         } else if (STATUS.equals(e.getName())) {
417             newLine();
418             indent--;
419         } else if (DATE.equals(e.getName())) {
420             newLine();
421             indent--;
422         } else if (COPYRIGHT.equals(e.getName())) {
423             newLine();
424             indent--;
425         }
426 
427         if (needNewLine) {
428             // on ajoute une nouvelle ligne que si on est pas le dernier
429             // fils, cela evite que le fils, le pere, et le grand-pere ne
430             // demande tous une nouvelle ligne et donc au lieu d'en avoir
431             // une comme on le souhaite on en est 3 voir plus
432             Element parent = e.getParent();
433             Node lastNode = parent.node(parent.nodeCount() - 1);
434             if (lastNode != e) {
435                 // write("** new line ** " + e.getName() + ":" +
436                 // lastNode.getName());
437                 newLine();
438             }
439         }
440     }
441 
442     /*
443      * (non-Javadoc)
444      * 
445      * @see org.nuiton.jrst.legacy.DocumentHandler#startElement(org.dom4j.Element)
446      */
447     @Override
448     public void startElement(Element e) {
449         if (SECTION.equals(e.getName())) {
450             sectionLevel++;
451         } else if (TITLE.equals(e.getName())) {
452             if (sectionLevel == 0) {
453                 write(string("=", inlineLength(e)));
454                 newLine();
455             }
456         } else if (PARAGRAPH.equals(e.getName())) {
457             Element parent = e.getParent();
458             if (!((LIST_ITEM.equals(parent.getName()) || FIELD_BODY
459                     .equals(parent.getName())) && parent.node(0) == e)) {
460                 writeIndent();
461             }
462         } else if (TRANSITION.equals(e.getName())) {
463             write(string("-", 80));
464             newLine();
465             newLine();
466         } else if (EMPHASIS.equals(e.getName())) {
467             write("*");
468         } else if (STRONG.equals(e.getName())) {
469             write("**");
470         } else if (LITERAL.equals(e.getName())) {
471             write("``");
472         } else if (AUTHOR.equals(e.getName())) {
473             write(":Author: ");
474             indent++;
475         } else if (AUTHORS.equals(e.getName())) {
476             write(":Authors: ");
477             indent++;
478         } else if (ORGANIZATION.equals(e.getName())) {
479             write(":Organization: ");
480             indent++;
481         } else if (ADDRESS.equals(e.getName())) {
482             write(":Address: ");
483             indent++;
484         } else if (CONTACT.equals(e.getName())) {
485             write(":Contact: ");
486             indent++;
487         } else if (VERSION.equals(e.getName())) {
488             write(":Version: ");
489             indent++;
490         } else if (REVISION.equals(e.getName())) {
491             write(":Revision: ");
492             indent++;
493         } else if (STATUS.equals(e.getName())) {
494             write(":Status: ");
495             indent++;
496         } else if (DATE.equals(e.getName())) {
497             write(":Date: ");
498             indent++;
499         } else if (COPYRIGHT.equals(e.getName())) {
500             write(":Copyright: ");
501             indent++;
502         } else if (FIELD_NAME.equals(e.getName())) {
503             writeIndent(":");
504         } else if (FIELD_BODY.equals(e.getName())) {
505             write(": ");
506             indent++;
507         } else if (CLASSIFIER.equals(e.getName())) {
508             write(" : ");
509         } else if (DEFINITION.equals(e.getName())) {
510             // pour une fois on est obligé de passer une ligne dans le
511             // start, car on ne sait pas determiner la fin des classifiers
512             // qui doivent etre tous sur la ligne du TERM
513             newLine();
514             indent++;
515         } else if (LITERAL_BLOCK.equals(e.getName())) {
516             write("::");
517             newLine();
518             newLine();
519             indent++;
520         } else if (TABLE.equals(e.getName())) {
521             // TODO now we take table as LITERAL_BLOCK, but in near
522             // futur we must parse correctly TABLE (show JRSTReader and
523             // JRSTLexer too)
524         } else if (BULLET_LIST.equals(e.getName())) {
525             listType = BULLET_LIST;
526         } else if (ENUMERATED_LIST.equals(e.getName())) {
527             listType = ENUMERATED_LIST;
528             enumStart = Integer.parseInt(e.attributeValue("start"));
529         } else if (LIST_ITEM.equals(e.getName())) {
530             if (BULLET_LIST.equals(listType)) {
531                 writeIndent("- ");
532             } else if (ENUMERATED_LIST.equals(listType)) {
533                 writeIndent(enumtype(enumStart++, e.getParent().attributeValue(
534                         "enumtype"))
535                         + ". ");
536             }
537             indent++;
538         }
539     }
540 
541     /*
542      * (non-Javadoc)
543      * 
544      * @see org.nuiton.jrst.legacy.DocumentHandler#text(org.dom4j.Text)
545      */
546     @Override
547     public void text(Text t) {
548         if (LITERAL_BLOCK.equals(t.getParent().getName())) {
549             writeIndent(t.getText());
550         } else {
551             write(t.getText());
552         }
553     }
554 
555     protected void newLine() {
556         write("\n");
557     }
558 
559     protected void write(String text) {
560         write(text, false);
561     }
562 
563     protected void writeIndent() {
564         write("", true);
565     }
566 
567     protected void writeIndent(String text) {
568         write(text, true);
569     }
570 
571     /**
572      * Ecrit le text, si indent est vrai, alors chaque ligne est indentée
573      * 
574      * @param text text to write
575      * @param doIndent do indent
576      */
577     protected void write(String text, boolean doIndent) {
578         try {
579             String blank = "";
580             if (doIndent) {
581                 blank = string("  ", indent);
582             }
583             out.write(blank);
584             for (char c : text.toCharArray()) {
585                 out.write(c);
586                 if (c == '\n') {
587                     out.write(blank);
588                 }
589             }
590         } catch (IOException eee) {
591             if (log.isWarnEnabled()) {
592                 log.warn("TODO untreated error", eee);
593             }
594         }
595     }
596 }