Skip to content

Content of file SAXBufferProcessor.java

/*
 * Copyright (c) 2005, 2021 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Distribution License v. 1.0, which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

package com.sun.xml.stream.buffer.sax;

import com.sun.xml.stream.buffer.AbstractProcessor;
import com.sun.xml.stream.buffer.AttributesHolder;
import com.sun.xml.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() {
        super();
    }

    /**
     * @deprecated
     *      Use {@link #SAXBufferProcessor(XMLStreamBuffer, boolean)}
     */
    @Deprecated
    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);
    }

    @Override
    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);
        }
    }
    
    @Override
    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);
        }        
    }
    
    @Override
    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);
        }
    }
    
    @Override
    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);
        }
    }
    
    @Override
    public void setEntityResolver(EntityResolver resolver) {
        _entityResolver = resolver;
    }
    
    @Override
    public EntityResolver getEntityResolver() {
        return _entityResolver;
    }
    
    @Override
    public void setDTDHandler(DTDHandler handler) {
        _dtdHandler = handler;
    }
    
    @Override
    public DTDHandler getDTDHandler() {
        return _dtdHandler;
    }
    
    @Override
    public void setContentHandler(ContentHandler handler) {
        _contentHandler = handler;
    }
    
    @Override
    public ContentHandler getContentHandler() {
        return _contentHandler;
    }
    
    @Override
    public void setErrorHandler(ErrorHandler handler) {
        _errorHandler = handler;
    }
    
    @Override
    public ErrorHandler getErrorHandler() {
        return _errorHandler;
    }
    
    public void setLexicalHandler(LexicalHandler handler) {
        _lexicalHandler = handler;
    }
    
    public LexicalHandler getLexicalHandler() {
        return _lexicalHandler;
    }
    
    @Override
    public void parse(InputSource input) throws IOException, SAXException {
        // InputSource is ignored
        process();
    }
    
    @Override
    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)}
     */
    @Deprecated
    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 {
no @param for buffer
setXMLStreamBuffer(buffer); process(); } /** * Resets the parser to read from the beginning of the given {@link XMLStreamBuffer}. * * @deprecated * Use {@link #setXMLStreamBuffer(XMLStreamBuffer, boolean)}. */ @Deprecated 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. * * <p> * 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<String> prefixSet = inscope ? new HashSet<>() : Collections.<String>emptySet(); if ((item & TYPE_MASK) == T_NAMESPACE_ATTRIBUTE) { cacheNamespacePrefixStartingIndex(); hasNamespaceAttributes = true; item = processNamespaceAttributes(item, inscope, prefixSet); } if (inscope) { readInscopeNamespaces(prefixSet); } if ((item & TYPE_MASK) == T_ATTRIBUTE) { hasAttributes = true; processAttributes(item); } _contentHandler.startElement(uri, localName, qName, _attributes); if (hasAttributes) { _attributes.clear(); } do { item = readEiiState(); switch(item) { case STATE_ELEMENT_U_LN_QN: processElement(readStructureString(), readStructureString(), readStructureString(), false); break; case STATE_ELEMENT_P_U_LN: { final String p = readStructureString(); final String u = readStructureString(); final String ln = readStructureString(); processElement(u, ln, getQName(p, ln),false); break; } case STATE_ELEMENT_U_LN: { final String u = readStructureString(); final String ln = readStructureString(); processElement(u, ln, ln,false); break; } case STATE_ELEMENT_LN: { final String ln = readStructureString(); processElement("", ln, ln,false); break; } case STATE_TEXT_AS_CHAR_ARRAY_SMALL: { final int length = readStructure(); int start = readContentCharactersBuffer(length); _contentHandler.characters(_contentCharactersBuffer, start, length); break; } case STATE_TEXT_AS_CHAR_ARRAY_MEDIUM: { final int length = readStructure16(); int start = readContentCharactersBuffer(length); _contentHandler.characters(_contentCharactersBuffer, start, length); break; } case STATE_TEXT_AS_CHAR_ARRAY_COPY: { final char[] ch = readContentCharactersCopy(); _contentHandler.characters(ch, 0, ch.length); break; } case STATE_TEXT_AS_STRING: { final String s = readContentString(); _contentHandler.characters(s.toCharArray(), 0, s.length()); break; } case STATE_TEXT_AS_OBJECT: { final CharSequence c = (CharSequence)readContentObject(); final String s = c.toString(); _contentHandler.characters(s.toCharArray(), 0, s.length()); 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 T_COMMENT_AS_STRING: processComment(readContentString()); break; case STATE_PROCESSING_INSTRUCTION: processProcessingInstruction(readStructureString(), readStructureString()); break; case STATE_END: break; default: throw reportFatalError("Illegal state for child of EII: "+item); } } while(item != STATE_END); _contentHandler.endElement(uri, localName, qName); if (hasNamespaceAttributes) { processEndPrefixMapping(); } } private void readInscopeNamespaces(Set<String> prefixSet) throws SAXException { for (Map.Entry<String, String> e : _buffer.getInscopeNamespaces().entrySet()) { String key = fixNull(e.getKey()); // If the prefix is already written, do not write the prefix if (!prefixSet.contains(key)) { processNamespaceAttribute(key,e.getValue()); } } } private static String fixNull(String s) { if (s == null) return ""; else return s; } private void processCommentAsCharArrayCopy() throws SAXException { final char[] ch = readContentCharactersCopy(); processComment(ch, 0, ch.length); } private void processCommentAsCharArrayMedium() throws SAXException { final int length = readStructure16(); final int start = readContentCharactersBuffer(length); processComment(_contentCharactersBuffer, start, length); } private void processEndPrefixMapping() throws SAXException { final int end = _namespaceAttributesStack[--_namespaceAttributesStackIndex]; // final int start = (_namespaceAttributesStackIndex > 0) ? _namespaceAttributesStack[_namespaceAttributesStackIndex] : 0; final int start = (_namespaceAttributesStackIndex >= 0) ? _namespaceAttributesStartingStack[_namespaceAttributesStackIndex] : 0; for (int i = end - 1; i >= start; i--) { _contentHandler.endPrefixMapping(_namespacePrefixes[i]); } _namespacePrefixesIndex = start; } private int processNamespaceAttributes(int item,boolean collectPrefixes, Set<String> prefixSet) throws SAXException { do { String prefix; switch(getNIIState(item)) { case STATE_NAMESPACE_ATTRIBUTE: // Undeclaration of default namespace processNamespaceAttribute("", ""); if(collectPrefixes) { prefixSet.add(""); } break; case STATE_NAMESPACE_ATTRIBUTE_P: // Undeclaration of namespace prefix = readStructureString(); processNamespaceAttribute(prefix, ""); if(collectPrefixes) { prefixSet.add(prefix); } break; case STATE_NAMESPACE_ATTRIBUTE_P_U: // Declaration with prefix prefix = readStructureString(); processNamespaceAttribute(prefix, readStructureString()); if(collectPrefixes) { prefixSet.add(prefix); } break; case STATE_NAMESPACE_ATTRIBUTE_U: // Default declaration processNamespaceAttribute("", readStructureString()); if(collectPrefixes) { prefixSet.add(""); } break; default: throw reportFatalError("Illegal state: "+item); } readStructure(); item = peekStructure(); } while((item & TYPE_MASK) == T_NAMESPACE_ATTRIBUTE); cacheNamespacePrefixIndex(); return item; } private void processAttributes(int item) throws SAXException { do { switch(getAIIState(item)) { case STATE_ATTRIBUTE_U_LN_QN: _attributes.addAttributeWithQName(readStructureString(), readStructureString(), readStructureString(), readStructureString(), readContentString()); break; case STATE_ATTRIBUTE_P_U_LN: { final String p = readStructureString(); final String u = readStructureString(); final String ln = readStructureString(); _attributes.addAttributeWithQName(u, ln, getQName(p, ln), readStructureString(), readContentString()); break; } case STATE_ATTRIBUTE_U_LN: { final String u = readStructureString(); final String ln = readStructureString(); _attributes.addAttributeWithQName(u, ln, ln, readStructureString(), readContentString()); break; } case STATE_ATTRIBUTE_LN: { final String ln = readStructureString(); _attributes.addAttributeWithQName("", ln, ln, readStructureString(), readContentString()); break; } default: throw reportFatalError("Illegal state: "+item); } readStructure(); item = peekStructure(); } while((item & TYPE_MASK) == T_ATTRIBUTE); } private void processNamespaceAttribute(String prefix, String uri) throws SAXException { _contentHandler.startPrefixMapping(prefix, uri); if (_namespacePrefixesFeature) { // Add the namespace delcaration as an attribute if (prefix != "") { _attributes.addAttributeWithQName(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, prefix, getQName(XMLConstants.XMLNS_ATTRIBUTE, prefix), "CDATA", uri); } else { _attributes.addAttributeWithQName(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, XMLConstants.XMLNS_ATTRIBUTE, XMLConstants.XMLNS_ATTRIBUTE, "CDATA", uri); } } cacheNamespacePrefix(prefix); } private void cacheNamespacePrefix(String prefix) { if (_namespacePrefixesIndex == _namespacePrefixes.length) { final String[] namespaceAttributes = new String[_namespacePrefixesIndex * 3 / 2 + 1]; System.arraycopy(_namespacePrefixes, 0, namespaceAttributes, 0, _namespacePrefixesIndex); _namespacePrefixes = namespaceAttributes; } _namespacePrefixes[_namespacePrefixesIndex++] = prefix; } private void cacheNamespacePrefixIndex() { if (_namespaceAttributesStackIndex == _namespaceAttributesStack.length) { final int[] namespaceAttributesStack = new int[_namespaceAttributesStackIndex * 3 /2 + 1]; System.arraycopy(_namespaceAttributesStack, 0, namespaceAttributesStack, 0, _namespaceAttributesStackIndex); _namespaceAttributesStack = namespaceAttributesStack; } _namespaceAttributesStack[_namespaceAttributesStackIndex++] = _namespacePrefixesIndex; } private void cacheNamespacePrefixStartingIndex() { if (_namespaceAttributesStackIndex == _namespaceAttributesStartingStack.length) { final int[] namespaceAttributesStart = new int[_namespaceAttributesStackIndex * 3 /2 + 1]; System.arraycopy(_namespaceAttributesStartingStack, 0, namespaceAttributesStart, 0, _namespaceAttributesStackIndex); _namespaceAttributesStartingStack = namespaceAttributesStart; } _namespaceAttributesStartingStack[_namespaceAttributesStackIndex] = _namespacePrefixesIndex; } private void processComment(String s) throws SAXException { processComment(s.toCharArray(), 0, s.length()); } private void processComment(char[] ch, int start, int length) throws SAXException { _lexicalHandler.comment(ch, start, length); } private void processProcessingInstruction(String target, String data) throws SAXException { _contentHandler.processingInstruction(target, data); } private static final DefaultWithLexicalHandler DEFAULT_LEXICAL_HANDLER = new DefaultWithLexicalHandler(); }