Help generation: add the ability to generate an image from inlined Scilab code
[scilab.git] / scilab / modules / helptools / src / java / org / scilab / modules / helptools / DocbookTagConverter.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2010 - Calixte DENIZET
4  *
5  * This file must be used under the terms of the CeCILL.
6  * This source file is licensed as described in the file COPYING, which
7  * you should have received as part of this distribution.  The terms
8  * are also available at
9  * http://www.cecill.info/licences/Licence_CeCILL_V2-en.txt
10  *
11  */
12
13 package org.scilab.modules.helptools;
14
15 import java.io.File;
16 import java.io.IOException;
17 import java.lang.reflect.InvocationTargetException;
18 import java.lang.reflect.Method;
19 import java.util.ArrayList;
20 import java.util.HashMap;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Stack;
24
25 import javax.xml.parsers.ParserConfigurationException;
26 import javax.xml.parsers.SAXParser;
27 import javax.xml.parsers.SAXParserFactory;
28
29 import org.xml.sax.Attributes;
30 import org.xml.sax.InputSource;
31 import org.xml.sax.Locator;
32 import org.xml.sax.SAXException;
33 import org.xml.sax.helpers.DefaultHandler;
34
35 import org.scilab.modules.helptools.external.ExternalXMLHandler;
36 import org.scilab.modules.helptools.external.HTMLScilabHandler;
37
38 /**
39  * Class the convert a DocBook xml file
40  * @author Calixte DENIZET
41  */
42 public class DocbookTagConverter extends DefaultHandler {
43
44     private static final String DOCBOOKURI = "http://docbook.org/ns/docbook";
45     private static final Class[] argsType = new Class[] {Map.class, String.class};
46
47     private Map<String, Method> mapMeth = new HashMap();
48     private Map<String, ExternalXMLHandler> externalHandlers = new HashMap();
49     private List<DocbookTagConverter> converters;
50     private final File in;
51     private DocbookElement baseElement = new DocbookElement(null, null, null);
52     private Stack<DocbookElement> stack = new Stack();
53     private String errors = "";
54
55     /**
56      * True when an error is met during the parsing
57      */
58     protected boolean hasError;
59
60     /**
61      * The file which is parsed
62      */
63     protected String currentFileName;
64
65     /**
66      * Useful to locate the errors
67      */
68     protected Locator locator;
69
70     /**
71      * Constructor
72      * @param in the input file path
73      */
74     public DocbookTagConverter(String in) throws IOException {
75         if (in != null && !in.isEmpty()) {
76             this.in = new File(in);
77         } else {
78             this.in = null;
79         }
80     }
81
82     /**
83      * Constructor
84      * @param in the inputstream
85      * @param inName the name of the input file
86      */
87     public DocbookTagConverter(String inName, DocbookElement elem) throws IOException {
88         this(inName);
89         baseElement = elem;
90     }
91
92     /**
93      * Handle the tag
94      * @param tag the tag to handle
95      * @param attributes the attributes as a Map
96      * @param contents the contents between the matching tags
97      * @return the HTML code
98      * @throws UnhandledDocbookTagException if an handled tga is encountered
99      */
100     public String handleDocbookTag(String tag, Map attributes, String contents) throws SAXException {
101         if (tag != null && tag.length() > 0) {
102             Method method = mapMeth.get(tag);
103             if (method == null) {
104                 String name = "handle" + Character.toString(Character.toUpperCase(tag.charAt(0))) + tag.substring(1);
105                 try {
106                     method = this.getClass().getMethod(name, argsType);
107                     mapMeth.put(tag, method);
108                 } catch (NoSuchMethodException e) {
109                     throw new UnhandledDocbookTagException(tag);
110                 }
111             }
112             try {
113                 return (String) method.invoke(this, new Object[] {attributes, contents});
114             } catch (IllegalAccessException e) {
115                 throw new UnhandledDocbookTagException(tag);
116             } catch (InvocationTargetException e) {
117                 throw new SAXException("Problem with tag " + tag + "\n" + e.getCause());
118             }
119         } else {
120             throw new UnhandledDocbookTagException(tag);
121         }
122     }
123
124     public String getCurrentFileName() {
125         return currentFileName;
126     }
127
128     /**
129      * Register an Docbook tag converter. The aim is to parse the xml one time.
130      * @param c the converter to register
131      */
132     public void registerConverter(DocbookTagConverter c) {
133         if (converters == null) {
134             converters = new ArrayList();
135         }
136
137         converters.add(c);
138         c.setDocumentLocator(locator);
139     }
140
141     /**
142      * @param c the converter to remove
143      */
144     public void removeConverter(DocbookTagConverter c) {
145         if (converters != null) {
146             converters.remove(c);
147         }
148     }
149
150     /**
151      * @return all the registered converters
152      */
153     public DocbookTagConverter[] getConverters() {
154         if (converters == null) {
155             return null;
156         }
157
158         return converters.toArray(new DocbookTagConverter[0]);
159     }
160
161     /**
162      * Register an XMLHandler for external XML
163      * @param h the external XML handler
164      */
165     public void registerExternalXMLHandler(ExternalXMLHandler h) {
166         if (externalHandlers.get(h.getURI()) == null) {
167             externalHandlers.put(h.getURI(), h);
168             h.setConverter(this);
169         }
170     }
171
172     /**
173      * @param tagName the tag name
174      * @return true if the contents of the tag must be escaped
175      */
176     public boolean isEscapable(String tagName, String uri) {
177         return true;
178     }
179
180     /**
181      * @param tagName the tag name
182      * @return true if the contents of the tag must be trimed
183      */
184     public boolean isTrimable(String tagName) {
185         return true;
186     }
187
188     /**
189      * @return the parent tag name
190      */
191     public String getParentTagName() {
192         return stack.peek().getName();
193     }
194
195     /**
196      * @return the parent tag contents
197      */
198     public String getParentContent() {
199         return stack.peek().getStringBuilder().toString();
200     }
201
202     /**
203      * Start the conversion
204      * @throws SAXException if a problem is encountered during the parsing
205      * @throws IOException if an IO problem is encountered
206      */
207     public void convert() throws SAXException, IOException {
208         SAXParserFactory factory = SAXParserFactory.newInstance();
209         factory.setValidating(false);
210         factory.setNamespaceAware(true);
211
212         try {
213             factory.setFeature("http://xml.org/sax/features/namespace-prefixes", true);
214             SAXParser parser = factory.newSAXParser();
215             parser.parse(in, this);
216         } catch (ParserConfigurationException e) {
217             exceptionOccured(e);
218         } catch (SAXException e) {
219             System.err.println(e);
220         } catch (IOException e) {
221             System.err.println(e);
222         }
223
224         if (hasError) {
225             throw new SAXException(errors);
226         }
227     }
228
229     /**
230      * {@inheritDoc}
231      */
232     public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
233         currentFileName = systemId;
234         HTMLScilabHandler.getInstance().resetCompt();
235         if (converters != null) {
236             for (DocbookTagConverter conv : converters) {
237                 conv.resolveEntity(publicId, systemId);
238             }
239         }
240
241         return super.resolveEntity(publicId, systemId);
242     }
243
244     /**
245      * {@inheritDoc}
246      */
247     public void startDocument() throws SAXException {
248         stack.push(baseElement.getNewInstance(null, null, null));
249         if (converters != null) {
250             for (DocbookTagConverter conv : converters) {
251                 conv.startDocument();
252             }
253         }
254     }
255
256     /**
257      * {@inheritDoc}
258      */
259     public void endDocument() throws SAXException {
260         stack.clear();
261
262         if (converters != null) {
263             for (DocbookTagConverter conv : converters) {
264                 conv.endDocument();
265             }
266         }
267     }
268
269     /**
270      * {@inheritDoc}
271      */
272     public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
273         if (uri.equals(DOCBOOKURI)) {
274             int len = attributes.getLength();
275             Map<String, String> map = new HashMap(len);
276             for (int i = 0; i < len; i++) {
277                 map.put(attributes.getLocalName(i), attributes.getValue(i));
278             }
279             stack.push(baseElement.getNewInstance(localName, uri, map));
280         } else {
281             ExternalXMLHandler h = externalHandlers.get(uri);
282             if (h == null) {
283                 exceptionOccured(new SAXException("uri " + uri + " not handled"));
284                 return;
285             }
286             StringBuilder buf = h.startExternalXML(localName, attributes);
287             if (buf != null) {
288                 DocbookElement elem = baseElement.getNewInstance(localName, uri, null);
289                 elem.setStringBuilder(buf);
290                 stack.push(elem);
291             }
292         }
293
294         if (converters != null) {
295             for (DocbookTagConverter conv : converters) {
296                 conv.startElement(uri, localName, qName, attributes);
297             }
298         }
299     }
300
301     /**
302      * {@inheritDoc}
303      */
304     public void endElement(String uri, String localName, String qName) throws SAXException {
305         if (uri.equals(DOCBOOKURI)) {
306             DocbookElement elem = stack.pop();
307             if (!elem.getName().equals(localName)) {
308                 exceptionOccured(new SAXException("tag " + elem.getName() + " is closed with tag " + localName));
309                 return;
310             }
311             try {
312                 DocbookElement elemp = stack.peek();
313                 elemp.setParent(elem);
314                 StringBuilder buf = elem.getStringBuilder();
315                 if (isTrimable(elem.getName())) {
316                     buf = trim(buf);
317                 }
318                 String str = handleDocbookTag(elem.getName(), elem.getAttributes(), buf.toString());
319                 if (str != null) {
320                     elemp.getStringBuilder().append(str);
321                 }
322             } catch (SAXException e) {
323                 exceptionOccured(e);
324                 return;
325             }
326         } else {
327             ExternalXMLHandler h = externalHandlers.get(uri);
328             if (h == null) {
329                 exceptionOccured(new SAXException("uri " + uri + " not handled"));
330                 return;
331             }
332             String str = h.endExternalXML(localName);
333             if (str != null) {
334                 stack.pop();
335                 stack.peek().getStringBuilder().append(str);
336             }
337         }
338
339         if (converters != null) {
340             for (DocbookTagConverter conv : converters) {
341                 conv.endElement(uri, localName, qName);
342             }
343         }
344     }
345
346     /**
347      * {@inheritDoc}
348      */
349     public void characters(char[] ch, int start, int length) throws SAXException {
350         int end = start + length;
351
352         if (isEscapable(stack.peek().getName(), stack.peek().getURI())) {
353             StringBuilder buf = stack.peek().getStringBuilder();
354             int save = start;
355             for (int i = start; i < end; i++) {
356                 switch (ch[i]) {
357                     case '\'' :
358                         buf.append(ch, save, i - save);
359                         buf.append("&#0039;");
360                         save = i + 1;
361                         break;
362                     case '\"' :
363                         buf.append(ch, save, i - save);
364                         buf.append("&#0034;");
365                         save = i + 1;
366                         break;
367                     case '<' :
368                         buf.append(ch, save, i - save);
369                         buf.append("&lt;");
370                         save = i + 1;
371                         break;
372                     case '>' :
373                         buf.append(ch, save, i - save);
374                         buf.append("&gt;");
375                         save = i + 1;
376                         break;
377                     case '&' :
378                         buf.append(ch, save, i - save);
379                         buf.append("&amp;");
380                         save = i + 1;
381                         break;
382                     default :
383                         break;
384                 }
385             }
386
387             if (save < end) {
388                 buf.append(ch, save, end - save);
389             }
390         } else {
391             stack.peek().getStringBuilder().append(ch, start, length);
392         }
393
394         if (converters != null) {
395             for (DocbookTagConverter conv : converters) {
396                 conv.characters(ch, start, length);
397             }
398         }
399     }
400
401     /**
402      * {@inheritDoc}
403      */
404     public void setDocumentLocator(Locator locator) {
405         this.locator = locator;
406     }
407
408     /**
409      * @return the document locator
410      */
411     public Locator getDocumentLocator() {
412         return locator;
413     }
414
415     /**
416      * @return the used stack
417      */
418     protected Stack<DocbookElement> getStack() {
419         return stack;
420     }
421
422     /**
423      * Handle an exception depending on the presence of a locator
424      * @param e the exception to handle
425      * @throws SAXException if problem
426      */
427     protected void fatalExceptionOccured(Exception e) throws SAXException {
428         throw new SAXException(errors + "\nFATAL error:\n" + e.getMessage());
429     }
430
431     /**
432      * Handle an exception depending on the presence of a locator
433      * @param e the exception to handle
434      * @throws SAXException if problem
435      */
436     protected void exceptionOccured(Exception e) {
437         if (!hasError) {
438             hasError = true;
439         }
440         errors += makeErrorMessage(e);
441     }
442
443     private String makeErrorMessage(Exception e) {
444         String sId = "";
445         if (currentFileName != null) {
446             sId = "SystemID:" + currentFileName;
447         }
448
449         String file;
450         try {
451             file = in.getCanonicalPath();
452         } catch (IOException e1) {
453             e1.printStackTrace();
454             file = null;
455         }
456         if (locator != null) {
457             return "\nCannot parse " + file + ":\n" + e.getMessage() + "\n" + sId + " at line " + locator.getLineNumber();
458         } else {
459             return "\nCannot parse " + file + ":\n" + e.getMessage() + "\n" + sId;
460         }
461     }
462
463     /**
464      * @param the StringBuilder to trim
465      * @return a trimed StringBuilder
466      */
467     private static StringBuilder trim(StringBuilder buf) {
468         int start = 0;
469         int i = 0;
470         int end = buf.length();
471         char c;
472         for (; i < end; i++) {
473             c = buf.charAt(i);
474             if (c != ' ' && c != '\t' && c != '\r' && c != '\n') {
475                 break;
476             }
477         }
478         buf.delete(0, i);
479         end = end - i;
480         for (i = end - 1; i >= 0; i--) {
481             c = buf.charAt(i);
482             if (c != ' ' && c != '\t' && c != '\r' && c != '\n') {
483                 break;
484             }
485         }
486         buf.setLength(i + 1);
487
488         return buf;
489     }
490 }