/* Woodstox Lite ("wool") XML processor
 *
 * Copyright (c) 2006- Tatu Saloranta, tatu.saloranta@iki.fi
 *
 * Licensed under the License specified in the file LICENSE which is
 * included with the source code.
 * You may not use this file except in compliance with the License.
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.fasterxml.aalto.sax;

import java.io.*;
import java.net.URL;

import javax.xml.parsers.SAXParser;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;

import org.xml.sax.*;
import org.xml.sax.ext.Attributes2;
import org.xml.sax.ext.DeclHandler;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.ext.Locator2;
import org.xml.sax.helpers.DefaultHandler;

import com.fasterxml.aalto.in.*;
import com.fasterxml.aalto.stax.InputFactoryImpl;
import com.fasterxml.aalto.util.URLUtil;

@SuppressWarnings("deprecation")
class SAXParserImpl
    extends SAXParser
    implements Parser // SAX1
               ,XMLReader // SAX2
               ,Attributes2 // SAX2
               ,Locator2 // SAX2
{
    final InputFactoryImpl _staxFactory;

    
Since the stream reader would mostly be just a wrapper around the underlying scanner (its main job is to implement Stax interface), we can and should just use the scanner. In effect, this class is then a replacement of StreamReaderImpl, when using SAX interfaces.
/** * Since the stream reader would mostly be just a wrapper around * the underlying scanner (its main job is to implement Stax * interface), we can and should just use the scanner. In effect, * this class is then a replacement of StreamReaderImpl, when * using SAX interfaces. */
protected XmlScanner _scanner; protected AttributeCollector _attrCollector; // // // Listeners attached: protected ContentHandler _contentHandler; protected DTDHandler _dtdHandler; private EntityResolver _entityResolver; private ErrorHandler _errorHandler; private LexicalHandler _lexicalHandler; private DeclHandler _declHandler; // // // State: private int _attrCount; /* /********************************************************************** /* Life-cycle /********************************************************************** */ SAXParserImpl(InputFactoryImpl sf) { _staxFactory = sf; } @Override public final Parser getParser() { return this; } @Override public final XMLReader getXMLReader() { return this; } /* /********************************************************************** /* Configuration, SAXParser /********************************************************************** */ @Override public boolean isNamespaceAware() { return true; } @Override public boolean isValidating() { return false; } @Override public Object getProperty(String name) throws SAXNotRecognizedException, SAXNotSupportedException { SAXProperty stdProp = SAXUtil.findStdProperty(name); if (stdProp != null) { switch (stdProp) { case DECLARATION_HANDLER: return _declHandler; case DOCUMENT_XML_VERSION: // as per [Issue 9], provide version info (is it ok to return potentially null?) return _scanner.getConfig().getXmlDeclVersion(); case DOM_NODE: // not implemented, won't be return null; case LEXICAL_HANDLER: return _lexicalHandler; case XML_STRING: // not implemented, won't be return null; } } SAXUtil.reportUnknownProperty(name); return null; } @Override public void setProperty(String name, Object value) throws SAXNotRecognizedException, SAXNotSupportedException { SAXProperty stdProp = SAXUtil.findStdProperty(name); if (stdProp != null) { switch (stdProp) { case DECLARATION_HANDLER: _declHandler = (DeclHandler) value; return; case DOCUMENT_XML_VERSION: // as per [Issue 9]: _scanner.getConfig().setXmlVersion((value == null) ? null : String.valueOf(value)); return; case DOM_NODE: // not implemented, won't be return; case LEXICAL_HANDLER: _lexicalHandler = (LexicalHandler) value; return; case XML_STRING: // not implemented, won't be return; } } SAXUtil.reportUnknownFeature(name); } /* /********************************************************************** /* Overrides, SAXParser /********************************************************************** */ /* Have to override some methods from SAXParser; JDK * implementation is sucky, as it tries to override * many things it really should not... */ @Override public void parse(InputSource is, HandlerBase hb) throws SAXException, IOException { if (hb != null) { /* Ok: let's ONLY set if there are no explicit sets... not * extremely clear, but JDK tries to set them always so * let's at least do damage control. */ if (_contentHandler == null) { setDocumentHandler(hb); } if (_entityResolver == null) { setEntityResolver(hb); } if (_errorHandler == null) { setErrorHandler(hb); } if (_dtdHandler == null) { setDTDHandler(hb); } } parse(is); } @Override public void parse(InputSource is, DefaultHandler dh) throws SAXException, IOException { if (dh != null) { /* Ok: let's ONLY set if there are no explicit sets... not * extremely clear, but JDK tries to set them always so * let's at least do damage control. */ if (_contentHandler == null) { setContentHandler(dh); } if (_entityResolver == null) { setEntityResolver(dh); } if (_errorHandler == null) { setErrorHandler(dh); } if (_dtdHandler == null) { setDTDHandler(dh); } } parse(is); } /* /********************************************************************** /* XLMReader (SAX2) implementation: cfg access /********************************************************************** */ @Override public ContentHandler getContentHandler() { return _contentHandler; } @Override public DTDHandler getDTDHandler() { return _dtdHandler; } @Override public EntityResolver getEntityResolver() { return _entityResolver; } @Override public ErrorHandler getErrorHandler() { return _errorHandler; } @Override public boolean getFeature(String name) throws SAXNotRecognizedException { // Standard feature? SAXFeature stdFeat = SAXUtil.findStdFeature(name); if (stdFeat != null) { // fixed? Boolean b = SAXUtil.getFixedStdFeatureValue(stdFeat); if (b != null) { return b.booleanValue(); } // ok, may change: switch (stdFeat) { case IS_STANDALONE: // read-only, but only during parsing // !!! TBI return true; default: } } else { // any non-standard one we may support? } // nope, not recognized: SAXUtil.reportUnknownFeature(name); return false; // never gets here } // Already implemented for SAXParser //public Object getProperty(String name) /* /********************************************************************** /* XLMReader (SAX2) implementation: cfg changing /********************************************************************** */ @Override public void setContentHandler(ContentHandler handler) { _contentHandler = handler; } @Override public void setDTDHandler(DTDHandler handler) { _dtdHandler = handler; } @Override public void setEntityResolver(EntityResolver resolver) { _entityResolver = resolver; } @Override public void setErrorHandler(ErrorHandler handler) { _errorHandler = handler; } @Override public void setFeature(String name, boolean value) throws SAXNotRecognizedException { // Standard feature? SAXFeature stdFeat = SAXUtil.findStdFeature(name); if (stdFeat != null) { //boolean ok; // !!! TBI /* switch (stdFeat) { } */ } else { SAXUtil.reportUnknownFeature(name); } } // Already implemented for SAXParser //public void setProperty(String name, Object value) /* /********************************************************************** /* XLMReader (SAX2) implementation: parsing /********************************************************************** */ @Override public void parse(InputSource input) throws SAXException { String enc = input.getEncoding(); String systemId = input.getSystemId(); /* Let's ask for default (non-event-reader-bound) reader * first. One open question: whether auto-closing needs to be * forced? For now, let's assume not (second false, first is for * 'isForEventReader') */ ReaderConfig cfg = _staxFactory.getNonSharedConfig (systemId, input.getPublicId(), enc, false, false); /* But let's disable lazy parsing: with SAX there's no good * way to make use of it (similar to why it's disabled for * event readers) */ cfg.doParseLazily(false); // Let's figure out input, first, before sending start-doc event InputStream is = null; Reader r = input.getCharacterStream(); if (r == null) { is = input.getByteStream(); if (is == null) { if (systemId == null) { throw new SAXException("Invalid InputSource passed: neither character or byte stream passed, nor system id specified"); } try { URL url = URLUtil.urlFromSystemId(systemId); is = URLUtil.inputStreamFromURL(url); } catch (IOException ioe) { SAXException saxe = new SAXException(ioe); if (saxe.getCause() == null) { saxe.initCause(ioe); } throw saxe; } } } if (_contentHandler != null) { _contentHandler.setDocumentLocator(this); _contentHandler.startDocument(); } try { if (r != null) { _scanner = CharSourceBootstrapper.construct(cfg, r).bootstrap(); } else { _scanner = ByteSourceBootstrapper.construct(cfg, is).bootstrap(); } _attrCollector = _scanner.getAttrCollector(); fireEvents(); } catch (XMLStreamException strex) { throwSaxException(strex); } finally { if (_contentHandler != null) { _contentHandler.endDocument(); } /* Could try holding onto the buffers, too... but * maybe it's better to allow them to be reclaimed, if * needed by GC */ if (_scanner != null) { try { _scanner.close(false); // false -> no forced closing of source } catch (XMLStreamException strex) { /* Hmmh. Should we bother trying to throw it? Should * never really happen as it can only occur from * stream.close() failing... which is a useless exception * if it can occur. So, for once, let's just supress it. */ ; // intentional no-action } _scanner = null; } if (r != null) { try { r.close(); } catch (IOException ioe) { /* whatever */ } } if (is != null) { try { is.close(); } catch (IOException ioe) { /* whatever */ } } } } @Override public void parse(String systemId) throws SAXException { InputSource src = new InputSource(systemId); parse(src); } /* /********************************************************************** /* Parsing loop, helper methods /********************************************************************** */
This is the actual "tight event loop" that will send all events between start and end document events. Although we could use the stream reader here, there's not much as it mostly just forwards requests to the scanner: and so we can as well just copy the little code stream reader's next() method has.
/** * This is the actual "tight event loop" that will send all events * between start and end document events. Although we could * use the stream reader here, there's not much as it mostly * just forwards requests to the scanner: and so we can as well * just copy the little code stream reader's next() method has. */
private final void fireEvents() throws SAXException, XMLStreamException { // First we are in prolog: int type; while ((type = _scanner.nextFromProlog(true)) != XMLStreamConstants.START_ELEMENT) { fireAuxEvent(type, false); } // Now just starting the tree, need to process the START_ELEMENT fireStartTag(); int depth = 1; while (true) { type = _scanner.nextFromTree(); if (type == XMLStreamConstants.START_ELEMENT) { fireStartTag(); ++depth; } else if (type == XMLStreamConstants.END_ELEMENT) { fireEndTag(); if (--depth < 1) { break; } } else if (type == XMLStreamConstants.CHARACTERS) { _scanner.fireSaxCharacterEvents(_contentHandler); } else { fireAuxEvent(type, true); } } // And then epilog: while (true) { type = _scanner.nextFromProlog(false); if (type == XmlScanner.TOKEN_EOI) { break; } if (type == XmlScanner.SPACE) { /* Not to be reported via SAX interface (which may or may not * be different from Stax) */ continue; } fireAuxEvent(type, false); } } private final void fireAuxEvent(int type, boolean inTree) throws SAXException, XMLStreamException { switch (type) { case XMLStreamConstants.COMMENT: _scanner.fireSaxCommentEvent(_lexicalHandler); break; case XMLStreamConstants.CDATA: if (_lexicalHandler != null) { _lexicalHandler.startCDATA(); _scanner.fireSaxCharacterEvents(_contentHandler); _lexicalHandler.endCDATA(); } else { _scanner.fireSaxCharacterEvents(_contentHandler); } break; case XMLStreamConstants.DTD: if (_lexicalHandler != null) { PName n = _scanner.getName(); _lexicalHandler.startDTD(n.getPrefixedName(), _scanner.getDTDPublicId(), _scanner.getDTDSystemId()); _lexicalHandler.endDTD(); } break; case XMLStreamConstants.PROCESSING_INSTRUCTION: _scanner.fireSaxPIEvent(_contentHandler); break; case XMLStreamConstants.SPACE: /* With SAX, only to be sent as an event if inside the * tree, not from within prolog/epilog */ if (inTree) { _scanner.fireSaxSpaceEvents(_contentHandler); } break; default: if (type == XmlScanner.TOKEN_EOI) { throwSaxException("Unexpected end-of-input in "+(inTree ? "tree" : "prolog")); } throw new RuntimeException("Internal error: unexpected type, "+type); } } private final void fireStartTag() throws SAXException { _attrCount = _scanner.getAttrCount(); _scanner.fireSaxStartElement(_contentHandler, this); } private final void fireEndTag() throws SAXException { _scanner.fireSaxEndElement(_contentHandler); } /* /********************************************************************** /* Parser (SAX1) implementation /********************************************************************** */ // Already implemented for XMLReader: //public void parse(InputSource source) //public void parse(String systemId) //public void setEntityResolver(EntityResolver resolver) //public void setErrorHandler(ErrorHandler handler) @Override public void setDocumentHandler(DocumentHandler handler) { setContentHandler(new DocHandlerWrapper(handler)); } @Override public void setLocale(java.util.Locale locale) { // Not supported, let's just ignore } /* /********************************************************************** /* Attributes (SAX2) implementation /********************************************************************** */ @Override public int getIndex(String qName) { return (_attrCollector == null) ? -1 : _attrCollector.findIndex(null, qName); } @Override public int getIndex(String uri, String localName) { return (_attrCollector == null) ? -1 : _attrCollector.findIndex(uri, localName); } @Override public int getLength() { return _attrCount; } @Override public String getLocalName(int index) { return (index < 0 || index >= _attrCount) ? null : _attrCollector.getName(index).getLocalName(); } @Override public String getQName(int index) { return (index < 0 || index >= _attrCount) ? null : _attrCollector.getName(index).getPrefixedName(); } @Override public String getType(int index) { /* 13-Sep-2006, tatus: Note: not yet really implemented, will * just return "CDATA". */ return (index < 0 || index >= _attrCount) ? null : _scanner.getAttrType(index); } @Override public String getType(String qName) { int ix = getIndex(qName); return (ix < 0) ? null : _scanner.getAttrType(ix); } @Override public String getType(String uri, String localName) { int ix = getIndex(uri, localName); return (ix < 0) ? null : _scanner.getAttrType(ix); } @Override public String getURI(int index) { if (index < 0 || index >= _attrCount) { return null; } String uri = _attrCollector.getName(index).getNsUri(); return (uri == null) ? "" : uri; } @Override public String getValue(int index) { return (index < 0 || index >= _attrCount) ? null : _attrCollector.getValue(index); } @Override public String getValue(String qName) { int ix = getIndex(qName); return (ix < 0) ? null : _attrCollector.getValue(ix); } @Override public String getValue(String uri, String localName) { int ix = getIndex(uri, localName); return (ix < 0) ? null : _attrCollector.getValue(ix); } /* /********************************************************************** /* Attributes2 (SAX2) implementation /********************************************************************** */ /* Note: for now (in absence of DTD processing), none of attributes * are declared, and all are specified (can not default without * a DTD) */ @Override public boolean isDeclared(int index) { return false; } @Override public boolean isDeclared(String qName) { return false; } @Override public boolean isDeclared(String uri, String localName) { return false; } @Override public boolean isSpecified(int index) { return true; } @Override public boolean isSpecified(String qName) { return true; } @Override public boolean isSpecified(String uri, String localName) { return true; } /* /********************************************************************** /* Locator (SAX1) implementation /********************************************************************** */ @Override public int getColumnNumber() { return (_scanner != null) ? _scanner.getCurrentColumnNr() : -1; } @Override public int getLineNumber() { return (_scanner != null) ? _scanner.getCurrentLineNr() : -1; } @Override public String getPublicId() { return (_scanner != null) ? _scanner.getInputPublicId() : null; } @Override public String getSystemId() { return (_scanner != null) ? _scanner.getInputSystemId() : null; } /* /********************************************************************** /* Locator2 (SAX2) implementation /********************************************************************** */ @Override public String getEncoding() { ReaderConfig cfg = _scanner.getConfig(); String enc = cfg.getActualEncoding(); if (enc == null) { enc = cfg.getXmlDeclEncoding(); if (enc == null) { enc = cfg.getExternalEncoding(); } } return enc; } @Override public String getXMLVersion() { return _scanner.getConfig().getXmlDeclVersion(); } /* /********************************************************************** /* Internal methods /********************************************************************** */ private void throwSaxException(Exception e) throws SAXException { SAXParseException se = new SAXParseException(e.getMessage(), (Locator) this, e); if (se.getCause() == null) { se.initCause(e); } if (_errorHandler != null) { _errorHandler.fatalError(se); } throw se; } private void throwSaxException(String msg) throws SAXException { SAXParseException se = new SAXParseException(msg, (Locator) this); if (_errorHandler != null) { _errorHandler.fatalError(se); } throw se; } /* /********************************************************************** /* Helper classes for SAX1 support /********************************************************************** */ final static class DocHandlerWrapper implements ContentHandler { final DocumentHandler mDocHandler; final AttributesWrapper mAttrWrapper = new AttributesWrapper(); DocHandlerWrapper(DocumentHandler h) { mDocHandler = h; } @Override public void characters(char[] ch, int start, int length) throws SAXException { mDocHandler.characters(ch, start, length); } @Override public void endDocument() throws SAXException { mDocHandler.endDocument(); } @Override public void endElement(String uri, String localName, String qName) throws SAXException { if (qName == null) { qName = localName; } mDocHandler.endElement(qName); } @Override public void endPrefixMapping(String prefix) { // no equivalent in SAX1, ignore } @Override public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { mDocHandler.ignorableWhitespace(ch, start, length); } @Override public void processingInstruction(String target, String data) throws SAXException { mDocHandler.processingInstruction(target, data); } @Override public void setDocumentLocator(Locator locator) { mDocHandler.setDocumentLocator(locator); } @Override public void skippedEntity(String name) { // no equivalent in SAX1, ignore } @Override public void startDocument() throws SAXException { mDocHandler.startDocument(); } @Override public void startElement(String uri, String localName, String qName, Attributes attrs) throws SAXException { if (qName == null) { qName = localName; } // Also, need to wrap Attributes to look like AttributeLost mAttrWrapper.setAttributes(attrs); mDocHandler.startElement(qName, mAttrWrapper); } @Override public void startPrefixMapping(String prefix, String uri) { // no equivalent in SAX1, ignore } } final static class AttributesWrapper implements AttributeList { Attributes mAttrs; public AttributesWrapper() { } public void setAttributes(Attributes a) { mAttrs = a; } @Override public int getLength() { return mAttrs.getLength(); } @Override public String getName(int i) { String n = mAttrs.getQName(i); return (n == null) ? mAttrs.getLocalName(i) : n; } @Override public String getType(int i) { return mAttrs.getType(i); } @Override public String getType(String name) { return mAttrs.getType(name); } @Override public String getValue(int i) { return mAttrs.getValue(i); } @Override public String getValue(String name) { return mAttrs.getValue(name); } } }