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