1 | /* This file is part of the project "Hilbert II" - http://www.qedeq.org |
2 | * |
3 | * Copyright 2000-2014, Michael Meyling <mime@qedeq.org>. |
4 | * |
5 | * "Hilbert II" is free software; you can redistribute |
6 | * it and/or modify it under the terms of the GNU General Public |
7 | * License as published by the Free Software Foundation; either |
8 | * version 2 of the License, or (at your option) any later version. |
9 | * |
10 | * This program is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | * GNU General Public License for more details. |
14 | */ |
15 | package org.qedeq.kernel.xml.handler.common; |
16 | |
17 | import java.util.Stack; |
18 | |
19 | import org.qedeq.base.io.SourceArea; |
20 | import org.qedeq.base.io.SourcePosition; |
21 | import org.qedeq.base.trace.Trace; |
22 | import org.qedeq.kernel.se.common.ModuleService; |
23 | import org.qedeq.kernel.se.common.SourceFileException; |
24 | import org.qedeq.kernel.se.common.SourceFileExceptionList; |
25 | import org.qedeq.kernel.xml.common.XmlSyntaxException; |
26 | import org.xml.sax.Attributes; |
27 | import org.xml.sax.SAXException; |
28 | import org.xml.sax.SAXParseException; |
29 | |
30 | |
31 | /** |
32 | * Default SAX handler. Delegates SAX events to a |
33 | * {@link org.qedeq.kernel.xml.handler.common.AbstractSimpleHandler} |
34 | * which could also delegate events to other |
35 | * {@link org.qedeq.kernel.xml.handler.common.AbstractSimpleHandler}s. |
36 | * <p> |
37 | * Before anything is parsed the method {@link #setExceptionList(SourceFileExceptionList)} |
38 | * must be called. |
39 | * |
40 | * @author Michael Meyling |
41 | */ |
42 | public class SaxDefaultHandler extends SimpleHandler { |
43 | |
44 | /** This class. */ |
45 | private static final Class CLASS = SaxDefaultHandler.class; |
46 | |
47 | /** Delegate currently to this handler. */ |
48 | private AbstractSimpleHandler currentHandler; |
49 | |
50 | /** Stack of previous {@link AbstractSimpleHandler}s. */ |
51 | private Stack handlerStack = new Stack(); |
52 | |
53 | /** Top level handler. This handler is activated after the begin of the document. */ |
54 | private AbstractSimpleHandler basisHandler; |
55 | |
56 | /** Collect errors in this object. */ |
57 | private SourceFileExceptionList errorList; |
58 | |
59 | /** Buffer for combining character events. */ |
60 | private StringBuffer buffer = new StringBuffer(2000); |
61 | |
62 | /** Tag level for current handler. */ |
63 | private int level; |
64 | |
65 | /** Tag level for previous handlers. */ |
66 | private Stack levelStack = new Stack(); |
67 | |
68 | /** Current tag name. Could be <code>null</code>. */ |
69 | private String currentElementName; |
70 | |
71 | /** The plugin we work for. */ |
72 | private final ModuleService plugin; |
73 | |
74 | /** |
75 | * Constructor. |
76 | * |
77 | * @param plugin The plugin we work for. |
78 | */ |
79 | public SaxDefaultHandler(final ModuleService plugin) { |
80 | super(); |
81 | this.plugin = plugin; |
82 | } |
83 | |
84 | /** |
85 | * Set parse exception list. This list collects occurring parsing errors. |
86 | * |
87 | * @param errorList Collect errors here. |
88 | */ |
89 | public void setExceptionList(final SourceFileExceptionList errorList) { |
90 | this.errorList = errorList; |
91 | } |
92 | |
93 | /** |
94 | * Set basis handler for documents. |
95 | * |
96 | * @param handler Basis handler for documents. This handler might also pass control to |
97 | * another handler via the |
98 | * {@link AbstractSimpleHandler#changeHandler(AbstractSimpleHandler, String, SimpleAttributes)} |
99 | * method. |
100 | */ |
101 | public final void setBasisDocumentHandler(final AbstractSimpleHandler handler) { |
102 | basisHandler = handler; |
103 | currentHandler = handler; |
104 | handlerStack.clear(); |
105 | level = 0; |
106 | } |
107 | |
108 | /* (non-Javadoc) |
109 | * @see org.xml.sax.helpers.DefaultHandler#startDocument() |
110 | */ |
111 | public final void startDocument() throws SAXException { |
112 | sendCharacters(); |
113 | currentHandler = basisHandler; |
114 | handlerStack.clear(); |
115 | level = 0; |
116 | currentElementName = null; |
117 | } |
118 | |
119 | /* (non-Javadoc) |
120 | * @see org.xml.sax.helpers.DefaultHandler#endDocument() |
121 | */ |
122 | public final void endDocument() throws SAXException { |
123 | sendCharacters(); |
124 | currentElementName = null; |
125 | } |
126 | |
127 | /* (non-Javadoc) |
128 | * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String, |
129 | * java.lang.String, org.xml.sax.Attributes) |
130 | */ |
131 | public final void startElement(final String uri, final String localName, final String qName, |
132 | final Attributes amap) throws SAXException { |
133 | final String method = "startElement"; |
134 | try { |
135 | Trace.param(CLASS, this, method, "currentHandler", currentHandler.getClass().getName()); |
136 | Trace.param(CLASS, this, method, "localName", localName); |
137 | Trace.param(CLASS, this, method, "qName", qName); |
138 | if (handlerStack.empty() && level == 0) { |
139 | currentHandler.init(); |
140 | } |
141 | level++; |
142 | Trace.param(CLASS, this, method, "level", level); |
143 | sendCharacters(); |
144 | currentElementName = localName; |
145 | final SimpleAttributes attributes = new SimpleAttributes(); |
146 | for (int i = 0; i < amap.getLength(); i++) { |
147 | attributes.add(amap.getQName(i), amap.getValue(i)); |
148 | } |
149 | Trace.param(CLASS, this, method, "attributes", attributes); |
150 | currentHandler.startElement(qName, attributes); |
151 | } catch (XmlSyntaxException e) { |
152 | Trace.trace(CLASS, this, method, e); |
153 | addXmlSyntaxException(e); |
154 | } catch (RuntimeException e) { |
155 | Trace.trace(CLASS, this, method, e); |
156 | final XmlSyntaxException ex = XmlSyntaxException.createByRuntimeException(e); |
157 | addXmlSyntaxException(ex); |
158 | } |
159 | } |
160 | |
161 | /* (non-Javadoc) |
162 | * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String, |
163 | * java.lang.String) |
164 | */ |
165 | public final void endElement(final String uri, final String localName, final String qName) |
166 | throws SAXException { |
167 | sendCharacters(); |
168 | final String method = "endElement"; |
169 | try { |
170 | Trace.param(CLASS, this, method, "currentHandler", currentHandler.getClass().getName()); |
171 | Trace.param(CLASS, this, method, "qName", qName); |
172 | currentHandler.endElement(qName); |
173 | } catch (XmlSyntaxException e) { |
174 | Trace.trace(CLASS, this, method, e); |
175 | addXmlSyntaxException(e); |
176 | } catch (RuntimeException e) { |
177 | Trace.trace(CLASS, this, method, e); |
178 | final XmlSyntaxException ex = XmlSyntaxException.createByRuntimeException(e); |
179 | addXmlSyntaxException(ex); |
180 | } |
181 | try { |
182 | currentElementName = null; |
183 | level--; |
184 | Trace.param(CLASS, this, method, "level", level); |
185 | if (level <= 0) { |
186 | restoreHandler(localName); |
187 | } |
188 | } catch (XmlSyntaxException e) { |
189 | Trace.trace(CLASS, this, method, e); |
190 | addXmlSyntaxException(e); |
191 | } catch (RuntimeException e) { |
192 | Trace.trace(CLASS, this, method, e); |
193 | final XmlSyntaxException ex = XmlSyntaxException.createByRuntimeException(e); |
194 | addXmlSyntaxException(ex); |
195 | } |
196 | } |
197 | |
198 | /* (non-Javadoc) |
199 | * @see org.xml.sax.helpers.DefaultHandler#characters(char[], int, int) |
200 | */ |
201 | public final void characters(final char[] ch, final int start, final int length) { |
202 | buffer.append(ch, start, length); |
203 | } |
204 | |
205 | /** |
206 | * Sends <code>characters</code> event to current handler. Whitespace is preserved. |
207 | * It fires only, if there is something beside whitespace. |
208 | */ |
209 | private void sendCharacters() { |
210 | try { |
211 | if (buffer.length() > 0) { |
212 | final String str = buffer.toString(); |
213 | buffer.setLength(0); |
214 | if (str.trim().length() > 0) { // anything there beside whitespace? |
215 | currentHandler.characters(currentElementName, str); |
216 | } |
217 | } |
218 | } catch (XmlSyntaxException e) { |
219 | Trace.trace(CLASS, this, "sendCharacters", e); |
220 | addXmlSyntaxException(e); |
221 | } catch (RuntimeException e) { |
222 | Trace.trace(CLASS, this, "sendCharacters", e); |
223 | final XmlSyntaxException ex = XmlSyntaxException.createByRuntimeException(e); |
224 | addXmlSyntaxException(ex); |
225 | } |
226 | } |
227 | |
228 | /** |
229 | * Add exception to exception list if we have only than 20 exceptions yet. |
230 | * |
231 | * @param exception Add this exception. |
232 | */ |
233 | private void addXmlSyntaxException(final XmlSyntaxException exception) { |
234 | if (errorList.size() < 20) { |
235 | errorList.add(new SourceFileException(plugin, exception, createSourceArea(), null)); |
236 | } |
237 | } |
238 | |
239 | /** |
240 | * Change current handler to new one. The new handler is initialized by calling |
241 | * {@link AbstractSimpleHandler#init()}. |
242 | * The new handler also gets a {@link AbstractSimpleHandler#startElement(String, |
243 | * SimpleAttributes)} event. |
244 | * The current handler is stacked. After the new handler gets the appropriate endElement |
245 | * event, the control is switched back to the old handler. |
246 | * <p> |
247 | * The switch back is also done, if the tag level gets back to the same number. That means |
248 | * if for example the new handler starts with the <code><banana></code> tag, the |
249 | * old handler is restored when the misspelled <code></bnana></code> tag occurs: |
250 | * <p> |
251 | * <pre> |
252 | * <banana> |
253 | * <one /> |
254 | * <two > |
255 | * <one /> |
256 | * <one /> |
257 | * </two > |
258 | * </bnana> |
259 | * </pre> |
260 | * |
261 | * @param newHandler This handler gets the new events. |
262 | * @param elementName Element name. |
263 | * @param attributes Element attributes. |
264 | * @throws XmlSyntaxException New Handler detected a semantic problem. |
265 | */ |
266 | public final void changeHandler(final AbstractSimpleHandler newHandler, |
267 | final String elementName, final SimpleAttributes attributes) |
268 | throws XmlSyntaxException { |
269 | handlerStack.push(currentHandler); |
270 | levelStack.push(new Integer(level)); |
271 | currentHandler = newHandler; |
272 | level = 0; |
273 | level++; |
274 | Trace.param(CLASS, this, "changeHandler", "level", level); |
275 | currentHandler.init(); |
276 | currentHandler.startElement(elementName, attributes); |
277 | } |
278 | |
279 | /** |
280 | * Restore previous handler if there is any. An {@link #endElement} event is also send to the restored |
281 | * handler. |
282 | * |
283 | * @param elementName Current element. |
284 | * @throws XmlSyntaxException Handler dosen't like this event. |
285 | */ |
286 | private final void restoreHandler(final String elementName) throws XmlSyntaxException { |
287 | while (level <= 0 && !handlerStack.empty()) { |
288 | currentHandler = (AbstractSimpleHandler) handlerStack.pop(); |
289 | Trace.param(CLASS, this, "restoreHandler", "currentHandler", currentHandler); |
290 | level = ((Integer) levelStack.pop()).intValue(); |
291 | currentHandler.endElement(elementName); |
292 | level--; |
293 | Trace.param(CLASS, this, "restoreHandler", "level", level); |
294 | } |
295 | if (handlerStack.empty()) { |
296 | Trace.trace(CLASS, this, "restoreHandler", "no handler to restore"); |
297 | } |
298 | } |
299 | |
300 | /** |
301 | * Get current level. |
302 | * |
303 | * @return Current level. |
304 | */ |
305 | public final int getLevel() { |
306 | return level; |
307 | } |
308 | |
309 | /** |
310 | * Wraps exception in new {@link SAXParseException} including parsing position information. |
311 | * |
312 | * @param e Exception to wrap. |
313 | * @return Exception to throw. |
314 | */ |
315 | public final SAXParseException createSAXParseException(final Exception e) { |
316 | return new SAXParseException(null, getLocator(), e); |
317 | } |
318 | |
319 | /** |
320 | * Creates new {@link SAXParseException} including parsing position information. |
321 | * |
322 | * @param message Problem description. |
323 | * @return Exception to throw. |
324 | */ |
325 | public final SAXParseException createSAXParseException(final String message) { |
326 | return new SAXParseException(message, getLocator()); |
327 | } |
328 | |
329 | /** |
330 | * Create current source area. |
331 | * |
332 | * @return Current area. |
333 | */ |
334 | public final SourceArea createSourceArea() { |
335 | if (getLocator() != null && getUrl() != null) { |
336 | return new SourceArea(getUrl(), new SourcePosition(getLocator().getLineNumber(), 1), |
337 | new SourcePosition(getLocator().getLineNumber(), getLocator().getColumnNumber())); |
338 | } |
339 | return new SourceArea(getUrl()); |
340 | } |
341 | |
342 | /** |
343 | * Get plugin we work for. |
344 | * |
345 | * @return Plugin. |
346 | */ |
347 | public ModuleService getPlugin() { |
348 | return plugin; |
349 | } |
350 | |
351 | } |