Project dom4j 1.5.2 [5/2/05 10:13 PM]
 
Coverage - org/dom4j/io/SAXWriter.java
  /*
   * 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         &