/* 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);
}
}
}