5286372e0ff5e6777282432908d8cb7a4e9fcefb
[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<String, Method>();
48     private Map<String, ExternalXMLHandler> externalHandlers = new HashMap<String, ExternalXMLHandler>();
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<DocbookElement>();
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<DocbookTagConverter>();
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             // Must be uncommented to be able to read comments
216             //parser.setProperty("http://xml.org/sax/properties/lexical-handler", this);
217             parser.parse(in, this);
218         } catch (ParserConfigurationException e) {
219             exceptionOccured(e);
220         } catch (SAXException e) {
221             System.err.println(e);
222         } catch (IOException e) {
223             System.err.println(e);
224         }
225
226         if (hasError) {
227             throw new SAXException(errors);
228         }
229     }
230
231     /**
232      * {@inheritDoc}
233      */
234     public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
235         currentFileName = systemId;
236         HTMLScilabHandler.getInstance().resetCompt();
237         if (converters != null) {
238             for (DocbookTagConverter conv : converters) {
239                 conv.resolveEntity(publicId, systemId);
240             }
241         }
242
243         return super.resolveEntity(publicId, systemId);
244     }
245
246     /**
247      * {@inheritDoc}
248      */
249     public void startDocument() throws SAXException {
250         stack.push(baseElement.getNewInstance(null, null, null));
251         if (converters != null) {
252             for (DocbookTagConverter conv : converters) {
253                 conv.startDocument();
254             }
255         }
256     }
257
258     /**
259      * {@inheritDoc}
260      */
261     public void endDocument() throws SAXException {
262         stack.clear();
263
264         if (converters != null) {
265             for (DocbookTagConverter conv : converters) {
266                 conv.endDocument();
267             }
268         }
269     }
270
271     /**
272      * {@inheritDoc}
273      */
274     public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
275         if (uri.equals(DOCBOOKURI)) {
276             int len = attributes.getLength();
277             Map<String, String> map = new HashMap<String, String>(len);
278             for (int i = 0; i < len; i++) {
279                 map.put(attributes.getLocalName(i), attributes.getValue(i));
280             }
281             stack.push(baseElement.getNewInstance(localName, uri, map));
282         } else {
283             ExternalXMLHandler h = externalHandlers.get(uri);
284             if (h == null) {
285                 exceptionOccured(new SAXException("uri " + uri + " not handled"));
286                 return;
287             }
288             StringBuilder buf = h.startExternalXML(localName, attributes);
289             if (buf != null) {
290                 DocbookElement elem = baseElement.getNewInstance(localName, uri, null);
291                 elem.setStringBuilder(buf);
292                 stack.push(elem);
293             }
294         }
295
296         if (converters != null) {
297             for (DocbookTagConverter conv : converters) {
298                 conv.startElement(uri, localName, qName, attributes);
299             }
300         }
301     }
302
303     /**
304      * {@inheritDoc}
305      */
306     public void endElement(String uri, String localName, String qName) throws SAXException {
307         if (uri.equals(DOCBOOKURI)) {
308             DocbookElement elem = stack.pop();
309             if (!elem.getName().equals(localName)) {
310                 exceptionOccured(new SAXException("tag " + elem.getName() + " is closed with tag " + localName));
311                 return;
312             }
313             try {
314                 DocbookElement elemp = stack.peek();
315                 elemp.setParent(elem);
316                 StringBuilder buf = elem.getStringBuilder();
317                 if (isTrimable(elem.getName())) {
318                     buf = trim(buf);
319                 }
320                 String str = handleDocbookTag(elem.getName(), elem.getAttributes(), buf.toString());
321                 if (str != null) {
322                     elemp.getStringBuilder().append(str);
323                 }
324             } catch (SAXException e) {
325                 exceptionOccured(e);
326                 return;
327             }
328         } else {
329             ExternalXMLHandler h = externalHandlers.get(uri);
330             if (h == null) {
331                 exceptionOccured(new SAXException("uri " + uri + " not handled"));
332                 return;
333             }
334             String str = h.endExternalXML(localName);
335             if (str != null) {
336                 stack.pop();
337                 stack.peek().getStringBuilder().append(str);
338             }
339         }
340
341         if (converters != null) {
342             for (DocbookTagConverter conv : converters) {
343                 conv.endElement(uri, localName, qName);
344             }
345         }
346     }
347
348     /*public void comment(char[] ch, int start, int length) throws SAXException {
349
350       }*/
351
352     /**
353      * {@inheritDoc}
354      */
355     public void characters(char[] ch, int start, int length) throws SAXException {
356         int end = start + length;
357
358         if (isEscapable(stack.peek().getName(), stack.peek().getURI())) {
359             StringBuilder buf = stack.peek().getStringBuilder();
360             int save = start;
361             for (int i = start; i < end; i++) {
362                 switch (ch[i]) {
363                     case '\'' :
364                         buf.append(ch, save, i - save);
365                         buf.append("&#0039;");
366                         save = i + 1;
367                         break;
368                     case '\"' :
369                         buf.append(ch, save, i - save);
370                         buf.append("&#0034;");
371                         save = i + 1;
372                         break;
373                     case '<' :
374                         buf.append(ch, save, i - save);
375                         buf.append("&lt;");
376                         save = i + 1;
377                         break;
378                     case '>' :
379                         buf.append(ch, save, i - save);
380                         buf.append("&gt;");
381                         save = i + 1;
382                         break;
383                     case '&' :
384                         buf.append(ch, save, i - save);
385                         buf.append("&amp;");
386                         save = i + 1;
387                         break;
388                     default :
389                         break;
390                 }
391             }
392
393             if (save < end) {
394                 buf.append(ch, save, end - save);
395             }
396         } else {
397             stack.peek().getStringBuilder().append(ch, start, length);
398         }
399
400         if (converters != null) {
401             for (DocbookTagConverter conv : converters) {
402                 conv.characters(ch, start, length);
403             }
404         }
405     }
406
407     /**
408      * {@inheritDoc}
409      */
410     public void setDocumentLocator(Locator locator) {
411         this.locator = locator;
412     }
413
414     /**
415      * @return the document locator
416      */
417     public Locator getDocumentLocator() {
418         return locator;
419     }
420
421     /**
422      * @return the used stack
423      */
424     protected Stack<DocbookElement> getStack() {
425         return stack;
426     }
427
428     /**
429      * Handle an exception depending on the presence of a locator
430      * @param e the exception to handle
431      * @throws SAXException if problem
432      */
433     protected void fatalExceptionOccured(Exception e) throws SAXException {
434         throw new SAXException(errors + "\nFATAL error:\n" + e.getMessage());
435     }
436
437     /**
438      * Handle an exception depending on the presence of a locator
439      * @param e the exception to handle
440      * @throws SAXException if problem
441      */
442     protected void exceptionOccured(Exception e) {
443         if (!hasError) {
444             hasError = true;
445         }
446         errors += makeErrorMessage(e);
447     }
448
449     private String makeErrorMessage(Exception e) {
450         String sId = "";
451         if (currentFileName != null) {
452             sId = "SystemID:" + currentFileName;
453         }
454
455         String file;
456         try {
457             file = in.getCanonicalPath();
458         } catch (IOException e1) {
459             e1.printStackTrace();
460             file = null;
461         }
462         if (locator != null) {
463             return "\nCannot parse " + file + ":\n" + e.getMessage() + "\n" + sId + " at line " + locator.getLineNumber();
464         } else {
465             return "\nCannot parse " + file + ":\n" + e.getMessage() + "\n" + sId;
466         }
467     }
468
469     /**
470      * @param the StringBuilder to trim
471      * @return a trimed StringBuilder
472      */
473     private static StringBuilder trim(StringBuilder buf) {
474         int start = 0;
475         int i = 0;
476         int end = buf.length();
477         char c;
478         for (; i < end; i++) {
479             c = buf.charAt(i);
480             if (c != ' ' && c != '\t' && c != '\r' && c != '\n') {
481                 break;
482             }
483         }
484         buf.delete(0, i);
485         end = end - i;
486         for (i = end - 1; i >= 0; i--) {
487             c = buf.charAt(i);
488             if (c != ' ' && c != '\t' && c != '\r' && c != '\n') {
489                 break;
490             }
491         }
492         buf.setLength(i + 1);
493
494         return buf;
495     }
496 }