/* * Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.xml.internal.stream.buffer.sax; import com.sun.xml.internal.stream.buffer.AbstractProcessor; import com.sun.xml.internal.stream.buffer.AttributesHolder; import com.sun.xml.internal.stream.buffer.XMLStreamBuffer; import org.xml.sax.ContentHandler; import org.xml.sax.DTDHandler; import org.xml.sax.EntityResolver; import org.xml.sax.ErrorHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXNotRecognizedException; import org.xml.sax.SAXNotSupportedException; import org.xml.sax.SAXParseException; import org.xml.sax.XMLReader; import org.xml.sax.ext.LexicalHandler; import org.xml.sax.helpers.LocatorImpl; import javax.xml.XMLConstants; import java.io.IOException; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * A processor of a {@link XMLStreamBuffer} that that reads the XML infoset as * {@link XMLReader}. */ public class SAXBufferProcessor extends AbstractProcessor implements XMLReader { /** * Reference to entity resolver. */ protected EntityResolver _entityResolver = DEFAULT_LEXICAL_HANDLER; /** * Reference to dtd handler. */ protected DTDHandler _dtdHandler = DEFAULT_LEXICAL_HANDLER; /** * Reference to content handler. */ protected ContentHandler _contentHandler = DEFAULT_LEXICAL_HANDLER; /** * Reference to error handler. */ protected ErrorHandler _errorHandler = DEFAULT_LEXICAL_HANDLER; /** * Reference to lexical handler. */ protected LexicalHandler _lexicalHandler = DEFAULT_LEXICAL_HANDLER; /** * SAX Namespace attributes features */ protected boolean _namespacePrefixesFeature = false; protected AttributesHolder _attributes = new AttributesHolder(); protected String[] _namespacePrefixes = new String[16]; protected int _namespacePrefixesIndex; protected int[] _namespaceAttributesStartingStack = new int[16]; protected int[] _namespaceAttributesStack = new int[16]; protected int _namespaceAttributesStackIndex; public SAXBufferProcessor() { } /** * @deprecated * Use {@link #SAXBufferProcessor(XMLStreamBuffer, boolean)} */ public SAXBufferProcessor(XMLStreamBuffer buffer) { setXMLStreamBuffer(buffer); } /** * @param produceFragmentEvent * True to generate fragment SAX events without start/endDocument. * False to generate a full document SAX events. */ public SAXBufferProcessor(XMLStreamBuffer buffer, boolean produceFragmentEvent) { setXMLStreamBuffer(buffer,produceFragmentEvent); } public boolean getFeature(String name) throws SAXNotRecognizedException, SAXNotSupportedException { if (name.equals(Features.NAMESPACES_FEATURE)) { return true; } else if (name.equals(Features.NAMESPACE_PREFIXES_FEATURE)) { return _namespacePrefixesFeature; } else if (name.equals(Features.EXTERNAL_GENERAL_ENTITIES)) { return true; } else if (name.equals(Features.EXTERNAL_PARAMETER_ENTITIES)) { return true; } else if (name.equals(Features.STRING_INTERNING_FEATURE)) { return _stringInterningFeature; } else { throw new SAXNotRecognizedException( "Feature not supported: " + name); } } public void setFeature(String name, boolean value) throws SAXNotRecognizedException, SAXNotSupportedException { if (name.equals(Features.NAMESPACES_FEATURE)) { if (!value) { throw new SAXNotSupportedException(name + ":" + value); } } else if (name.equals(Features.NAMESPACE_PREFIXES_FEATURE)) { _namespacePrefixesFeature = value; } else if (name.equals(Features.EXTERNAL_GENERAL_ENTITIES)) { // ignore } else if (name.equals(Features.EXTERNAL_PARAMETER_ENTITIES)) { // ignore } else if (name.equals(Features.STRING_INTERNING_FEATURE)) { if (value != _stringInterningFeature) { throw new SAXNotSupportedException(name + ":" + value); } } else { throw new SAXNotRecognizedException( "Feature not supported: " + name); } } public Object getProperty(String name) throws SAXNotRecognizedException, SAXNotSupportedException { if (name.equals(Properties.LEXICAL_HANDLER_PROPERTY)) { return getLexicalHandler(); } else { throw new SAXNotRecognizedException("Property not recognized: " + name); } } public void setProperty(String name, Object value) throws SAXNotRecognizedException, SAXNotSupportedException { if (name.equals(Properties.LEXICAL_HANDLER_PROPERTY)) { if (value instanceof LexicalHandler) { setLexicalHandler((LexicalHandler)value); } else { throw new SAXNotSupportedException(Properties.LEXICAL_HANDLER_PROPERTY); } } else { throw new SAXNotRecognizedException("Property not recognized: " + name); } } public void setEntityResolver(EntityResolver resolver) { _entityResolver = resolver; } public EntityResolver getEntityResolver() { return _entityResolver; } public void setDTDHandler(DTDHandler handler) { _dtdHandler = handler; } public DTDHandler getDTDHandler() { return _dtdHandler; } public void setContentHandler(ContentHandler handler) { _contentHandler = handler; } public ContentHandler getContentHandler() { return _contentHandler; } public void setErrorHandler(ErrorHandler handler) { _errorHandler = handler; } public ErrorHandler getErrorHandler() { return _errorHandler; } public void setLexicalHandler(LexicalHandler handler) { _lexicalHandler = handler; } public LexicalHandler getLexicalHandler() { return _lexicalHandler; } public void parse(InputSource input) throws IOException, SAXException { // InputSource is ignored process(); } public void parse(String systemId) throws IOException, SAXException { // systemId is ignored process(); } /** * Short-hand for {@link #setXMLStreamBuffer(XMLStreamBuffer)} then {@link #process()}. * * @deprecated * Use {@link #process(XMLStreamBuffer, boolean)} */ public final void process(XMLStreamBuffer buffer) throws SAXException { setXMLStreamBuffer(buffer); process(); } /** * Short-hand for {@link #setXMLStreamBuffer(XMLStreamBuffer,boolean)} then {@link #process()}. * * @param produceFragmentEvent * True to generate fragment SAX events without start/endDocument. * False to generate a full document SAX events. */ public final void process(XMLStreamBuffer buffer, boolean produceFragmentEvent) throws SAXException { setXMLStreamBuffer(buffer); process(); } /** * Resets the parser to read from the beginning of the given {@link XMLStreamBuffer}. * * @deprecated * Use {@link #setXMLStreamBuffer(XMLStreamBuffer, boolean)}. */ public void setXMLStreamBuffer(XMLStreamBuffer buffer) { setBuffer(buffer); } /** * Resets the parser to read from the beginning of the given {@link XMLStreamBuffer}. * * @param produceFragmentEvent * True to generate fragment SAX events without start/endDocument. * False to generate a full document SAX events. */ public void setXMLStreamBuffer(XMLStreamBuffer buffer, boolean produceFragmentEvent) { if(!produceFragmentEvent && _treeCount>1) throw new IllegalStateException("Can't write a forest to a full XML infoset"); setBuffer(buffer,produceFragmentEvent); } /** * Parse the sub-tree (or a whole document) that {@link XMLStreamBuffer} * points to, and sends events to handlers. * *
* TODO:
* We probably need two modes for a sub-tree event generation. One for
* firing a sub-tree as if it's a whole document (in which case start/endDocument
* and appropriate additional namespace bindings are necessary), and the other
* mode for firing a subtree as a subtree, like it does today.
* A stream buffer SAX feature could be used to specify this.
*
* @throws SAXException
* Follow the same semantics as {@link XMLReader#parse(InputSource)}.
*/
public final void process() throws SAXException {
if(!_fragmentMode) {
LocatorImpl nullLocator = new LocatorImpl();
nullLocator.setSystemId(_buffer.getSystemId());
nullLocator.setLineNumber(-1);
nullLocator.setColumnNumber(-1);
_contentHandler.setDocumentLocator(nullLocator);
_contentHandler.startDocument();
// TODO: if we are writing a fragment stream buffer as a full XML document,
// we need to declare in-scope namespaces as if they are on the root element.
}
while (_treeCount>0) {
final int item = readEiiState();
switch(item) {
case STATE_DOCUMENT:
processDocument();
_treeCount--;
break;
case STATE_END:
// Empty buffer
return;
case STATE_ELEMENT_U_LN_QN:
processElement(readStructureString(), readStructureString(), readStructureString(), isInscope());
_treeCount--;
break;
case STATE_ELEMENT_P_U_LN:
{
final String prefix = readStructureString();
final String uri = readStructureString();
final String localName = readStructureString();
processElement(uri, localName, getQName(prefix, localName),isInscope());
_treeCount--;
break;
}
case STATE_ELEMENT_U_LN: {
final String uri = readStructureString();
final String localName = readStructureString();
processElement(uri, localName, localName,isInscope());
_treeCount--;
break;
}
case STATE_ELEMENT_LN:
{
final String localName = readStructureString();
processElement("", localName, localName,isInscope());
_treeCount--;
break;
}
case STATE_COMMENT_AS_CHAR_ARRAY_SMALL:
processCommentAsCharArraySmall();
break;
case STATE_COMMENT_AS_CHAR_ARRAY_MEDIUM:
processCommentAsCharArrayMedium();
break;
case STATE_COMMENT_AS_CHAR_ARRAY_COPY:
processCommentAsCharArrayCopy();
break;
case STATE_COMMENT_AS_STRING:
processComment(readContentString());
break;
case STATE_PROCESSING_INSTRUCTION:
processProcessingInstruction(readStructureString(), readStructureString());
break;
default:
throw reportFatalError("Illegal state for DIIs: "+item);
}
}
if(!_fragmentMode)
_contentHandler.endDocument();
}
private void processCommentAsCharArraySmall() throws SAXException {
final int length = readStructure();
final int start = readContentCharactersBuffer(length);
processComment(_contentCharactersBuffer, start, length);
}
/**
* Report a fatal error and abort.
*
* This is necessary to follow the SAX semantics of error handling.
*/
private SAXParseException reportFatalError(String msg) throws SAXException {
SAXParseException spe = new SAXParseException(msg, null);
if(_errorHandler!=null)
_errorHandler.fatalError(spe);
return spe;
}
private boolean isInscope() {
return _buffer.getInscopeNamespaces().size() > 0;
}
private void processDocument() throws SAXException {
while(true) {
int item = readEiiState();
switch(item) {
case STATE_ELEMENT_U_LN_QN:
processElement(readStructureString(), readStructureString(), readStructureString(),isInscope());
break;
case STATE_ELEMENT_P_U_LN:
{
final String prefix = readStructureString();
final String uri = readStructureString();
final String localName = readStructureString();
processElement(uri, localName, getQName(prefix, localName),isInscope());
break;
}
case STATE_ELEMENT_U_LN: {
final String uri = readStructureString();
final String localName = readStructureString();
processElement(uri, localName, localName,isInscope());
break;
}
case STATE_ELEMENT_LN:
{
final String localName = readStructureString();
processElement("", localName, localName,isInscope());
break;
}
case STATE_COMMENT_AS_CHAR_ARRAY_SMALL:
processCommentAsCharArraySmall();
break;
case STATE_COMMENT_AS_CHAR_ARRAY_MEDIUM:
processCommentAsCharArrayMedium();
break;
case STATE_COMMENT_AS_CHAR_ARRAY_COPY:
processCommentAsCharArrayCopy();
break;
case STATE_COMMENT_AS_STRING:
processComment(readContentString());
break;
case STATE_PROCESSING_INSTRUCTION:
processProcessingInstruction(readStructureString(), readStructureString());
break;
case STATE_END:
return;
default:
throw reportFatalError("Illegal state for child of DII: "+item);
}
}
}
protected void processElement(String uri, String localName, String qName, boolean inscope) throws SAXException {
boolean hasAttributes = false;
boolean hasNamespaceAttributes = false;
int item = peekStructure();
Set