View Javadoc

1   /* This file is part of the project "Hilbert II" - http://www.qedeq.org" target="alexandria_uri">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>&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 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 }