001 /* This file is part of the project "Hilbert II" - http://www.qedeq.org
002 *
003 * Copyright 2000-2013, Michael Meyling <mime@qedeq.org>.
004 *
005 * "Hilbert II" is free software; you can redistribute
006 * it and/or modify it under the terms of the GNU General Public
007 * License as published by the Free Software Foundation; either
008 * version 2 of the License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013 * GNU General Public License for more details.
014 */
015 package org.qedeq.kernel.xml.handler.common;
016
017 import java.util.Stack;
018
019 import org.qedeq.base.io.SourceArea;
020 import org.qedeq.base.io.SourcePosition;
021 import org.qedeq.base.trace.Trace;
022 import org.qedeq.kernel.se.common.Plugin;
023 import org.qedeq.kernel.se.common.SourceFileException;
024 import org.qedeq.kernel.se.common.SourceFileExceptionList;
025 import org.qedeq.kernel.xml.common.XmlSyntaxException;
026 import org.xml.sax.Attributes;
027 import org.xml.sax.SAXException;
028 import org.xml.sax.SAXParseException;
029
030
031 /**
032 * Default SAX handler. Delegates SAX events to a
033 * {@link org.qedeq.kernel.xml.handler.common.AbstractSimpleHandler}
034 * which could also delegate events to other
035 * {@link org.qedeq.kernel.xml.handler.common.AbstractSimpleHandler}s.
036 * <p>
037 * Before anything is parsed the method {@link #setExceptionList(SourceFileExceptionList)}
038 * must be called.
039 *
040 * @author Michael Meyling
041 */
042 public class SaxDefaultHandler extends SimpleHandler {
043
044 /** This class. */
045 private static final Class CLASS = SaxDefaultHandler.class;
046
047 /** Delegate currently to this handler. */
048 private AbstractSimpleHandler currentHandler;
049
050 /** Stack of previous {@link AbstractSimpleHandler}s. */
051 private Stack handlerStack = new Stack();
052
053 /** Top level handler. This handler is activated after the begin of the document. */
054 private AbstractSimpleHandler basisHandler;
055
056 /** Collect errors in this object. */
057 private SourceFileExceptionList errorList;
058
059 /** Buffer for combining character events. */
060 private StringBuffer buffer = new StringBuffer(2000);
061
062 /** Tag level for current handler. */
063 private int level;
064
065 /** Tag level for previous handlers. */
066 private Stack levelStack = new Stack();
067
068 /** Current tag name. Could be <code>null</code>. */
069 private String currentElementName;
070
071 /** The plugin we work for. */
072 private final Plugin plugin;
073
074 /**
075 * Constructor.
076 *
077 * @param plugin The plugin we work for.
078 */
079 public SaxDefaultHandler(final Plugin plugin) {
080 super();
081 this.plugin = plugin;
082 }
083
084 /**
085 * Set parse exception list. This list collects occurring parsing errors.
086 *
087 * @param errorList Collect errors here.
088 */
089 public void setExceptionList(final SourceFileExceptionList errorList) {
090 this.errorList = errorList;
091 }
092
093 /**
094 * Set basis handler for documents.
095 *
096 * @param handler Basis handler for documents. This handler might also pass control to
097 * another handler via the
098 * {@link AbstractSimpleHandler#changeHandler(AbstractSimpleHandler, String, SimpleAttributes)}
099 * 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 Plugin getPlugin() {
348 return plugin;
349 }
350
351 }
|