| 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 | } |