/*
* Copyright (c) 2004- Tatu Saloranta, tatu.saloranta@iki.fi
*
* Licensed under the License specified in file LICENSE, 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.ctc.wstx.sax;
import java.io.*;
import java.net.URL;
import javax.xml.XMLConstants;
import javax.xml.parsers.SAXParser;
import javax.xml.stream.Location;
import javax.xml.stream.XMLResolver;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import org.xml.sax.*;
import org.xml.sax.helpers.DefaultHandler;
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.codehaus.stax2.DTDInfo;
import com.ctc.wstx.api.ReaderConfig;
import com.ctc.wstx.dtd.DTDEventListener;
import com.ctc.wstx.exc.WstxIOException;
import com.ctc.wstx.io.DefaultInputResolver;
import com.ctc.wstx.io.InputBootstrapper;
import com.ctc.wstx.io.ReaderBootstrapper;
import com.ctc.wstx.io.StreamBootstrapper;
import com.ctc.wstx.sr.*;
import com.ctc.wstx.stax.WstxInputFactory;
import com.ctc.wstx.util.ExceptionUtil;
import com.ctc.wstx.util.URLUtil;
/**
* This class implements parser part of JAXP and SAX interfaces; and
* effectively offers an alternative to using Stax input factory /
* stream reader combination.
*/
@SuppressWarnings("deprecation")
public class WstxSAXParser
extends SAXParser
implements Parser // SAX1
,XMLReader // SAX2
,Attributes2 // SAX2
,Locator2 // SAX2
,DTDEventListener // Woodstox-internal
{
final static boolean FEAT_DEFAULT_NS_PREFIXES = false;
/**
* We will need the factory reference mostly for constructing
* underlying stream reader we use.
*/
protected final WstxInputFactory mStaxFactory;
protected final ReaderConfig mConfig;
protected boolean mFeatNsPrefixes;
/**
* 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 BasicStreamReader, when
* using SAX interfaces.
*/
protected BasicStreamReader mScanner;
protected AttributeCollector mAttrCollector;
protected InputElementStack mElemStack;
// // // Info from xml declaration
protected String mEncoding;
protected String mXmlVersion;
protected boolean mStandalone;
// // // Listeners attached:
protected ContentHandler mContentHandler;
protected DTDHandler mDTDHandler;
private EntityResolver mEntityResolver;
private ErrorHandler mErrorHandler;
private LexicalHandler mLexicalHandler;
private DeclHandler mDeclHandler;
// // // State:
/**
* Number of attributes accessible via {@link Attributes} and
* {@link Attributes2} interfaces, for the current start element.
*<p>
* Note: does not include namespace declarations, even they are to
* be reported as attributes.
*/
protected int mAttrCount;
/**
* Need to keep track of number of namespaces, if namespace declarations
* are to be reported along with attributes (see
* {@link #mFeatNsPrefixes}).
*/
protected int mNsCount = 0;
/*
/////////////////////////////////////////////////
// Life-cycle
/////////////////////////////////////////////////
*/
/**
*<p>
* NOTE: this was a protected constructor for versions 4.0
* and 3.2; changed to public in 4.1
*/
public WstxSAXParser(WstxInputFactory sf, boolean nsPrefixes)
{
mStaxFactory = sf;
mFeatNsPrefixes = nsPrefixes;
mConfig = sf.createPrivateConfig();
mConfig.doSupportDTDs(true);
/* Lazy parsing is a tricky thing: although most of the time
* it's useless with SAX, it is actually necessary to be able
* to properly model internal DTD subsets, for example. So,
* we can not really easily determine defaults.
*/
ResolverProxy r = new ResolverProxy();
/* SAX doesn't distinguish between DTD (ext. subset, PEs) and
* entity (external general entities) resolvers, so let's
* assign them both:
*/
mConfig.setDtdResolver(r);
mConfig.setEntityResolver(r);
mConfig.setDTDEventListener(this);
/* These settings do NOT make sense as generic defaults, but
* are helpful when using some test frameworks. Specifically,
* - DTD caching may remove calls to resolvers, changing
* observed behavior
* - Using min. segment length of 1 will force flushing of
* all content before entity expansion, which will
* completely serialize entity resolution calls wrt.
* CHARACTERS events.
*/
// !!! ONLY for testing; never remove for prod use
//mConfig.setShortestReportedTextSegment(1);
//mConfig.doCacheDTDs(false);
}
/*
* This constructor is provided for two main use cases: testing,
* and introspection via SAX classes (as opposed to JAXP-based
* introspection).
*/
public WstxSAXParser()
{
this(new WstxInputFactory(), FEAT_DEFAULT_NS_PREFIXES);
}
public final Parser getParser()
{
return this;
}
public final XMLReader getXMLReader()
{
return this;
}
/**
* Accessor used to allow configuring all standard Stax configuration
* settings that the underlying reader uses.
*
* @since 4.0.8
*/
public final ReaderConfig getStaxConfig() {
return mConfig;
}
/*
/////////////////////////////////////////////////
// Configuration, SAXParser
/////////////////////////////////////////////////
*/
public boolean isNamespaceAware() {
return mConfig.willSupportNamespaces();
}
public boolean isValidating() {
return mConfig.willValidateWithDTD();
}
public Object getProperty(String name)
throws SAXNotRecognizedException, SAXNotSupportedException
{
SAXProperty prop = SAXProperty.findByUri(name);
if (prop == SAXProperty.DECLARATION_HANDLER) {
return mDeclHandler;
} else if (prop == SAXProperty.DOCUMENT_XML_VERSION) {
return mXmlVersion;
} else if (prop == SAXProperty.DOM_NODE) {
return null;
} else if (prop == SAXProperty.LEXICAL_HANDLER) {
return mLexicalHandler;
} else if (prop == SAXProperty.XML_STRING) {
return null;
}
throw new SAXNotRecognizedException("Property '"+name+"' not recognized");
}
public void setProperty(String name, Object value)
throws SAXNotRecognizedException, SAXNotSupportedException
{
SAXProperty prop = SAXProperty.findByUri(name);
if (prop == SAXProperty.DECLARATION_HANDLER) {
mDeclHandler = (DeclHandler) value;
return;
} else if (prop == SAXProperty.DOCUMENT_XML_VERSION) {
; // read-only
} else if (prop == SAXProperty.DOM_NODE) {
; // read-only
} else if (prop == SAXProperty.LEXICAL_HANDLER) {
mLexicalHandler = (LexicalHandler) value;
return;
} else if (prop == SAXProperty.XML_STRING) {
; // read-only
} else {
throw new SAXNotRecognizedException("Property '"+name+"' not recognized");
}
// Trying to modify read-only properties?
throw new SAXNotSupportedException("Property '"+name+"' is read-only, can not be modified");
}
/*
/////////////////////////////////////////////////
// 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 (mContentHandler == null) {
setDocumentHandler(hb);
}
if (mEntityResolver == null) {
setEntityResolver(hb);
}
if (mErrorHandler == null) {
setErrorHandler(hb);
}
if (mDTDHandler == 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 (mContentHandler == null) {
setContentHandler(dh);
}
if (mEntityResolver == null) {
setEntityResolver(dh);
}
if (mErrorHandler == null) {
setErrorHandler(dh);
}
if (mDTDHandler == null) {
setDTDHandler(dh);
}
}
parse(is);
}
/*
/////////////////////////////////////////////////////
// XLMReader (SAX2) implementation: cfg access
/////////////////////////////////////////////////////
*/
public ContentHandler getContentHandler()
{
return mContentHandler;
}
public DTDHandler getDTDHandler()
{
return mDTDHandler;
}
public EntityResolver getEntityResolver()
{
return mEntityResolver;
}
public ErrorHandler getErrorHandler()
{
return mErrorHandler;
}
public boolean getFeature(String name)
throws SAXNotRecognizedException
{
SAXFeature stdFeat = SAXFeature.findByUri(name);
if (stdFeat == SAXFeature.EXTERNAL_GENERAL_ENTITIES) {
return mConfig.willSupportExternalEntities();
} else if (stdFeat == SAXFeature.EXTERNAL_PARAMETER_ENTITIES) {
return mConfig.willSupportExternalEntities();
} else if (stdFeat == SAXFeature.IS_STANDALONE) {
return mStandalone;
} else if (stdFeat == SAXFeature.LEXICAL_HANDLER_PARAMETER_ENTITIES) {
// !!! TODO:
return false;
} else if (stdFeat == SAXFeature.NAMESPACES) {
return mConfig.willSupportNamespaces();
} else if (stdFeat == SAXFeature.NAMESPACE_PREFIXES) {
return !mConfig.willSupportNamespaces();
} else if (stdFeat == SAXFeature.RESOLVE_DTD_URIS) {
// !!! TODO:
return false;
} else if (stdFeat == SAXFeature.STRING_INTERNING) {
return true;
} else if (stdFeat == SAXFeature.UNICODE_NORMALIZATION_CHECKING) {
return false;
} else if (stdFeat == SAXFeature.USE_ATTRIBUTES2) {
return true;
} else if (stdFeat == SAXFeature.USE_LOCATOR2) {
return true;
} else if (stdFeat == SAXFeature.USE_ENTITY_RESOLVER2) {
return true;
} else if (stdFeat == SAXFeature.VALIDATION) {
return mConfig.willValidateWithDTD();
} else if (stdFeat == SAXFeature.XMLNS_URIS) {
/* !!! TODO: default value should be false... but not sure
* if implementing that mode makes sense
*/
return true;
} else if (stdFeat == SAXFeature.XML_1_1) {
return true;
}
throw new SAXNotRecognizedException("Feature '"+name+"' not recognized");
}
// Already implemented for SAXParser
//public Object getProperty(String name)
/*
/////////////////////////////////////////////////////
// XLMReader (SAX2) implementation: cfg changing
/////////////////////////////////////////////////////
*/
public void setContentHandler(ContentHandler handler)
{
mContentHandler = handler;
}
public void setDTDHandler(DTDHandler handler)
{
mDTDHandler = handler;
}
public void setEntityResolver(EntityResolver resolver)
{
mEntityResolver = resolver;
}
public void setErrorHandler(ErrorHandler handler)
{
mErrorHandler = handler;
}
public void setFeature(String name, boolean value)
throws SAXNotRecognizedException, SAXNotSupportedException
{
boolean invalidValue = false;
boolean readOnly = false;
SAXFeature stdFeat = SAXFeature.findByUri(name);
if (stdFeat == SAXFeature.EXTERNAL_GENERAL_ENTITIES) {
mConfig.doSupportExternalEntities(value);
} else if (stdFeat == SAXFeature.EXTERNAL_PARAMETER_ENTITIES) {
// !!! TODO
} else if (stdFeat == SAXFeature.IS_STANDALONE) {
readOnly = true;
} else if (stdFeat == SAXFeature.LEXICAL_HANDLER_PARAMETER_ENTITIES) {
// !!! TODO
} else if (stdFeat == SAXFeature.NAMESPACES) {
mConfig.doSupportNamespaces(value);
} else if (stdFeat == SAXFeature.NAMESPACE_PREFIXES) {
mFeatNsPrefixes = value;
} else if (stdFeat == SAXFeature.RESOLVE_DTD_URIS) {
// !!! TODO
} else if (stdFeat == SAXFeature.STRING_INTERNING) {
invalidValue = !value;
} else if (stdFeat == SAXFeature.UNICODE_NORMALIZATION_CHECKING) {
invalidValue = value;
} else if (stdFeat == SAXFeature.USE_ATTRIBUTES2) {
readOnly = true;
} else if (stdFeat == SAXFeature.USE_LOCATOR2) {
readOnly = true;
} else if (stdFeat == SAXFeature.USE_ENTITY_RESOLVER2) {
readOnly = true;
} else if (stdFeat == SAXFeature.VALIDATION) {
mConfig.doValidateWithDTD(value);
} else if (stdFeat == SAXFeature.XMLNS_URIS) {
invalidValue = !value;
} else if (stdFeat == SAXFeature.XML_1_1) {
readOnly = true;
} else {
throw new SAXNotRecognizedException("Feature '"+name+"' not recognized");
}
// Trying to modify read-only properties?
if (readOnly) {
throw new SAXNotSupportedException("Feature '"+name+"' is read-only, can not be modified");
}
if (invalidValue) {
throw new SAXNotSupportedException("Trying to set invalid value for feature '"+name+"', '"+value+"'");
}
}
// Already implemented for SAXParser
//public void setProperty(String name, Object value)
/*
/////////////////////////////////////////////////////
// XLMReader (SAX2) implementation: parsing
/////////////////////////////////////////////////////
*/
public void parse(InputSource input)
throws SAXException
{
mScanner = null;
String systemId = input.getSystemId();
ReaderConfig cfg = mConfig;
URL srcUrl = null;
// 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 {
srcUrl = URLUtil.urlFromSystemId(systemId);
is = URLUtil.inputStreamFromURL(srcUrl);
} catch (IOException ioe) {
SAXException saxe = new SAXException(ioe);
ExceptionUtil.setInitCause(saxe, ioe);
throw saxe;
}
}
}
if (mContentHandler != null) {
mContentHandler.setDocumentLocator(this);
mContentHandler.startDocument();
}
/* Note: since we are reusing the same config instance, need to
* make sure state is not carried forward. Thus:
*/
cfg.resetState();
try {
String inputEnc = input.getEncoding();
String publicId = input.getPublicId();
// Got an InputStream and encoding? Can create a Reader:
if (r == null && (inputEnc != null && inputEnc.length() > 0)) {
r = DefaultInputResolver.constructOptimizedReader(cfg, is, false, inputEnc);
}
InputBootstrapper bs;
if (r != null) {
bs = ReaderBootstrapper.getInstance(publicId, systemId, r, inputEnc);
// false -> not for event reader; false -> no auto-closing
mScanner = (BasicStreamReader) mStaxFactory.createSR(cfg, systemId, bs, false, false);
} else {
bs = StreamBootstrapper.getInstance(publicId, systemId, is);
mScanner = (BasicStreamReader) mStaxFactory.createSR(cfg, systemId, bs, false, false);
}
// Need to get xml declaration stuff out now:
{
String enc2 = mScanner.getEncoding();
if (enc2 == null) {
enc2 = mScanner.getCharacterEncodingScheme();
}
mEncoding = enc2;
}
mXmlVersion = mScanner.getVersion();
mStandalone = mScanner.standaloneSet();
mAttrCollector = mScanner.getAttributeCollector();
mElemStack = mScanner.getInputElementStack();
fireEvents();
} catch (IOException io) {
throwSaxException(io);
} catch (XMLStreamException strex) {
throwSaxException(strex);
} finally {
if (mContentHandler != null) {
mContentHandler.endDocument();
}
// Could try holding onto the buffers, too... but
// maybe it's better to allow them to be reclaimed, if
// needed by GC
if (mScanner != null) {
BasicStreamReader sr = mScanner;
mScanner = null;
try {
sr.close();
} catch (XMLStreamException sex) { }
}
if (r != null) {
try {
r.close();
} catch (IOException ioe) { }
}
if (is != null) {
try {
is.close();
} catch (IOException ioe) { }
}
}
}
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.
*/
private final void fireEvents()
throws IOException, SAXException, XMLStreamException
{
// First we are in prolog:
int type;
/* Need to enable lazy parsing, to get DTD start events before
* its content events. Plus, can skip more efficiently too.
*/
mConfig.doParseLazily(false);
while ((type = mScanner.next()) != XMLStreamConstants.START_ELEMENT) {
fireAuxEvent(type, false);
}
// Now just starting the tree, need to process the START_ELEMENT
fireStartTag();
int depth = 1;
while (true) {
type = mScanner.next();
if (type == XMLStreamConstants.START_ELEMENT) {
fireStartTag();
++depth;
} else if (type == XMLStreamConstants.END_ELEMENT) {
mScanner.fireSaxEndElement(mContentHandler);
if (--depth < 1) {
break;
}
} else if (type == XMLStreamConstants.CHARACTERS) {
mScanner.fireSaxCharacterEvents(mContentHandler);
} else {
fireAuxEvent(type, true);
}
}
// And then epilog:
while (true) {
type = mScanner.next();
if (type == XMLStreamConstants.END_DOCUMENT) {
break;
}
if (type == XMLStreamConstants.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 IOException, SAXException, XMLStreamException
{
switch (type) {
case XMLStreamConstants.COMMENT:
mScanner.fireSaxCommentEvent(mLexicalHandler);
break;
case XMLStreamConstants.CDATA:
if (mLexicalHandler != null) {
mLexicalHandler.startCDATA();
mScanner.fireSaxCharacterEvents(mContentHandler);
mLexicalHandler.endCDATA();
} else {
mScanner.fireSaxCharacterEvents(mContentHandler);
}
break;
case XMLStreamConstants.DTD:
if (mLexicalHandler != null) {
/* Note: this is bit tricky, since calling getDTDInfo() will
* trigger full reading of the subsets... but we need to
* get some info first, to be able to send dtd-start event,
* and only then get the rest. Thus, need to call separate
* accessors first:
*/
String rootName = mScanner.getDTDRootName();
String sysId = mScanner.getDTDSystemId();
String pubId = mScanner.getDTDPublicId();
mLexicalHandler.startDTD(rootName, pubId, sysId);
// Ok, let's get rest (if any) read:
try {
/*DTDInfo dtdInfo =*/ mScanner.getDTDInfo();
} catch (WrappedSaxException wse) {
throw wse.getSaxException();
}
mLexicalHandler.endDTD();
}
break;
case XMLStreamConstants.PROCESSING_INSTRUCTION:
mScanner.fireSaxPIEvent(mContentHandler);
break;
case XMLStreamConstants.SPACE:
// With SAX, only to be sent as an event if inside the
// tree, not from within prolog/epilog
if (inTree) {
mScanner.fireSaxSpaceEvents(mContentHandler);
}
break;
case XMLStreamConstants.ENTITY_REFERENCE:
/* Only occurs in non-entity-expanding mode; so effectively
* we are skipping the entity?
*/
if (mContentHandler != null) {
mContentHandler.skippedEntity(mScanner.getLocalName());
}
break;
default:
if (type == XMLStreamConstants.END_DOCUMENT) {
throwSaxException("Unexpected end-of-input in "+(inTree ? "tree" : "prolog"));
}
throw new RuntimeException("Internal error: unexpected type, "+type);
}
}
private final void fireStartTag()
throws SAXException
{
mAttrCount = mAttrCollector.getCount();
if (mFeatNsPrefixes) {
/* 15-Dec-2006, TSa: Note: apparently namespace bindings that
* are added via defaulting are only visible via element
* stack. Thus, we MUST access things via element stack,
* not attribute collector; even though latter seems like
* the more direct route. See
* {@link NsInputElementStack#addNsBinding} for the method
* that injects such special namespace bindings (yes, it's
* a hack, afterthought)
*/
//mNsCount = mAttrCollector.getNsCount();
mNsCount = mElemStack.getCurrentNsCount();
}
mScanner.fireSaxStartElement(mContentHandler, this);
}
/*
/////////////////////////////////////////////////
// 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)
public void setDocumentHandler(DocumentHandler handler)
{
setContentHandler(new DocHandlerWrapper(handler));
}
public void setLocale(java.util.Locale locale)
{
// Not supported, let's just ignore
}
/*
/////////////////////////////////////////////////////
// Attributes (SAX2) implementation
/////////////////////////////////////////////////////
*/
public int getIndex(String qName)
{
if (mElemStack == null) {
return -1;
}
int ix = mElemStack.findAttributeIndex(null, qName);
// !!! In ns-as-attrs mode, should also match ns decls?
return ix;
}
public int getIndex(String uri, String localName)
{
if (mElemStack == null) {
return -1;
}
int ix = mElemStack.findAttributeIndex(uri, localName);
// !!! In ns-as-attrs mode, should also match ns decls?
return ix;
}
public int getLength()
{
return mAttrCount + mNsCount;
}
public String getLocalName(int index)
{
if (index < mAttrCount) {
return (index < 0) ? null : mAttrCollector.getLocalName(index);
}
index -= mAttrCount;
if (index < mNsCount) {
/* As discussed in <code>fireStartTag</code>, we must use
* element stack, not attribute collector:
*/
//String prefix = mAttrCollector.getNsPrefix(index);
String prefix = mElemStack.getLocalNsPrefix(index);
return (prefix == null || prefix.length() == 0) ?
"xmlns" : prefix;
}
return null;
}
public String getQName(int index)
{
if (index < mAttrCount) {
if (index < 0) {
return null;
}
String prefix = mAttrCollector.getPrefix(index);
String ln = mAttrCollector.getLocalName(index);
return (prefix == null || prefix.length() == 0) ?
ln : (prefix + ":" + ln);
}
index -= mAttrCount;
if (index < mNsCount) {
/* As discussed in <code>fireStartTag</code>, we must use
* element stack, not attribute collector:
*/
//String prefix = mAttrCollector.getNsPrefix(index);
String prefix = mElemStack.getLocalNsPrefix(index);
if (prefix == null || prefix.length() == 0) {
return "xmlns";
}
return "xmlns:"+prefix;
}
return null;
}
public String getType(int index)
{
if (index < mAttrCount) {
if (index < 0) {
return null;
}
/* Note: Woodstox will have separate type for enumerated values;
* SAX considers these NMTOKENs, so may need to convert (but
* note: some SAX impls also use "ENUMERATED")
*/
String type = mElemStack.getAttributeType(index);
// Let's count on it being interned:
if (type == "ENUMERATED") {
type = "NMTOKEN";
}
return type;
}
// But how about namespace declarations... let's just call them CDATA?
index -= mAttrCount;
if (index < mNsCount) {
return "CDATA";
}
return null;
}
public String getType(String qName)
{
return getType(getIndex(qName));
}
public String getType(String uri, String localName)
{
return getType(getIndex(uri, localName));
}
public String getURI(int index)
{
if (index < mAttrCount) {
if (index < 0) {
return null;
}
String uri = mAttrCollector.getURI(index);
return (uri == null) ? "" : uri;
}
if ((index - mAttrCount) < mNsCount) {
return XMLConstants.XMLNS_ATTRIBUTE_NS_URI;
}
return null;
}
public String getValue(int index)
{
if (index < mAttrCount) {
return (index < 0) ? null : mAttrCollector.getValue(index);
}
index -= mAttrCount;
if (index < mNsCount) {
/* As discussed in <code>fireStartTag</code>, we must use
* element stack, not attribute collector:
*/
//String uri = mAttrCollector.getNsURI(index);
String uri = mElemStack.getLocalNsURI(index);
return (uri == null) ? "" : uri;
}
return null;
}
public String getValue(String qName)
{
return getValue(getIndex(qName));
}
public String getValue(String uri, String localName)
{
return getValue(getIndex(uri, localName));
}
/*
/////////////////////////////////////////////////////
// Attributes2 (SAX2) implementation
/////////////////////////////////////////////////////
*/
public boolean isDeclared(int index)
{
if (index < mAttrCount) {
if (index >= 0) {
// !!! TODO: implement properly
return true;
}
} else {
index -= mAttrCount;
if (index < mNsCount) {
/* DTD and namespaces don't really play nicely together;
* and in general xmlns: pseudo-attributes are not declared...
* so not quite sure what to return here. For now, let's
* return true, to indicate they ought to be valid
*/
return true;
}
}
throwNoSuchAttribute(index);
return false; // never gets here
}
public boolean isDeclared(String qName)
{
return false;
}
public boolean isDeclared(String uri, String localName)
{
return false;
}
public boolean isSpecified(int index)
{
if (index < mAttrCount) {
if (index >= 0) {
return mAttrCollector.isSpecified(index);
}
} else {
index -= mAttrCount;
if (index < mNsCount) {
/* Determining default-attr - based namespace declarations
* would need new accessors on Woodstox... but they are
* extremely rare, too
*/
return true;
}
}
throwNoSuchAttribute(index);
return false; // never gets here
}
public boolean isSpecified(String qName)
{
int ix = getIndex(qName);
if (ix < 0) {
throw new IllegalArgumentException("No attribute with qName '"+qName+"'");
}
return isSpecified(ix);
}
public boolean isSpecified(String uri, String localName)
{
int ix = getIndex(uri, localName);
if (ix < 0) {
throw new IllegalArgumentException("No attribute with uri "+uri+", local name '"+localName+"'");
}
return isSpecified(ix);
}
/*
/////////////////////////////////////////////////////
// Locator (SAX1) implementation
/////////////////////////////////////////////////////
*/
public int getColumnNumber()
{
if (mScanner != null) {
Location loc = mScanner.getLocation();
return loc.getColumnNumber();
}
return -1;
}
public int getLineNumber()
{
if (mScanner != null) {
Location loc = mScanner.getLocation();
return loc.getLineNumber();
}
return -1;
}
public String getPublicId()
{
if (mScanner != null) {
Location loc = mScanner.getLocation();
return loc.getPublicId();
}
return null;
}
public String getSystemId()
{
if (mScanner != null) {
Location loc = mScanner.getLocation();
return loc.getSystemId();
}
return null;
}
/*
/////////////////////////////////////////////////////
// Locator2 (SAX2) implementation
/////////////////////////////////////////////////////
*/
public String getEncoding()
{
return mEncoding;
}
public String getXMLVersion()
{
return mXmlVersion;
}
/*
/////////////////////////////////////////////////////
// DTDEventListener (woodstox internal API) impl
/////////////////////////////////////////////////////
*/
public boolean dtdReportComments()
{
return (mLexicalHandler != null);
}
public void dtdComment(char[] data, int offset, int len)
{
if (mLexicalHandler != null) {
try {
mLexicalHandler.comment(data, offset, len);
} catch (SAXException sex) {
throw new WrappedSaxException(sex);
}
}
}
public void dtdProcessingInstruction(String target, String data)
{
if (mContentHandler != null) {
try {
mContentHandler.processingInstruction(target, data);
} catch (SAXException sex) {
throw new WrappedSaxException(sex);
}
}
}
public void dtdSkippedEntity(String name)
{
if (mContentHandler != null) {
try {
mContentHandler.skippedEntity(name);
} catch (SAXException sex) {
throw new WrappedSaxException(sex);
}
}
}
// DTD declarations that must be exposed
public void dtdNotationDecl(String name, String publicId, String systemId, URL baseURL)
throws XMLStreamException
{
if (mDTDHandler != null) {
/* 24-Nov-2006, TSa: Note: SAX expects system identifiers to
* be fully resolved when reported...
*/
if (systemId != null && systemId.indexOf(':') < 0) {
try {
systemId = URLUtil.urlFromSystemId(systemId, baseURL).toExternalForm();
} catch (IOException ioe) {
throw new WstxIOException(ioe);
}
}
try {
mDTDHandler.notationDecl(name, publicId, systemId);
} catch (SAXException sex) {
throw new WrappedSaxException(sex);
}
}
}
public void dtdUnparsedEntityDecl(String name, String publicId, String systemId, String notationName, URL baseURL)
throws XMLStreamException
{
if (mDTDHandler != null) {
// SAX expects system id to be fully resolved?
if (systemId.indexOf(':') < 0) { // relative path...
try {
systemId = URLUtil.urlFromSystemId(systemId, baseURL).toExternalForm();
} catch (IOException ioe) {
throw new WstxIOException(ioe);
}
}
try {
mDTDHandler.unparsedEntityDecl(name, publicId, systemId, notationName);
} catch (SAXException sex) {
throw new WrappedSaxException(sex);
}
}
}
// DTD declarations that can be exposed
public void attributeDecl(String eName, String aName, String type, String mode, String value)
{
if (mDeclHandler != null) {
try {
mDeclHandler.attributeDecl(eName, aName, type, mode, value);
} catch (SAXException sex) {
throw new WrappedSaxException(sex);
}
}
}
public void dtdElementDecl(String name, String model)
{
if (mDeclHandler != null) {
try {
mDeclHandler.elementDecl(name, model);
} catch (SAXException sex) {
throw new WrappedSaxException(sex);
}
}
}
public void dtdExternalEntityDecl(String name, String publicId, String systemId)
{
if (mDeclHandler != null) {
try {
mDeclHandler.externalEntityDecl(name, publicId, systemId);
} catch (SAXException sex) {
throw new WrappedSaxException(sex);
}
}
}
public void dtdInternalEntityDecl(String name, String value)
{
if (mDeclHandler != null) {
try {
mDeclHandler.internalEntityDecl(name, value);
} catch (SAXException sex) {
throw new WrappedSaxException(sex);
}
}
}
/*
/////////////////////////////////////////////////
// Internal methods
/////////////////////////////////////////////////
*/
private void throwSaxException(Exception src)
throws SAXException
{
SAXParseException se = new SAXParseException(src.getMessage(), /*(Locator)*/ this, src);
ExceptionUtil.setInitCause(se, src);
if (mErrorHandler != null) {
mErrorHandler.fatalError(se);
}
throw se;
}
private void throwSaxException(String msg)
throws SAXException
{
SAXParseException se = new SAXParseException(msg, /*(Locator)*/ this);
if (mErrorHandler != null) {
mErrorHandler.fatalError(se);
}
throw se;
}
private void throwNoSuchAttribute(int index)
{
throw new IllegalArgumentException("No attribute with index "+index+" (have "+(mAttrCount+mNsCount)+" attributes)");
}
/*
/////////////////////////////////////////////////
// Helper class for dealing with entity resolution
/////////////////////////////////////////////////
*/
/**
* Simple helper class that converts from Stax API into SAX
* EntityResolver call(s)
*/
final class ResolverProxy
implements XMLResolver
{
public ResolverProxy() { }
public Object resolveEntity(String publicID, String systemID, String baseURI, String namespace)
throws XMLStreamException
{
if (mEntityResolver != null) {
try {
/* Hmmh. SAX expects system id to have been mangled prior
* to call... this may work, depending on stax impl:
*/
URL url = new URL(baseURI);
String ref = new URL(url, systemID).toExternalForm();
InputSource isrc = mEntityResolver.resolveEntity(publicID, ref);
if (isrc != null) {
//System.err.println("Debug: succesfully resolved '"+publicID+"', '"+systemID+"'");
InputStream in = isrc.getByteStream();
if (in != null) {
return in;
}
Reader r = isrc.getCharacterStream();
if (r != null) {
return r;
}
}
// Returning null should be fine, actually...
return null;
} catch (IOException ex) {
throw new WstxIOException(ex);
} catch (Exception ex) {
throw new XMLStreamException(ex.getMessage(), ex);
}
}
return null;
}
}
/*
/////////////////////////////////////////////////
// Helper classes for SAX1 support
/////////////////////////////////////////////////
*/
final static class DocHandlerWrapper
implements ContentHandler
{
final DocumentHandler mDocHandler;
final AttributesWrapper mAttrWrapper = new AttributesWrapper();
DocHandlerWrapper(DocumentHandler h)
{
mDocHandler = h;
}
public void characters(char[] ch, int start, int length)
throws SAXException
{
mDocHandler.characters(ch, start, length);
}
public void endDocument() throws SAXException
{
mDocHandler.endDocument();
}
public void endElement(String uri, String localName, String qName)
throws SAXException
{
if (qName == null) {
qName = localName;
}
mDocHandler.endElement(qName);
}
public void endPrefixMapping(String prefix)
{
// no equivalent in SAX1, ignore
}
public void ignorableWhitespace(char[] ch, int start, int length)
throws SAXException
{
mDocHandler.ignorableWhitespace(ch, start, length);
}
public void processingInstruction(String target, String data)
throws SAXException
{
mDocHandler.processingInstruction(target, data);
}
public void setDocumentLocator(Locator locator)
{
mDocHandler.setDocumentLocator(locator);
}
public void skippedEntity(String name)
{
// no equivalent in SAX1, ignore
}
public void startDocument()
throws SAXException
{
mDocHandler.startDocument();
}
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);
}
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;
}
public int getLength()
{
return mAttrs.getLength();
}
public String getName(int i)
{
String n = mAttrs.getQName(i);
return (n == null) ? mAttrs.getLocalName(i) : n;
}
public String getType(int i)
{
return mAttrs.getType(i);
}
public String getType(String name)
{
return mAttrs.getType(name);
}
public String getValue(int i)
{
return mAttrs.getValue(i);
}
public String getValue(String name)
{
return mAttrs.getValue(name);
}
}
}