| /* | ||
| * Copyright 2001-2004 (C) MetaStuff, Ltd. All Rights Reserved. | ||
| * | ||
| * This software is open source. | ||
| * See the bottom of this file for the licence. | ||
| * | ||
| * $Id: SAXContentHandler.java,v 1.59 2004/08/06 09:51:50 maartenc Exp $ | ||
| */ | ||
| package org.dom4j.io; | ||
| import java.lang.reflect.Method; | ||
| import java.util.ArrayList; | ||
| import java.util.HashMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import org.dom4j.Branch; | ||
| import org.dom4j.Document; | ||
| import org.dom4j.DocumentFactory; | ||
| import org.dom4j.DocumentType; | ||
| import org.dom4j.Element; | ||
| import org.dom4j.ElementHandler; | ||
| import org.dom4j.Namespace; | ||
| import org.dom4j.QName; | ||
| import org.dom4j.dtd.AttributeDecl; | ||
| import org.dom4j.dtd.ElementDecl; | ||
| import org.dom4j.dtd.ExternalEntityDecl; | ||
| import org.dom4j.dtd.InternalEntityDecl; | ||
| import org.dom4j.tree.AbstractElement; | ||
| import org.dom4j.tree.NamespaceStack; | ||
| import org.xml.sax.Attributes; | ||
| import org.xml.sax.DTDHandler; | ||
| import org.xml.sax.EntityResolver; | ||
| import org.xml.sax.InputSource; | ||
| import org.xml.sax.Locator; | ||
| import org.xml.sax.SAXException; | ||
| import org.xml.sax.SAXParseException; | ||
| import org.xml.sax.ext.DeclHandler; | ||
| import org.xml.sax.ext.LexicalHandler; | ||
| import org.xml.sax.helpers.DefaultHandler; | ||
| /** <p><code>SAXContentHandler</code> builds a dom4j tree via SAX events.</p> | ||
| * | ||
| * @author <a href="mailto:jstrachan@apache.org">James Strachan</a> | ||
| * @version $Revision: 1.59 $ | ||
| */ | ||
| public class SAXContentHandler extends DefaultHandler implements LexicalHandler, DeclHandler, DTDHandler { | ||
| /** The factory used to create new <code>Document</code> instances */ | ||
| private DocumentFactory documentFactory; | ||
| /** The document that is being built */ | ||
| private Document document; | ||
| /** stack of <code>Element</code> objects */ | ||
| private ElementStack elementStack; | ||
| /** stack of <code>Namespace</code> and <code>QName</code> objects */ | ||
| private NamespaceStack namespaceStack; | ||
| /** the <code>ElementHandler</code> called as the elements are complete */ | ||
| private ElementHandler elementHandler; | ||
| /** the Locator */ | ||
| private Locator locator; | ||
| /** The name of the current entity */ | ||
| private String entity; | ||
| /** Flag used to indicate that we are inside a DTD section */ | ||
| private boolean insideDTDSection; | ||
| /** Flag used to indicate that we are inside a CDATA section */ | ||
| private boolean insideCDATASection; | ||
| /** buffer to hold contents of cdata section across multiple characters events */ | ||
| private StringBuffer cdataText; | ||
| /** namespaces that are available for use */ | ||
| 11186x | private Map availableNamespaceMap = new HashMap(); | |
| /** declared namespaces that are not yet available for use */ | ||
| 11186x | private List declaredNamespaceList = new ArrayList(); | |
| /** internal DTD declarations */ | ||
| private List internalDTDDeclarations; | ||
| /** external DTD declarations */ | ||
| private List externalDTDDeclarations; | ||
| /** The number of namespaces that are declared in the current scope */ | ||
| private int declaredNamespaceIndex; | ||
| /** The entity resolver */ | ||
| private EntityResolver entityResolver; | ||
| private InputSource inputSource; | ||
| /** The current element we are on */ | ||
| private Element currentElement; | ||
| /** Should internal DTD declarations be expanded into a List in the DTD */ | ||
| 11186x | private boolean includeInternalDTDDeclarations = false; | |
| /** Should external DTD declarations be expanded into a List in the DTD */ | ||
| 11186x | private boolean includeExternalDTDDeclarations = false; | |
| /** The number of levels deep we are inside a startEntity / endEntity call */ | ||
| private int entityLevel; | ||
| /** Are we in an internal DTD subset? */ | ||
| 11186x | private boolean internalDTDsubset = false; | |
| /** Whether adjacent text nodes should be merged */ | ||
| 11186x | private boolean mergeAdjacentText = false; | |
| /** Have we added text to the buffer */ | ||
| 11186x | private boolean textInTextBuffer = false; | |
| /** Should we ignore comments */ | ||
| 11186x | private boolean ignoreComments = false; | |
| /** Buffer used to concatenate text together */ | ||
| private StringBuffer textBuffer; | ||
| /** Holds value of property stripWhitespaceText. */ | ||
| 11186x | private boolean stripWhitespaceText = false; | |
| public SAXContentHandler() { | ||
| 2x | this(DocumentFactory.getInstance()); | |
| 2x | } | |
| public SAXContentHandler(DocumentFactory documentFactory) { | ||
| 2x | this(documentFactory, null); | |
| 2x | } | |
| public SAXContentHandler(DocumentFactory documentFactory, ElementHandler elementHandler) { | ||
| 11186x | this(documentFactory, elementHandler, null); | |
| 11186x | this.elementStack = createElementStack(); | |
| 11186x | } | |
| 11186x | public SAXContentHandler(DocumentFactory documentFactory, ElementHandler elementHandler, ElementStack elementStack) { | |
| 11186x | this.documentFactory = documentFactory; | |
| 11186x | this.elementHandler = elementHandler; | |
| 11186x | this.elementStack = elementStack; | |
| 11186x | this.namespaceStack = new NamespaceStack(documentFactory); | |
| 11186x | } | |
| /** @return the document that has been or is being built | ||
| */ | ||
| public Document getDocument() { | ||
| 2/2 22406x | if ( document == null ) { | |
| 11182x | document = createDocument(); | |
| } | ||
| 22406x | return document; | |
| } | ||
| // ContentHandler interface | ||
| //------------------------------------------------------------------------- | ||
| public void setDocumentLocator(Locator locator) { | ||
| 11182x | this.locator = locator; | |
| 11182x | } | |
| public void processingInstruction(String target, String data) throws SAXException { | ||
| 1/4 26x | if ( mergeAdjacentText && textInTextBuffer ) { | |
| 0x | completeCurrentTextNode(); | |
| } | ||
| 1/2 26x | if ( currentElement != null ) { | |
| 0x | currentElement.addProcessingInstruction(target, data); | |
| } | ||
| else { | ||
| 26x | getDocument().addProcessingInstruction(target, data); | |
| } | ||
| 26x | } | |
| public void startPrefixMapping(String prefix, String uri) throws SAXException { | ||
| 11090x | namespaceStack.push( prefix, uri ); | |
| 11090x | } | |
| public void endPrefixMapping(String prefix) throws SAXException { | ||
| 11090x | namespaceStack.pop( prefix ); | |
| 11090x | declaredNamespaceIndex = namespaceStack.size(); | |
| 11090x | } | |
| public void startDocument() throws SAXException { | ||
| //document = createDocument(); | ||
| 11182x | document = null; | |
| 11182x | currentElement = null; | |
| 11182x | elementStack.clear(); | |
| 3/4 11182x | if ( (elementHandler != null) && | |
| (elementHandler instanceof DispatchHandler) ) { | ||
| 32x | elementStack.setDispatchHandler((DispatchHandler)elementHandler); | |
| } | ||
| 11182x | namespaceStack.clear(); | |
| 11182x | declaredNamespaceIndex = 0; | |
| 3/4 11182x | if ( mergeAdjacentText && textBuffer == null ) { | |
| 8x | textBuffer = new StringBuffer(); | |
| } | ||
| 11182x | textInTextBuffer = false; | |
| 11182x | } | |
| public void endDocument() throws SAXException { | ||
| 11182x | namespaceStack.clear(); | |
| 11182x | elementStack.clear(); | |
| 11182x | currentElement = null; | |
| 11182x | textBuffer = null; | |
| 11182x | } | |
| public void startElement(String namespaceURI, String localName, String qualifiedName, Attributes attributes) throws SAXException { | ||
| 4/4 120500x | if ( mergeAdjacentText && textInTextBuffer ) { | |
| 2x | completeCurrentTextNode(); | |
| } | ||
| 120500x | QName qName = namespaceStack.getQName( | |
| namespaceURI, localName, qualifiedName | ||
| ); | ||
| 120500x | Branch branch = currentElement; | |
| 2/2 120500x | if ( branch == null ) { | |
| 11182x | branch = getDocument(); | |
| } | ||
| 120500x | Element element = branch.addElement(qName); | |
| // add all declared namespaces | ||
| 120500x | addDeclaredNamespaces(element); | |
| // now lets add all attribute values | ||
| 120500x | addAttributes( element, attributes ); | |
| 120500x | elementStack.pushElement(element); | |
| 120500x | currentElement = element; | |
| 120500x | entity = null; // fixes bug527062 | |
| 2/2 120500x | if ( elementHandler != null ) { | |
| 128x | elementHandler.onStart(elementStack); | |
| } | ||
| 120500x | } | |
| public void endElement(String namespaceURI, String localName, String qName) throws SAXException { | ||
| 4/4 120500x | if ( mergeAdjacentText && textInTextBuffer ) { | |
| 10x | completeCurrentTextNode(); | |
| } | ||
| 3/4 120500x | if ( elementHandler != null && currentElement != null ) { | |
| 128x | elementHandler.onEnd(elementStack); | |
| } | ||
| 120500x | elementStack.popElement(); | |
| 120500x | currentElement = elementStack.peekElement(); | |
| 120500x | } | |
| public void characters(char[] ch, int start, int end) throws SAXException { | ||
| 1/2 97970x | if ( end == 0 ) { | |
| 0x | return; | |
| } | ||
| 1/2 97970x | if ( currentElement != null ) { | |
| 1/2 97970x | if (entity != null) { | |
| 0/4 0x | if ( mergeAdjacentText && textInTextBuffer ) { | |
| 0x | completeCurrentTextNode(); | |
| } | ||
| 0x | currentElement.addEntity(entity, new String(ch, start, end)); | |
| 0x | entity = null; | |
| } | ||
| 2/2 97970x | else if (insideCDATASection) { | |
| 3/4 18x | if ( mergeAdjacentText && textInTextBuffer ) { | |
| 2x | completeCurrentTextNode(); | |
| } | ||
| 18x | cdataText.append(new String(ch, start, end)); | |
| } | ||
| else { | ||
| 2/2 97952x | if ( mergeAdjacentText ) { | |
| 64x | textBuffer.append(ch, start, end); | |
| 64x | textInTextBuffer = true; | |
| } | ||
| else { | ||
| 97888x | currentElement.addText(new String(ch, start, end)); | |
| } | ||
| } | ||
| } | ||
| 97970x | } | |
| // ErrorHandler interface | ||
| //------------------------------------------------------------------------- | ||
| /** This method is called when a warning occurs during the parsing | ||
| * of the document. | ||
| * This method does nothing. | ||
| */ | ||
| public void warning(SAXParseException exception) throws SAXException { | ||
| // ignore warnings by default | ||
| 0x | } | |
| /** This method is called when an error is detected during parsing | ||
| * such as a validation error. | ||
| * This method rethrows the exception | ||
| */ | ||
| public void error(SAXParseException exception) throws SAXException { | ||
| 0x | throw exception; | |
| } | ||
| /** This method is called when a fatal error occurs during parsing. | ||
| * This method rethrows the exception | ||
| */ | ||
| public void fatalError(SAXParseException exception) throws SAXException { | ||
| 0x | throw exception; | |
| } | ||
| // LexicalHandler interface | ||
| //------------------------------------------------------------------------- | ||
| public void startDTD(String name, String publicId, String systemId) throws SAXException { | ||
| 4x | getDocument().addDocType(name, publicId, systemId); | |
| 4x | insideDTDSection = true; | |
| 4x | internalDTDsubset = true; | |
| 4x | } | |
| public void endDTD() throws SAXException { | ||
| 4x | insideDTDSection = false; | |
| 4x | DocumentType docType = getDocument().getDocType(); | |
| 1/2 4x | if ( docType != null ) { | |
| 2/2 4x | if ( internalDTDDeclarations != null ) { | |
| 2x | docType.setInternalDeclarations( internalDTDDeclarations ); | |
| } | ||
| 1/2 4x | if ( externalDTDDeclarations != null ) { | |
| 0x | docType.setExternalDeclarations( externalDTDDeclarations ); | |
| } | ||
| } | ||
| 4x | internalDTDDeclarations = null; | |
| 4x | externalDTDDeclarations = null; | |
| 4x | } | |
| public void startEntity(String name) throws SAXException { | ||
| 70x | ++entityLevel; | |
| // Ignore DTD references | ||
| 70x | entity = null; | |
| 1/2 70x | if (! insideDTDSection ) { | |
| 1/2 70x | if ( ! isIgnorableEntity(name) ) { | |
| 0x | entity = name; | |
| } | ||
| } | ||
| // internal DTD subsets can only appear outside of a | ||
| // startEntity/endEntity block | ||
| // see the startDTD method in | ||
| // http://dom4j.org/javadoc/org/xml/sax/ext/LexicalHandler.html | ||
| // or here:- | ||
| // http://dom4j.org/javadoc/org/xml/sax/ext/LexicalHandler.html#startDTD(java.lang.String, java.lang.String, java.lang.String) | ||
| 70x | internalDTDsubset = false; | |
| 70x | } | |
| public void endEntity(String name) throws SAXException { | ||
| 70x | --entityLevel; | |
| 70x | entity = null; | |
| 1/2 70x | if ( entityLevel == 0 ) { | |
| 70x | internalDTDsubset = true; | |
| } | ||
| 70x | } | |
| public void startCDATA() throws SAXException { | ||
| 8x | insideCDATASection = true; | |
| 8x | cdataText = new StringBuffer(); | |
| 8x | } | |
| public void endCDATA() throws SAXException { | ||
| 8x | insideCDATASection = false; | |
| 8x | currentElement.addCDATA(cdataText.toString()); | |
| 8x | } | |
| public void comment(char[] ch, int start, int end) throws SAXException { | ||
| 1/2 34x | if (!ignoreComments) { | |
| 3/4 34x | if ( mergeAdjacentText && textInTextBuffer ) { | |
| 2x | completeCurrentTextNode(); | |
| } | ||
| 34x | String text = new String(ch, start, end); | |
| 2/4 34x | if (!insideDTDSection && text.length() > 0) { | |
| 2/2 34x | if ( currentElement != null ) { | |
| 26x | currentElement.addComment(text); | |
| } | ||
| else { | ||
| 8x | getDocument().addComment(text); | |
| } | ||
| } | ||
| } | ||
| 34x | } | |
| // DeclHandler interface | ||
| //------------------------------------------------------------------------- | ||
| /** | ||
| * Report an element type declaration. | ||
| * | ||
| * <p>The content model will consist of the string "EMPTY", the | ||
| * string "ANY", or a parenthesised group, optionally followed | ||
| * by an occurrence indicator. The model will be normalized so | ||
| * that all parameter entities are fully resolved and all whitespace | ||
| * is removed,and will include the enclosing parentheses. Other | ||
| * normalization (such as removing redundant parentheses or | ||
| * simplifying occurrence indicators) is at the discretion of the | ||
| * parser.</p> | ||
| * | ||
| * @param name The element type name. | ||
| * @param model The content model as a normalized string. | ||
| * @exception SAXException The application may raise an exception. | ||
| */ | ||
| public void elementDecl(String name, String model) throws SAXException { | ||
| 0/2 0x | if ( internalDTDsubset ) { | |
| 0/2 0x | if ( includeInternalDTDDeclarations ) { | |
| 0x | addDTDDeclaration( new ElementDecl( name, model ) ); | |
| } | ||
| } | ||
| else { | ||
| 0/2 0x | if ( includeExternalDTDDeclarations ) { | |
| 0x | addExternalDTDDeclaration( new ElementDecl( name, model ) ); | |
| } | ||
| } | ||
| 0x | } | |
| /** | ||
| * Report an attribute type declaration. | ||
| * | ||
| * <p>Only the effective (first) declaration for an attribute will | ||
| * be reported. The type will be one of the strings "CDATA", | ||
| * "ID", "IDREF", "IDREFS", "NMTOKEN", "NMTOKENS", "ENTITY", | ||
| * "ENTITIES", a parenthesized token group with | ||
| * the separator "|" and all whitespace removed, or the word | ||
| * "NOTATION" followed by a space followed by a parenthesized | ||
| * token group with all whitespace removed.</p> | ||
| * | ||
| * <p>Any parameter entities in the attribute value will be | ||
| * expanded, but general entities will not.</p> | ||
| * | ||
| * @param eName The name of the associated element. | ||
| * @param aName The name of the attribute. | ||
| * @param type A string representing the attribute type. | ||
| * @param valueDefault A string representing the attribute default | ||
| * ("#IMPLIED", "#REQUIRED", or "#FIXED") or null if | ||
| * none of these applies. | ||
| * @param value A string representing the attribute's default value, | ||
| * or null if there is none. | ||
| * @exception SAXException The application may raise an exception. | ||
| */ | ||
| public void attributeDecl(String eName,String aName,String type,String valueDefault,String value) throws SAXException { | ||
| 0/2 0x | if ( internalDTDsubset ) { | |
| 0/2 0x | if ( includeInternalDTDDeclarations ) { | |
| 0x | addDTDDeclaration( new AttributeDecl( eName, aName, type, valueDefault, value) ); | |
| } | ||
| } | ||
| else { | ||
| 0/2 0x | if ( includeExternalDTDDeclarations ) { | |
| 0x | addExternalDTDDeclaration( new AttributeDecl( eName, aName, type, valueDefault, value) ); | |
| } | ||
| } | ||
| 0x | } | |
| /** | ||
| * Report an internal entity declaration. | ||
| * | ||
| * <p>Only the effective (first) declaration for each entity | ||
| * will be reported. All parameter entities in the value | ||
| * will be expanded, but general entities will not.</p> | ||
| * | ||
| * @param name The name of the entity. If it is a parameter | ||
| * entity, the name will begin with '%'. | ||
| * @param value The replacement text of the entity. | ||
| * @exception SAXException The application may raise an exception. | ||
| * @see #externalEntityDecl | ||
| * @see org.xml.sax.DTDHandler#unparsedEntityDecl | ||
| */ | ||