Update jflex to 1.8.2
[scilab.git] / scilab / modules / helptools / src / java / org / scilab / modules / helptools / HTMLDocbookLinkResolver.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2010 - Calixte DENIZET
4  *
5  * Copyright (C) 2012 - 2016 - Scilab Enterprises
6  *
7  * This file is hereby licensed under the terms of the GNU GPL v2.0,
8  * pursuant to article 5.3.4 of the CeCILL v.2.1.
9  * This file was originally licensed under the terms of the CeCILL v2.1,
10  * and continues to be available under such terms.
11  * For more information, see the COPYING file which you should have received
12  * along with this program.
13  *
14  */
15
16 package org.scilab.modules.helptools;
17
18 import java.io.IOException;
19 import java.io.File;
20 import java.util.ArrayList;
21 import java.util.HashMap;
22 import java.util.LinkedHashMap;
23 import java.util.List;
24 import java.util.Map;
25
26 import javax.xml.parsers.ParserConfigurationException;
27 import javax.xml.parsers.SAXParser;
28 import javax.xml.parsers.SAXParserFactory;
29
30 import org.xml.sax.Attributes;
31 import org.xml.sax.InputSource;
32 import org.xml.sax.Locator;
33 import org.xml.sax.SAXException;
34 import org.xml.sax.helpers.DefaultHandler;
35
36 import org.scilab.modules.commons.ScilabCommonsUtils;
37
38 /**
39  * Class the convert a DocBook xml file
40  * @author Calixte DENIZET
41  */
42 public class HTMLDocbookLinkResolver extends DefaultHandler {
43
44     private static boolean isCaseInsensitiveOS = System.getProperty("os.name").toLowerCase().contains("windows") || System.getProperty("os.name").toLowerCase().contains("mac");
45
46     private Map<String, String> mapId = new LinkedHashMap<>();
47     private List<String> listIdIgnoreCase = new ArrayList<>();
48     private Map<String, String> toc = new LinkedHashMap<>();
49     private Map<String, String> mapIdPurpose = new LinkedHashMap<>();
50     private Map<String, String> mapIdRefname = new LinkedHashMap<>();
51     private Map<String, TreeId> mapTreeId = new HashMap<>();
52     private Map<String, String> mapIdDeclaringFile = new HashMap<>();
53     private TreeId tree = new TreeId(null, "root");
54
55     private TreeId currentLeaf = tree;
56
57     private String current;
58     private String lastId;
59     private Locator locator;
60     private String currentFileName;
61     private boolean waitForRefname;
62     private boolean waitForRefpurpose;
63     private boolean waitForTitle;
64     private boolean getContents;
65     private boolean idInRefentry;
66     private final File in;
67     private StringBuilder buffer = new StringBuilder(256);
68
69     /**
70      * Constructor
71      * @param in the input file path
72      */
73     public HTMLDocbookLinkResolver(String in) throws IOException, SAXException {
74         this.in = new File(in);
75         resolvLinks();
76         mapIdDeclaringFile = null;
77     }
78
79     /**
80      * @return the map id
81      */
82     public Map<String, String> getMapId() {
83         return mapId;
84     }
85
86     /**
87      * @return the map id-&gt;title
88      */
89     public Map<String, String> getMapIdPurpose() {
90         return mapIdPurpose;
91     }
92
93     /**
94      * @return the map id-&gt;title
95      */
96     public Map<String, String> getMapIdRefname() {
97         return mapIdRefname;
98     }
99
100     /**
101      * @return the tocs
102      */
103     public Map<String, String> getToc() {
104         return toc;
105     }
106
107     /**
108      * @return the document tree
109      */
110     public TreeId getTree() {
111         return tree;
112     }
113
114     /**
115      * @return the document tree
116      */
117     public Map<String, TreeId> getMapTreeId() {
118         return mapTreeId;
119     }
120
121     /**
122      * {@inheritDoc}
123      */
124     public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
125         currentFileName = systemId;
126         return super.resolveEntity(publicId, systemId);
127     }
128
129     /**
130      * {@inheritDoc}
131      */
132     public void startDocument() throws SAXException { }
133
134     /**
135      * {@inheritDoc}
136      */
137     public void endDocument() throws SAXException { }
138
139     /**
140      * {@inheritDoc}
141      */
142     public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
143         int len = attributes.getLength();
144         String id = null;
145         for (int i = 0; i < len; i++) {
146             if (attributes.getLocalName(i).equals("id")) {
147                 id = attributes.getValue(i);
148                 break;
149             }
150         }
151
152         if (localName.equals("refentry")) {
153             idInRefentry = false;
154         }
155
156         if (localName.equals("title")) {
157             if (waitForTitle) {
158                 getContents = true;
159                 buffer.setLength(0);
160             }
161         } else if (localName.equals("refname")) {
162             if (waitForRefname) {
163                 getContents = true;
164                 buffer.setLength(0);
165             }
166         } else if (localName.equals("refpurpose")) {
167             if (waitForRefpurpose) {
168                 getContents = true;
169                 buffer.setLength(0);
170             }
171         } else if ((id != null && localName.equals("refentry")) || localName.equals("section")
172                    || localName.equals("part") || localName.equals("chapter") || (!idInRefentry && localName.equals("refnamediv"))) {
173             if (id == null) {
174                 throw new SAXException(errorMsg());
175             }
176             current = makeFileName(id);
177             listIdIgnoreCase.add(id.toLowerCase());
178             lastId = id;
179             if (mapIdDeclaringFile.containsKey(id)) {
180                 String prev = mapIdDeclaringFile.get(id);
181                 throw new SAXException("The id " + id + " in file " + currentFileName + " was previously declared in " + prev);
182             } else {
183                 mapIdDeclaringFile.put(id, currentFileName);
184             }
185             mapId.put(id, current);
186             waitForTitle = localName.charAt(0) != 'r';
187             waitForRefname = !waitForTitle;
188             idInRefentry = waitForRefname;
189             waitForRefpurpose = waitForRefname;
190             TreeId leaf = new TreeId(currentLeaf, id);
191             currentLeaf.add(leaf);
192             currentLeaf = leaf;
193         } else if (id != null && current != null) {
194             if (mapIdDeclaringFile.containsKey(id)) {
195                 String prev = mapIdDeclaringFile.get(id);
196                 throw new SAXException("The id " + id + " in file " + currentFileName + " was previously declared in " + prev);
197             } else {
198                 mapIdDeclaringFile.put(id, currentFileName);
199             }
200             mapId.put(id, current + "#" + id);
201         }
202     }
203
204     /**
205      * Make a file name which take into account that under Windows the file name
206      * is case insensitive and the xml:id is case sensitive.
207      * @param id the xml:id
208      * @return an unique file name
209      */
210     public String makeFileName(String id) {
211         if (isCaseInsensitiveOS && listIdIgnoreCase.contains(id.toLowerCase())) {
212             return id + "-" + ScilabCommonsUtils.getMD5(id) + ".html";
213         }
214
215         return id + ".html";
216     }
217
218     /**
219      * {@inheritDoc}
220      */
221     public void endElement(String uri, String localName, String qName) throws SAXException {
222         if (getContents) {
223             if (localName.equals("refpurpose")) {
224                 mapIdPurpose.put(lastId, buffer.toString().trim());
225                 waitForRefpurpose = false;
226                 getContents = false;
227             } else if (localName.equals("title") || localName.equals("refname")) {
228                 toc.put(lastId, buffer.toString().trim());
229                 mapIdRefname.put(lastId, buffer.toString().trim());
230                 getContents = false;
231                 waitForRefname = false;
232                 waitForTitle = false;
233             }
234         }
235         if (localName.equals("refentry") || localName.equals("section") || localName.equals("part") || localName.equals("chapter")) {
236             currentLeaf = currentLeaf.parent;
237         }
238     }
239
240     /**
241      * {@inheritDoc}
242      */
243     public void setDocumentLocator(Locator locator) {
244         this.locator = locator;
245     }
246
247     /**
248      * {@inheritDoc}
249      */
250     public void characters(char[] ch, int start, int length) throws SAXException {
251         if (getContents) {
252             int end = start + length;
253             int save = start;
254             for (int i = start; i < end; i++) {
255                 switch (ch[i]) {
256                     case '\'' :
257                         buffer.append(ch, save, i - save);
258                         buffer.append("&#0039;");
259                         save = i + 1;
260                         break;
261                     case '\"' :
262                         buffer.append(ch, save, i - save);
263                         buffer.append("&quot;");
264                         save = i + 1;
265                         break;
266                     case '<' :
267                         buffer.append(ch, save, i - save);
268                         buffer.append("&lt;");
269                         save = i + 1;
270                         break;
271                     case '>' :
272                         buffer.append(ch, save, i - save);
273                         buffer.append("&gt;");
274                         save = i + 1;
275                         break;
276                     case '&' :
277                         buffer.append(ch, save, i - save);
278                         buffer.append("&amp;");
279                         save = i + 1;
280                         break;
281                     default :
282                         break;
283                 }
284             }
285
286             if (save < end) {
287                 buffer.append(ch, save, end - save);
288             }
289         }
290     }
291
292     /**
293      * Start the conversion
294      * @throws SAXException if a problem is encountered during the parsing
295      * @throws IOException if an IO problem is encountered
296      */
297     protected void resolvLinks() throws SAXException, IOException {
298         SAXParserFactory factory = SAXParserFactory.newInstance();
299         factory.setValidating(false);
300         factory.setNamespaceAware(true);
301         factory.setXIncludeAware(true);
302
303         try {
304             SAXParser parser = factory.newSAXParser();
305             parser.parse(in, this);
306         } catch (ParserConfigurationException e) {
307             System.err.println(e);
308         }
309     }
310
311     /**
312      * @return the error message
313      */
314     private String errorMsg() {
315         String str;
316         if (currentFileName != null) {
317             str = currentFileName;
318         } else {
319             try {
320                 str = in.getCanonicalPath();
321             } catch (IOException e) {
322                 e.printStackTrace();
323                 str = null;
324             }
325         }
326
327         return "No id attribute in <refentry> or <refnamediv> in file " + str + " at line " + locator.getLineNumber();
328     }
329
330     public class TreeId {
331
332         String id;
333         TreeId parent;
334         int pos;
335         List<TreeId> children;
336
337         TreeId(TreeId parent, String id) {
338             this.parent = parent;
339             this.id = id;
340             if (parent == null) {
341                 mapTreeId.clear();
342             }
343             mapTreeId.put(id, this);
344         }
345
346         void add(TreeId child) {
347             if (children == null) {
348                 children = new ArrayList<>();
349             }
350             child.pos = children.size();
351             children.add(child);
352         }
353
354         boolean isRoot() {
355             return parent == null;
356         }
357
358         TreeId getPrevious() {
359             if (pos > 0) {
360                 return parent.children.get(pos - 1);
361             }
362             return parent;
363         }
364
365         TreeId getNext() {
366             TreeId l = this;
367             while (l.parent != null && l.pos == l.parent.children.size() - 1) {
368                 l = l.parent;
369             }
370
371             if (l.parent == null) {
372                 return null;
373             }
374             return l.parent.children.get(l.pos + 1);
375         }
376     }
377 }