| /* | ||
| * Copyright 2001-2004 (C) MetaStuff, Ltd. All Rights Reserved. | ||
| * | ||
| * This software is open source. | ||
| * See the bottom of this file for the licence. | ||
| * | ||
| * $Id: SAXWriter.java,v 1.22 2004/06/25 12:34:47 maartenc Exp $ | ||
| */ | ||
| package org.dom4j.io; | ||
| import java.io.IOException; | ||
| import java.util.HashMap; | ||
| import java.util.Iterator; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import org.dom4j.Attribute; | ||
| import org.dom4j.Branch; | ||
| import org.dom4j.CDATA; | ||
| import org.dom4j.CharacterData; | ||
| import org.dom4j.Comment; | ||
| import org.dom4j.Document; | ||
| import org.dom4j.DocumentType; | ||
| import org.dom4j.Element; | ||
| import org.dom4j.Entity; | ||
| import org.dom4j.Namespace; | ||
| import org.dom4j.Node; | ||
| import org.dom4j.ProcessingInstruction; | ||
| import org.dom4j.Text; | ||
| import org.dom4j.tree.NamespaceStack; | ||
| import org.xml.sax.Attributes; | ||
| 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.XMLReader; | ||
| import org.xml.sax.ext.LexicalHandler; | ||
| import org.xml.sax.helpers.AttributesImpl; | ||
| import org.xml.sax.helpers.LocatorImpl; | ||
| /** <p><code>SAXWriter</code> writes a DOM4J tree to a SAX ContentHandler.</p> | ||
| * | ||
| * @author <a href="mailto:james.strachan@metastuff.com">James Strachan</a> | ||
| * @version $Revision: 1.22 $ | ||
| */ | ||
| public class SAXWriter implements XMLReader { | ||
| protected static final String[] LEXICAL_HANDLER_NAMES = { | ||
| "http://xml.org/sax/properties/lexical-handler", | ||
| "http://xml.org/sax/handlers/LexicalHandler" | ||
| }; | ||
| protected static String FEATURE_NAMESPACE_PREFIXES = "http://xml.org/sax/features/namespace-prefixes"; | ||
| protected static String FEATURE_NAMESPACES = "http://xml.org/sax/features/namespaces"; | ||
| /** <code>ContentHandler</code> to which SAX events are raised */ | ||
| private ContentHandler contentHandler; | ||
| /** code>DTDHandler</code> fired when a document has a DTD */ | ||
| private DTDHandler dtdHandler; | ||
| /** code>EntityResolver</code> fired when a document has a DTD */ | ||
| private EntityResolver entityResolver; | ||
| private ErrorHandler errorHandler; | ||
| /** code>LexicalHandler</code> fired on Entity and CDATA sections */ | ||
| private LexicalHandler lexicalHandler; | ||
| /** code>AttributesImpl</code> used when generating the Attributes */ | ||
| 4x | private AttributesImpl attributes = new AttributesImpl(); | |
| /** Stores the features */ | ||
| 4x | private Map features = new HashMap(); | |
| /** Stores the properties */ | ||
| 4x | private Map properties = new HashMap(); | |
| /** Whether namespace declarations are exported as attributes or not */ | ||
| private boolean declareNamespaceAttributes; | ||
| 4x | public SAXWriter() { | |
| 4x | properties.put( FEATURE_NAMESPACE_PREFIXES, Boolean.FALSE ); | |
| 4x | properties.put( FEATURE_NAMESPACE_PREFIXES, Boolean.TRUE ); | |
| 4x | } | |
| public SAXWriter(ContentHandler contentHandler) { | ||
| 0x | this(); | |
| 0x | this.contentHandler = contentHandler; | |
| 0x | } | |
| public SAXWriter( | ||
| ContentHandler contentHandler, | ||
| LexicalHandler lexicalHandler | ||
| ) { | ||
| 0x | this(); | |
| 0x | this.contentHandler = contentHandler; | |
| 0x | this.lexicalHandler = lexicalHandler; | |
| 0x | } | |
| public SAXWriter( | ||
| ContentHandler contentHandler, | ||
| LexicalHandler lexicalHandler, | ||
| EntityResolver entityResolver | ||
| ) { | ||
| 0x | this(); | |
| 0x | this.contentHandler = contentHandler; | |
| 0x | this.lexicalHandler = lexicalHandler; | |
| 0x | this.entityResolver = entityResolver; | |
| 0x | } | |
| /** | ||
| * A polymorphic method to write any Node to this SAX stream | ||
| */ | ||
| public void write(Node node) throws SAXException { | ||
| 0x | int nodeType = node.getNodeType(); | |
| 0x | switch (nodeType) { | |
| case Node.ELEMENT_NODE: | ||
| 0x | write((Element) node); | |
| 0x | break; | |
| case Node.ATTRIBUTE_NODE: | ||
| 0x | write((Attribute) node); | |
| 0x | break; | |
| case Node.TEXT_NODE: | ||
| 0x | write(node.getText()); | |
| 0x | break; | |
| case Node.CDATA_SECTION_NODE: | ||
| 0x | write((CDATA) node); | |
| 0x | break; | |
| case Node.ENTITY_REFERENCE_NODE: | ||
| 0x | write((Entity) node); | |
| 0x | break; | |
| case Node.PROCESSING_INSTRUCTION_NODE: | ||
| 0x | write((ProcessingInstruction) node); | |
| 0x | break; | |
| case Node.COMMENT_NODE: | ||
| 0x | write((Comment) node); | |
| 0x | break; | |
| case Node.DOCUMENT_NODE: | ||
| 0x | write((Document) node); | |
| 0x | break; | |
| case Node.DOCUMENT_TYPE_NODE: | ||
| 0x | write((DocumentType) node); | |
| 0x | break; | |
| case Node.NAMESPACE_NODE: | ||
| // Will be output with attributes | ||
| //write((Namespace) node); | ||
| 0x | break; | |
| default: | ||
| 0x | throw new SAXException( "Invalid node type: " + node ); | |
| } | ||
| 0x | } | |
| /** Generates SAX events for the given Document and all its content | ||
| * | ||
| * @param document is the Document to parse | ||
| * @throws SAXException if there is a SAX error processing the events | ||
| */ | ||
| public void write(Document document) throws SAXException { | ||
| 1/2 4x | if (document != null) { | |
| 4x | checkForNullHandlers(); | |
| 4x | documentLocator(document); | |
| 4x | startDocument(); | |
| 4x | entityResolver(document); | |
| 4x | dtdHandler(document); | |
| 4x | writeContent( document, new NamespaceStack() ); | |
| 4x | endDocument(); | |
| } | ||
| 4x | } | |
| /** Generates SAX events for the given Element and all its content | ||
| * | ||
| * @param element is the Element to parse | ||
| * @throws SAXException if there is a SAX error processing the events | ||
| */ | ||
| public void write( Element element ) throws SAXException { | ||
| 0x | write( element, new NamespaceStack() ); | |
| 0x | } | |
| /** <p>Writes the opening tag of an {@link Element}, | ||
| * including its {@link Attribute}s | ||
| * but without its content.</p> | ||
| * | ||
| * @param element <code>Element</code> to output. | ||
| */ | ||
| public void writeOpen(Element element) throws SAXException { | ||
| 0x | startElement(element, null); | |
| 0x | } | |
| /** <p>Writes the closing tag of an {@link Element}</p> | ||
| * | ||
| * @param element <code>Element</code> to output. | ||
| */ | ||
| public void writeClose(Element element) throws SAXException { | ||
| 0x | endElement(element); | |
| 0x | } | |
| /** Generates SAX events for the given text | ||
| * | ||
| * @param text is the text to send to the SAX ContentHandler | ||
| * @throws SAXException if there is a SAX error processing the events | ||
| */ | ||
| public void write( String text ) throws SAXException { | ||
| 1/2 12x | if ( text != null ) { | |
| 12x | char[] chars = text.toCharArray(); | |
| 12x | contentHandler.characters( chars, 0, chars.length ); | |
| } | ||
| 12x | } | |
| /** Generates SAX events for the given CDATA | ||
| * | ||
| * @param cdata is the CDATA to parse | ||
| * @throws SAXException if there is a SAX error processing the events | ||
| */ | ||
| public void write( CDATA cdata ) throws SAXException { | ||
| 0x | String text = cdata.getText(); | |
| 0/2 0x | if ( lexicalHandler != null ) { | |
| 0x | lexicalHandler.startCDATA(); | |
| 0x | write( text ); | |
| 0x | lexicalHandler.endCDATA(); | |
| } | ||
| else { | ||
| 0x | write( text ); | |
| } | ||
| 0x | } | |
| /** Generates SAX events for the given Comment | ||
| * | ||
| * @param comment is the Comment to parse | ||
| * @throws SAXException if there is a SAX error processing the events | ||
| */ | ||
| public void write( Comment comment ) throws SAXException { | ||
| 0/2 0x | if ( lexicalHandler != null ) { | |
| 0x | String text = comment.getText(); | |
| 0x | char[] chars = text.toCharArray(); | |
| 0x | lexicalHandler.comment( chars, 0, chars.length ); | |
| } | ||
| 0x | } | |
| /** Generates SAX events for the given Entity | ||
| * | ||
| * @param entity is the Entity to parse | ||
| * @throws SAXException if there is a SAX error processing the events | ||
| */ | ||
| public void write( Entity entity ) throws SAXException { | ||
| 0x | String text = entity.getText(); | |
| 0/2 0x | if ( lexicalHandler != null ) { | |
| 0x | String name = entity.getName(); | |
| 0x | lexicalHandler.startEntity(name); | |
| 0x | write( text ); | |
| 0x | lexicalHandler.endEntity(name); | |
| } | ||
| else { | ||
| 0x | write( text ); | |
| } | ||
| 0x | } | |
| /** Generates SAX events for the given ProcessingInstruction | ||
| * | ||
| * @param pi is the ProcessingInstruction to parse | ||
| * @throws SAXException if there is a SAX error processing the events | ||
| */ | ||
| public void write( ProcessingInstruction pi ) throws SAXException { | ||
| 0x | String target = pi.getTarget(); | |
| 0x | String text = pi.getText(); | |
| 0x | contentHandler.processingInstruction(target, text); | |
| 0x | } | |
| /** Should namespace declarations be converted to "xmlns" attributes. This property | ||
| * defaults to <code>false</code> as per the SAX specification. | ||
| * This property is set via the SAX feature "http://xml.org/sax/features/namespace-prefixes" | ||
| */ | ||
| public boolean isDeclareNamespaceAttributes() { | ||
| 0x | return declareNamespaceAttributes; | |
| } | ||
| /** Sets whether namespace declarations should be exported as "xmlns" attributes or not. | ||
| * This property is set from the SAX feature "http://xml.org/sax/features/namespace-prefixes" | ||
| */ | ||
| public void setDeclareNamespaceAttributes(boolean declareNamespaceAttributes) { | ||
| 4x | this.declareNamespaceAttributes = declareNamespaceAttributes; | |
| 4x | } | |
| // XMLReader methods | ||
| //------------------------------------------------------------------------- | ||
| /** @return the <code>ContentHandler</code> called when SAX events | ||
| * are raised | ||
| */ | ||
| public ContentHandler getContentHandler() { | ||
| 0x | return contentHandler; | |
| } | ||
| /** Sets the <code>ContentHandler</code> called when SAX events | ||
| * are raised | ||
| * | ||
| * @param contentHandler is the <code>ContentHandler</code> called when SAX events | ||
| * are raised | ||
| */ | ||
| public void setContentHandler(ContentHandler contentHandler) { | ||
| 4x | this.contentHandler = contentHandler; | |
| 4x | } | |
| /** @return the <code>DTDHandler</code> | ||
| */ | ||
| public DTDHandler getDTDHandler() { | ||
| 0x | return dtdHandler; | |
| } | ||
| /** Sets the <code>DTDHandler</code>. | ||
| */ | ||
| public void setDTDHandler(DTDHandler dtdHandler) { | ||
| 4x | this.dtdHandler = dtdHandler; | |
| 4x | } | |
| /** @return the <code>ErrorHandler</code> | ||
| */ | ||
| public ErrorHandler getErrorHandler() { | ||
| 0x | return errorHandler; | |
| } | ||
| /** Sets the <code>ErrorHandler</code>. | ||
| */ | ||
| public void setErrorHandler(ErrorHandler errorHandler) { | ||
| 0x | this.errorHandler = errorHandler; | |
| 0x | } | |
| /** @return the <code>EntityResolver</code> used when a Document contains | ||
| * a DTD | ||
| */ | ||
| public EntityResolver getEntityResolver() { | ||
| 0x | return entityResolver; | |
| } | ||
| /** Sets the <code>EntityResolver</code> . | ||
| * | ||
| * @param entityResolver is the <code>EntityResolver</code> | ||
| */ | ||
| public void setEntityResolver(EntityResolver entityResolver) { | ||
| 0x | this.entityResolver = entityResolver; | |
| 0x | } | |
| /** @return the <code>LexicalHandler</code> used when a Document contains | ||
| * a DTD | ||
| */ | ||
| public LexicalHandler getLexicalHandler() { | ||
| 0x | return lexicalHandler; | |
| } | ||
| /** Sets the <code>LexicalHandler</code> . | ||
| * | ||
| * @param lexicalHandler is the <code>LexicalHandler</code> | ||
| */ | ||
| public void setLexicalHandler(LexicalHandler lexicalHandler) { | ||
| 8x | this.lexicalHandler = lexicalHandler; | |
| 8x | } | |
| /** Sets the <code>XMLReader</code> used to write SAX events to | ||
| * | ||
| * @param xmlReader is the <code>XMLReader</code> | ||
| */ | ||
| public void setXMLReader(XMLReader xmlReader) { | ||
| 0x | setContentHandler( xmlReader.getContentHandler() ); | |
| 0x | setDTDHandler( xmlReader.getDTDHandler() ); | |
| 0x | setEntityResolver( xmlReader.getEntityResolver() ); | |
| 0x | setErrorHandler( xmlReader.getErrorHandler() ); | |
| 0x | } | |
| /** Looks up the value of a feature. | ||
| */ | ||
| public boolean getFeature(String name) | ||
| throws SAXNotRecognizedException, SAXNotSupportedException { | ||
| 0x | Boolean answer = (Boolean) features.get(name); | |
| 0/4 0x | return answer != null && answer.booleanValue(); | |
| } | ||
| /** This implementation does actually use any features but just | ||
| * stores them for later retrieval | ||
| */ | ||
| public void setFeature(String name, boolean value) throws SAXNotRecognizedException, SAXNotSupportedException { | ||
| 1/2 4x | if ( FEATURE_NAMESPACE_PREFIXES.equals( name ) ) { | |
| 4x | setDeclareNamespaceAttributes( value ); | |
| } | ||
| 0/2 0x | else if ( FEATURE_NAMESPACE_PREFIXES.equals( name ) ) { | |
| 0/2 0x | if ( ! value ) { | |
| 0x | throw new SAXNotSupportedException(name + ". namespace feature is always supported in dom4j." ); | |
| } | ||
| } | ||
| 1/2 4x | features.put(name, (value) ? Boolean.TRUE : Boolean.FALSE ); | |
| 4x | } | |
| /** Sets the given SAX property | ||
| */ | ||
| public void setProperty(String name, Object value) throws SAXNotRecognizedException, SAXNotSupportedException { | ||
| 2/2 36x | for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) { | |
| 2/2 28x | if (LEXICAL_HANDLER_NAMES[i].equals(name)) { | |
| 8x | setLexicalHandler((LexicalHandler) value); | |
| 8x | return; | |
| } | ||
| } | ||
| 8x | properties.put(name, value); | |
| 8x | } | |
| /** Gets the given SAX property | ||
| */ | ||
| public Object getProperty(String name) throws SAXNotRecognizedException, SAXNotSupportedException { | ||
| 0/2 0x | for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) { | |
| 0/2 0x | if (LEXICAL_HANDLER_NAMES[i].equals(name)) { | |
| 0x | return getLexicalHandler(); | |
| } | ||
| } | ||
| 0x | return properties.get(name); | |
| } | ||
| /** This method is not supported. | ||
| */ | ||
| public void parse(String systemId) throws SAXNotSupportedException { | ||
| 0x | throw new SAXNotSupportedException( | |
| "This XMLReader can only accept <dom4j> InputSource objects" | ||
| ); | ||
| } | ||
| /** Parses an XML document. | ||
| * This method can only accept DocumentInputSource inputs | ||
| * otherwise a {@link SAXNotSupportedException} exception is thrown. | ||
| * | ||
| * @throws SAXNotSupportedException | ||
| * if the input source is not wrapping a dom4j document | ||
| */ | ||
| public void parse(InputSource input) throws SAXException { | ||
| 1/2 4x | if (input instanceof DocumentInputSource) { | |
| 4x | DocumentInputSource documentInput = (DocumentInputSource) input; | |
| 4x | Document document = documentInput.getDocument(); | |
| 4x | write( document ); | |
| } | ||
| else { | ||
| 0x | throw new SAXNotSupportedException( | |
| "This XMLReader can only accept <dom4j> InputSource objects" | ||
| ); | ||
| } | ||
| 4x | } | |
| // Implementation methods | ||
| //------------------------------------------------------------------------- | ||
| protected void writeContent( Branch branch, NamespaceStack namespaceStack ) throws SAXException { | ||
| 2/2 40x | for ( Iterator iter = branch.nodeIterator(); iter.hasNext(); ) { | |
| 24x | Object object = iter.next(); | |
| 2/2 24x | if ( object instanceof Element ) { | |
| 12x | write( (Element) object, namespaceStack ); | |
| } | ||
| 1/2 12x | else if ( object instanceof CharacterData ) { | |
| 1/2 12x | if ( object instanceof Text ) { | |
| 12x | Text text = (Text) object; | |
| 12x | write( text.getText() ); | |
| } | ||
| 0/2 0x | & |