SaxDefaultHandler.java
001 /* This file is part of the project "Hilbert II" - http://www.qedeq.org
002  *
003  * Copyright 2000-2011,  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.DefaultSourceFileExceptionList;
023 import org.qedeq.kernel.se.common.Plugin;
024 import org.qedeq.kernel.se.common.SourceFileException;
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(DefaultSourceFileExceptionList)}
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 DefaultSourceFileExceptionList 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 DefaultSourceFileExceptionList 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 amapthrows 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>&lt;banana&gt;</code> tag, the
249      * old handler is restored when the misspelled <code>&lt;/bnana&gt</code> tag occurs:
250      <p>
251      <pre>
252      * &lt;banana&gt;
253      *      &lt;one /&gt;
254      *      &lt;two &gt;
255      *          &lt;one /&gt;
256      *          &lt;one /&gt;
257      *      &lt;/two &gt;
258      * &lt;/bnana&gt
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 elementNamethrows XmlSyntaxException {
287         while (level <= && !handlerStack.empty()) {
288             currentHandler = (AbstractSimpleHandlerhandlerStack.pop();
289             Trace.param(CLASS, this, "restoreHandler""currentHandler", currentHandler);
290             level = ((IntegerlevelStack.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 }