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