diff NGSrich_0.5.5/src/org/jdom/output/SAXOutputter.java @ 0:89ad0a9cca52 default tip

Uploaded
author pfrommolt
date Mon, 21 Nov 2011 08:12:19 -0500
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/NGSrich_0.5.5/src/org/jdom/output/SAXOutputter.java	Mon Nov 21 08:12:19 2011 -0500
@@ -0,0 +1,1439 @@
+/*--
+
+ $Id: SAXOutputter.java,v 1.40 2007/11/10 05:29:01 jhunter Exp $
+
+ Copyright (C) 2000-2007 Jason Hunter & Brett McLaughlin.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ 1. Redistributions of source code must retain the above copyright
+    notice, this list of conditions, and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+    notice, this list of conditions, and the disclaimer that follows
+    these conditions in the documentation and/or other materials
+    provided with the distribution.
+
+ 3. The name "JDOM" must not be used to endorse or promote products
+    derived from this software without prior written permission.  For
+    written permission, please contact <request_AT_jdom_DOT_org>.
+
+ 4. Products derived from this software may not be called "JDOM", nor
+    may "JDOM" appear in their name, without prior written permission
+    from the JDOM Project Management <request_AT_jdom_DOT_org>.
+
+ In addition, we request (but do not require) that you include in the
+ end-user documentation provided with the redistribution and/or in the
+ software itself an acknowledgement equivalent to the following:
+     "This product includes software developed by the
+      JDOM Project (http://www.jdom.org/)."
+ Alternatively, the acknowledgment may be graphical using the logos
+ available at http://www.jdom.org/images/logos.
+
+ THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED.  IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ SUCH DAMAGE.
+
+ This software consists of voluntary contributions made by many
+ individuals on behalf of the JDOM Project and was originally
+ created by Jason Hunter <jhunter_AT_jdom_DOT_org> and
+ Brett McLaughlin <brett_AT_jdom_DOT_org>.  For more information
+ on the JDOM Project, please see <http://www.jdom.org/>.
+
+ */
+
+package org.jdom.output;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+import org.jdom.*;
+import org.xml.sax.*;
+import org.xml.sax.ext.*;
+import org.xml.sax.helpers.*;
+
+/**
+ * Outputs a JDOM document as a stream of SAX2 events.
+ * <p>
+ * Most ContentHandler callbacks are supported. Both
+ * <code>ignorableWhitespace()</code> and <code>skippedEntity()</code> have not
+ * been implemented. The <code>{@link JDOMLocator}</code> class returned by
+ * <code>{@link #getLocator}</code> exposes the current node being operated
+ * upon.
+ * <p>
+ * At this time, it is not possible to access notations and unparsed entity
+ * references in a DTD from JDOM. Therefore, <code>DTDHandler</code> callbacks
+ * have not been implemented yet.
+ * <p>
+ * The <code>ErrorHandler</code> callbacks have not been implemented, since
+ * these are supposed to be invoked when the document is parsed and at this
+ * point the document exists in memory and is known to have no errors. </p>
+ *
+ * @version $Revision: 1.40 $, $Date: 2007/11/10 05:29:01 $
+ * @author  Brett McLaughlin
+ * @author  Jason Hunter
+ * @author  Fred Trimble
+ * @author  Bradley S. Huffman
+ */
+public class SAXOutputter {
+
+    private static final String CVS_ID =
+      "@(#) $RCSfile: SAXOutputter.java,v $ $Revision: 1.40 $ $Date: 2007/11/10 05:29:01 $ $Name: jdom_1_1_1 $";
+
+    /** Shortcut for SAX namespaces core feature */
+    private static final String NAMESPACES_SAX_FEATURE =
+                        "http://xml.org/sax/features/namespaces";
+
+    /** Shortcut for SAX namespace-prefixes core feature */
+    private static final String NS_PREFIXES_SAX_FEATURE =
+                        "http://xml.org/sax/features/namespace-prefixes";
+
+    /** Shortcut for SAX validation core feature */
+    private static final String VALIDATION_SAX_FEATURE =
+                        "http://xml.org/sax/features/validation";
+
+    /** Shortcut for SAX-ext. lexical handler property */
+    private static final String LEXICAL_HANDLER_SAX_PROPERTY =
+                        "http://xml.org/sax/properties/lexical-handler";
+
+    /** Shortcut for SAX-ext. declaration handler property */
+    private static final String DECL_HANDLER_SAX_PROPERTY =
+                        "http://xml.org/sax/properties/declaration-handler";
+
+    /**
+     * Shortcut for SAX-ext. lexical handler alternate property.
+     * Although this property URI is not the one defined by the SAX
+     * "standard", some parsers use it instead of the official one.
+     */
+    private static final String LEXICAL_HANDLER_ALT_PROPERTY =
+                        "http://xml.org/sax/handlers/LexicalHandler";
+
+    /** Shortcut for SAX-ext. declaration handler alternate property */
+    private static final String DECL_HANDLER_ALT_PROPERTY =
+                        "http://xml.org/sax/handlers/DeclHandler";
+
+    /**
+     * Array to map JDOM attribute type (as entry index) to SAX
+     * attribute type names.
+     */
+    private static final String[] attrTypeToNameMap = new String[] {
+        "CDATA",        // Attribute.UNDEFINED_ATTRIBUTE, as per SAX 2.0 spec.
+        "CDATA",        // Attribute.CDATA_TYPE
+        "ID",           // Attribute.ID_TYPE
+        "IDREF",        // Attribute.IDREF_TYPE
+        "IDREFS",       // Attribute.IDREFS_TYPE
+        "ENTITY",       // Attribute.ENTITY_TYPE
+        "ENTITIES",     // Attribute.ENTITIES_TYPE
+        "NMTOKEN",      // Attribute.NMTOKEN_TYPE
+        "NMTOKENS",     // Attribute.NMTOKENS_TYPE
+        "NOTATION",     // Attribute.NOTATION_TYPE
+        "NMTOKEN",      // Attribute.ENUMERATED_TYPE, as per SAX 2.0 spec.
+    };
+
+    /** registered <code>ContentHandler</code> */
+    private ContentHandler contentHandler;
+
+    /** registered <code>ErrorHandler</code> */
+    private ErrorHandler errorHandler;
+
+    /** registered <code>DTDHandler</code> */
+    private DTDHandler dtdHandler;
+
+    /** registered <code>EntityResolver</code> */
+    private EntityResolver entityResolver;
+
+    /** registered <code>LexicalHandler</code> */
+    private LexicalHandler lexicalHandler;
+
+    /** registered <code>DeclHandler</code> */
+    private DeclHandler declHandler;
+
+    /**
+     * Whether to report attribute namespace declarations as xmlns attributes.
+     * Defaults to <code>false</code> as per SAX specifications.
+     *
+     * @see <a href="http://www.megginson.com/SAX/Java/namespaces.html">
+     *  SAX namespace specifications</a>
+     */
+    private boolean declareNamespaces = false;
+
+    /**
+     * Whether to report DTD events to DeclHandlers and LexicalHandlers.
+     * Defaults to <code>true</code>.
+     */
+    private boolean reportDtdEvents = true;
+
+    /**
+     * A SAX Locator that points at the JDOM node currently being
+     * outputted.
+     */
+    private JDOMLocator locator = null;
+
+    /**
+     * This will create a <code>SAXOutputter</code> without any
+     * registered handler.  The application is then responsible for
+     * registering them using the <code>setXxxHandler()</code> methods.
+     */
+    public SAXOutputter() {
+    }
+
+    /**
+     * This will create a <code>SAXOutputter</code> with the
+     * specified <code>ContentHandler</code>.
+     *
+     * @param contentHandler contains <code>ContentHandler</code>
+     * callback methods
+     */
+    public SAXOutputter(ContentHandler contentHandler) {
+        this(contentHandler, null, null, null, null);
+    }
+
+    /**
+     * This will create a <code>SAXOutputter</code> with the
+     * specified SAX2 handlers. At this time, only <code>ContentHandler</code>
+     * and <code>EntityResolver</code> are supported.
+     *
+     * @param contentHandler contains <code>ContentHandler</code>
+     * callback methods
+     * @param errorHandler contains <code>ErrorHandler</code> callback methods
+     * @param dtdHandler contains <code>DTDHandler</code> callback methods
+     * @param entityResolver contains <code>EntityResolver</code>
+     * callback methods
+     */
+    public SAXOutputter(ContentHandler contentHandler,
+                        ErrorHandler   errorHandler,
+                        DTDHandler     dtdHandler,
+                        EntityResolver entityResolver) {
+        this(contentHandler, errorHandler, dtdHandler, entityResolver, null);
+    }
+
+    /**
+     * This will create a <code>SAXOutputter</code> with the
+     * specified SAX2 handlers. At this time, only <code>ContentHandler</code>
+     * and <code>EntityResolver</code> are supported.
+     *
+     * @param contentHandler contains <code>ContentHandler</code>
+     * callback methods
+     * @param errorHandler contains <code>ErrorHandler</code> callback methods
+     * @param dtdHandler contains <code>DTDHandler</code> callback methods
+     * @param entityResolver contains <code>EntityResolver</code>
+     * callback methods
+     * @param lexicalHandler contains <code>LexicalHandler</code> callbacks.
+     */
+    public SAXOutputter(ContentHandler contentHandler,
+                        ErrorHandler   errorHandler,
+                        DTDHandler     dtdHandler,
+                        EntityResolver entityResolver,
+                        LexicalHandler lexicalHandler) {
+        this.contentHandler = contentHandler;
+        this.errorHandler = errorHandler;
+        this.dtdHandler = dtdHandler;
+        this.entityResolver = entityResolver;
+        this.lexicalHandler = lexicalHandler;
+    }
+
+    /**
+     * This will set the <code>ContentHandler</code>.
+     *
+     * @param contentHandler contains <code>ContentHandler</code>
+     * callback methods.
+     */
+    public void setContentHandler(ContentHandler contentHandler) {
+        this.contentHandler = contentHandler;
+    }
+
+    /**
+     * Returns the registered <code>ContentHandler</code>.
+     *
+     * @return the current <code>ContentHandler</code> or
+     * <code>null</code> if none was registered.
+     */
+    public ContentHandler getContentHandler() {
+        return this.contentHandler;
+    }
+
+    /**
+     * This will set the <code>ErrorHandler</code>.
+     *
+     * @param errorHandler contains <code>ErrorHandler</code> callback methods.
+     */
+    public void setErrorHandler(ErrorHandler errorHandler) {
+        this.errorHandler = errorHandler;
+    }
+
+    /**
+     * Return the registered <code>ErrorHandler</code>.
+     *
+     * @return the current <code>ErrorHandler</code> or
+     * <code>null</code> if none was registered.
+     */
+    public ErrorHandler getErrorHandler() {
+        return this.errorHandler;
+    }
+
+    /**
+     * This will set the <code>DTDHandler</code>.
+     *
+     * @param dtdHandler contains <code>DTDHandler</code> callback methods.
+     */
+    public void setDTDHandler(DTDHandler dtdHandler) {
+        this.dtdHandler = dtdHandler;
+    }
+
+    /**
+     * Return the registered <code>DTDHandler</code>.
+     *
+     * @return the current <code>DTDHandler</code> or
+     * <code>null</code> if none was registered.
+     */
+    public DTDHandler getDTDHandler() {
+        return this.dtdHandler;
+    }
+
+    /**
+     * This will set the <code>EntityResolver</code>.
+     *
+     * @param entityResolver contains EntityResolver callback methods.
+     */
+    public void setEntityResolver(EntityResolver entityResolver) {
+        this.entityResolver = entityResolver;
+    }
+
+    /**
+     * Return the registered <code>EntityResolver</code>.
+     *
+     * @return the current <code>EntityResolver</code> or
+     * <code>null</code> if none was registered.
+     */
+    public EntityResolver getEntityResolver() {
+        return this.entityResolver;
+    }
+
+    /**
+     * This will set the <code>LexicalHandler</code>.
+     *
+     * @param lexicalHandler contains lexical callback methods.
+     */
+    public void setLexicalHandler(LexicalHandler lexicalHandler) {
+        this.lexicalHandler = lexicalHandler;
+    }
+
+    /**
+     * Return the registered <code>LexicalHandler</code>.
+     *
+     * @return the current <code>LexicalHandler</code> or
+     * <code>null</code> if none was registered.
+     */
+    public LexicalHandler getLexicalHandler() {
+        return this.lexicalHandler;
+    }
+
+    /**
+     * This will set the <code>DeclHandler</code>.
+     *
+     * @param declHandler contains declaration callback methods.
+     */
+    public void setDeclHandler(DeclHandler declHandler) {
+        this.declHandler = declHandler;
+    }
+
+    /**
+     * Return the registered <code>DeclHandler</code>.
+     *
+     * @return the current <code>DeclHandler</code> or
+     * <code>null</code> if none was registered.
+     */
+    public DeclHandler getDeclHandler() {
+        return this.declHandler;
+    }
+
+    /**
+     * Returns whether attribute namespace declarations shall be reported as
+     * "xmlns" attributes.
+     *
+     * @return whether attribute namespace declarations shall be reported as
+     * "xmlns" attributes.
+     */
+    public boolean getReportNamespaceDeclarations() {
+        return declareNamespaces;
+    }
+
+    /**
+     * This will define whether attribute namespace declarations shall be
+     * reported as "xmlns" attributes.  This flag defaults to <code>false</code>
+     * and behaves as the "namespace-prefixes" SAX core feature.
+     *
+     * @param declareNamespaces whether attribute namespace declarations
+     * shall be reported as "xmlns" attributes.
+     */
+    public void setReportNamespaceDeclarations(boolean declareNamespaces) {
+        this.declareNamespaces = declareNamespaces;
+    }
+
+    /**
+     * Returns whether DTD events will be reported.
+     *
+     * @return whether DTD events will be reported
+     */
+    public boolean getReportDTDEvents() {
+        return reportDtdEvents;
+    }
+
+    /**
+     * This will define whether to report DTD events to SAX DeclHandlers
+     * and LexicalHandlers if these handlers are registered and the
+     * document to output includes a DocType declaration.
+     *
+     * @param reportDtdEvents whether to notify DTD events.
+     */
+    public void setReportDTDEvents(boolean reportDtdEvents) {
+        this.reportDtdEvents = reportDtdEvents;
+    }
+
+    /**
+     * This will set the state of a SAX feature.
+     * <p>
+     * All XMLReaders are required to support setting to true and to false.
+     * </p>
+     * <p>
+     * SAXOutputter currently supports the following SAX core features:
+     * <dl>
+     *  <dt><code>http://xml.org/sax/features/namespaces</code></dt>
+     *   <dd><strong>description:</strong> <code>true</code> indicates
+     *       namespace URIs and unprefixed local names for element and
+     *       attribute names will be available</dd>
+     *   <dd><strong>access:</strong> read/write, but always
+     *       <code>true</code>!</dd>
+     *  <dt><code>http://xml.org/sax/features/namespace-prefixes</code></dt>
+     *   <dd><strong>description:</strong> <code>true</code> indicates
+     *       XML 1.0 names (with prefixes) and attributes (including xmlns*
+     *       attributes) will be available</dd>
+     *   <dd><strong>access:</strong> read/write</dd>
+     *  <dt><code>http://xml.org/sax/features/validation</code></dt>
+     *   <dd><strong>description:</strong> controls whether SAXOutputter
+     *       is reporting DTD-related events; if <code>true</code>, the
+     *       DocType internal subset will be parsed to fire DTD events</dd>
+     *   <dd><strong>access:</strong> read/write, defaults to
+     *       <code>true</code></dd>
+     * </dl>
+     * </p>
+     *
+     * @param name  <code>String</code> the feature name, which is a
+     *              fully-qualified URI.
+     * @param value <code>boolean</code> the requested state of the
+     *              feature (true or false).
+     *
+     * @throws SAXNotRecognizedException when SAXOutputter does not
+     *         recognize the feature name.
+     * @throws SAXNotSupportedException  when SAXOutputter recognizes
+     *         the feature name but cannot set the requested value.
+     */
+    public void setFeature(String name, boolean value)
+                throws SAXNotRecognizedException, SAXNotSupportedException {
+        if (NS_PREFIXES_SAX_FEATURE.equals(name)) {
+            // Namespace prefix declarations.
+            this.setReportNamespaceDeclarations(value);
+        }
+        else {
+            if (NAMESPACES_SAX_FEATURE.equals(name)) {
+                if (value != true) {
+                    // Namespaces feature always supported by SAXOutputter.
+                    throw new SAXNotSupportedException(name);
+                }
+                // Else: true is OK!
+            }
+            else {
+                if (VALIDATION_SAX_FEATURE.equals(name)) {
+                    // Report DTD events.
+                    this.setReportDTDEvents(value);
+                }
+                else {
+                    // Not a supported feature.
+                    throw new SAXNotRecognizedException(name);
+                }
+            }
+        }
+    }
+
+    /**
+     * This will look up the value of a SAX feature.
+     *
+     * @param name <code>String</code> the feature name, which is a
+     *             fully-qualified URI.
+     * @return <code>boolean</code> the current state of the feature
+     *         (true or false).
+     *
+     * @throws SAXNotRecognizedException when SAXOutputter does not
+     *         recognize the feature name.
+     * @throws SAXNotSupportedException  when SAXOutputter recognizes
+     *         the feature name but determine its value at this time.
+     */
+    public boolean getFeature(String name)
+                throws SAXNotRecognizedException, SAXNotSupportedException {
+        if (NS_PREFIXES_SAX_FEATURE.equals(name)) {
+            // Namespace prefix declarations.
+            return (this.declareNamespaces);
+        }
+        else {
+            if (NAMESPACES_SAX_FEATURE.equals(name)) {
+                // Namespaces feature always supported by SAXOutputter.
+                return (true);
+            }
+            else {
+                if (VALIDATION_SAX_FEATURE.equals(name)) {
+                    // Report DTD events.
+                    return (this.reportDtdEvents);
+                }
+                else {
+                    // Not a supported feature.
+                    throw new SAXNotRecognizedException(name);
+                }
+            }
+        }
+    }
+
+    /**
+     * This will set the value of a SAX property.
+     * This method is also the standard mechanism for setting extended
+     * handlers.
+     * <p>
+     * SAXOutputter currently supports the following SAX properties:
+     * <dl>
+     *  <dt><code>http://xml.org/sax/properties/lexical-handler</code></dt>
+     *   <dd><strong>data type:</strong>
+     *       <code>org.xml.sax.ext.LexicalHandler</code></dd>
+     *   <dd><strong>description:</strong> An optional extension handler for
+     *       lexical events like comments.</dd>
+     *   <dd><strong>access:</strong> read/write</dd>
+     *  <dt><code>http://xml.org/sax/properties/declaration-handler</code></dt>
+     *   <dd><strong>data type:</strong>
+     *       <code>org.xml.sax.ext.DeclHandler</code></dd>
+     *   <dd><strong>description:</strong> An optional extension handler for
+     *       DTD-related events other than notations and unparsed entities.</dd>
+     *   <dd><strong>access:</strong> read/write</dd>
+     * </dl>
+     * </p>
+     *
+     * @param name  <code>String</code> the property name, which is a
+     *              fully-qualified URI.
+     * @param value <code>Object</code> the requested value for the property.
+     *
+     * @throws SAXNotRecognizedException when SAXOutputter does not recognize
+     *         the property name.
+     * @throws SAXNotSupportedException  when SAXOutputter recognizes the
+     *         property name but cannot set the requested value.
+     */
+    public void setProperty(String name, Object value)
+                throws SAXNotRecognizedException, SAXNotSupportedException {
+        if ((LEXICAL_HANDLER_SAX_PROPERTY.equals(name)) ||
+            (LEXICAL_HANDLER_ALT_PROPERTY.equals(name))) {
+            this.setLexicalHandler((LexicalHandler)value);
+        }
+        else {
+            if ((DECL_HANDLER_SAX_PROPERTY.equals(name)) ||
+                (DECL_HANDLER_ALT_PROPERTY.equals(name))) {
+                this.setDeclHandler((DeclHandler)value);
+            }
+            else {
+                throw new SAXNotRecognizedException(name);
+            }
+        }
+    }
+
+    /**
+     * This will look up the value of a SAX property.
+     *
+     * @param name <code>String</code> the property name, which is a
+     *             fully-qualified URI.
+     * @return <code>Object</code> the current value of the property.
+     *
+     * @throws SAXNotRecognizedException when SAXOutputter does not recognize
+     *         the property name.
+     * @throws SAXNotSupportedException  when SAXOutputter recognizes the
+     *         property name but cannot determine its value at this time.
+     */
+    public Object getProperty(String name)
+                throws SAXNotRecognizedException, SAXNotSupportedException {
+        if ((LEXICAL_HANDLER_SAX_PROPERTY.equals(name)) ||
+            (LEXICAL_HANDLER_ALT_PROPERTY.equals(name))) {
+            return this.getLexicalHandler();
+        }
+        else {
+            if ((DECL_HANDLER_SAX_PROPERTY.equals(name)) ||
+                (DECL_HANDLER_ALT_PROPERTY.equals(name))) {
+                return this.getDeclHandler();
+            }
+            else {
+                throw new SAXNotRecognizedException(name);
+            }
+        }
+    }
+
+
+    /**
+     * This will output the <code>JDOM Document</code>, firing off the
+     * SAX events that have been registered.
+     *
+     * @param document <code>JDOM Document</code> to output.
+     *
+     * @throws JDOMException if any error occurred.
+     */
+    public void output(Document document) throws JDOMException {
+        if (document == null) {
+            return;
+        }
+
+        // contentHandler.setDocumentLocator()
+        documentLocator(document);
+
+        // contentHandler.startDocument()
+        startDocument();
+
+        // Fire DTD events
+        if (this.reportDtdEvents) {
+           dtdEvents(document);
+        }
+
+        // Handle root element, as well as any root level
+        // processing instructions and comments
+        Iterator i = document.getContent().iterator();
+        while (i.hasNext()) {
+            Object obj = i.next();
+
+            // update locator
+            locator.setNode(obj);
+
+            if (obj instanceof Element) {
+                // process root element and its content
+                element(document.getRootElement(), new NamespaceStack());
+            }
+            else if (obj instanceof ProcessingInstruction) {
+                // contentHandler.processingInstruction()
+                processingInstruction((ProcessingInstruction) obj);
+            }
+            else if (obj instanceof Comment) {
+                // lexicalHandler.comment()
+                comment(((Comment) obj).getText());
+            }
+        }
+
+        // contentHandler.endDocument()
+        endDocument();
+    }
+
+    /**
+     * This will output a list of JDOM nodes as a document, firing
+     * off the SAX events that have been registered.
+     * <p>
+     * <strong>Warning</strong>: This method may output ill-formed XML
+     * documents if the list contains top-level objects that are not
+     * legal at the document level (e.g. Text or CDATA nodes, multiple
+     * Element nodes, etc.).  Thus, it should only be used to output
+     * document portions towards ContentHandlers capable of accepting
+     * such ill-formed documents (such as XSLT processors).</p>
+     *
+     * @param nodes <code>List</code> of JDOM nodes to output.
+     *
+     * @throws JDOMException if any error occurred.
+     *
+     * @see #output(org.jdom.Document)
+     */
+    public void output(List nodes) throws JDOMException {
+        if ((nodes == null) || (nodes.size() == 0)) {
+            return;
+        }
+
+        // contentHandler.setDocumentLocator()
+        documentLocator(null);
+
+        // contentHandler.startDocument()
+        startDocument();
+
+        // Process node list.
+        elementContent(nodes, new NamespaceStack());
+
+        // contentHandler.endDocument()
+        endDocument();
+    }
+
+    /**
+     * This will output a single JDOM element as a document, firing
+     * off the SAX events that have been registered.
+     *
+     * @param node the <code>Element</code> node to output.
+     *
+     * @throws JDOMException if any error occurred.
+     */
+    public void output(Element node) throws JDOMException {
+        if (node == null) {
+            return;
+        }
+
+        // contentHandler.setDocumentLocator()
+        documentLocator(null);
+
+        // contentHandler.startDocument()
+        startDocument();
+
+        // Output node.
+        elementContent(node, new NamespaceStack());
+
+        // contentHandler.endDocument()
+        endDocument();
+    }
+
+    /**
+     * This will output a list of JDOM nodes as a fragment of an XML
+     * document, firing off the SAX events that have been registered.
+     * <p>
+     * <strong>Warning</strong>: This method does not call the
+     * {@link ContentHandler#setDocumentLocator},
+     * {@link ContentHandler#startDocument} and
+     * {@link ContentHandler#endDocument} callbacks on the
+     * {@link #setContentHandler ContentHandler}.  The user shall
+     * invoke these methods directly prior/after outputting the
+     * document fragments.</p>
+     *
+     * @param nodes <code>List</code> of JDOM nodes to output.
+     *
+     * @throws JDOMException if any error occurred.
+     *
+     * @see #outputFragment(org.jdom.Content)
+     */
+    public void outputFragment(List nodes) throws JDOMException {
+        if ((nodes == null) || (nodes.size() == 0)) {
+            return;
+        }
+
+        // Output node list as a document fragment.
+        elementContent(nodes, new NamespaceStack());
+    }
+
+    /**
+     * This will output a single JDOM nodes as a fragment of an XML
+     * document, firing off the SAX events that have been registered.
+     * <p>
+     * <strong>Warning</strong>: This method does not call the
+     * {@link ContentHandler#setDocumentLocator},
+     * {@link ContentHandler#startDocument} and
+     * {@link ContentHandler#endDocument} callbacks on the
+     * {@link #setContentHandler ContentHandler}.  The user shall
+     * invoke these methods directly prior/after outputting the
+     * document fragments.</p>
+     *
+     * @param node the <code>Content</code> node to output.
+     *
+     * @throws JDOMException if any error occurred.
+     *
+     * @see #outputFragment(java.util.List)
+     */
+    public void outputFragment(Content node) throws JDOMException {
+        if (node == null) {
+            return;
+        }
+
+        // Output single node as a document fragment.
+        elementContent(node, new NamespaceStack());
+    }
+
+    /**
+     * This parses a DTD declaration to fire the related events towards
+     * the registered handlers.
+     *
+     * @param document <code>JDOM Document</code> the DocType is to
+     *                 process.
+     */
+    private void dtdEvents(Document document) throws JDOMException {
+        DocType docType = document.getDocType();
+
+        // Fire DTD-related events only if handlers have been registered.
+        if ((docType != null) &&
+            ((dtdHandler != null) || (declHandler != null))) {
+
+            // Build a dummy XML document that only references the DTD...
+            String dtdDoc = new XMLOutputter().outputString(docType);
+
+            try {
+                // And parse it to fire DTD events.
+                createDTDParser().parse(new InputSource(
+                                                new StringReader(dtdDoc)));
+
+                // We should never reach this point as the document is
+                // ill-formed; it does not have any root element.
+            }
+            catch (SAXParseException e) {
+                // Expected exception: There's no root element in document.
+            }
+            catch (SAXException e) {
+                throw new JDOMException("DTD parsing error", e);
+            }
+            catch (IOException e) {
+                throw new JDOMException("DTD parsing error", e);
+            }
+        }
+    }
+
+    /**
+     * <p>
+     * This method tells you the line of the XML file being parsed.
+     * For an in-memory document, it's meaningless. The location
+     * is only valid for the current parsing lifecycle, but
+     * the document has already been parsed. Therefore, it returns
+     * -1 for both line and column numbers.
+     * </p>
+     *
+     * @param document JDOM <code>Document</code>.
+     */
+    private void documentLocator(Document document) {
+        locator = new JDOMLocator();
+        String publicID = null;
+        String systemID = null;
+
+        if (document != null) {
+            DocType docType = document.getDocType();
+            if (docType != null) {
+                publicID = docType.getPublicID();
+                systemID = docType.getSystemID();
+            }
+        }
+        locator.setPublicId(publicID);
+        locator.setSystemId(systemID);
+        locator.setLineNumber(-1);
+        locator.setColumnNumber(-1);
+
+        contentHandler.setDocumentLocator(locator);
+    }
+
+    /**
+     * <p>
+     * This method is always the second method of all callbacks in
+     * all handlers to be invoked (setDocumentLocator is always first).
+     * </p>
+     */
+    private void startDocument() throws JDOMException {
+        try {
+            contentHandler.startDocument();
+        }
+        catch (SAXException se) {
+            throw new JDOMException("Exception in startDocument", se);
+        }
+    }
+
+    /**
+     * <p>
+     * Always the last method of all callbacks in all handlers
+     * to be invoked.
+     * </p>
+     */
+    private void endDocument() throws JDOMException {
+        try {
+            contentHandler.endDocument();
+
+            // reset locator
+            locator = null;
+        }
+        catch (SAXException se) {
+            throw new JDOMException("Exception in endDocument", se);
+        }
+    }
+
+    /**
+     * <p>
+     * This will invoke the <code>ContentHandler.processingInstruction</code>
+     * callback when a processing instruction is encountered.
+     * </p>
+     *
+     * @param pi <code>ProcessingInstruction</code> containing target and data.
+     */
+    private void processingInstruction(ProcessingInstruction pi)
+                           throws JDOMException {
+        if (pi != null) {
+            String target = pi.getTarget();
+            String data = pi.getData();
+            try {
+                contentHandler.processingInstruction(target, data);
+            }
+            catch (SAXException se) {
+                throw new JDOMException(
+                    "Exception in processingInstruction", se);
+            }
+        }
+    }
+
+    /**
+     * <p>
+     * This will recursively invoke all of the callbacks for a particular
+     * element.
+     * </p>
+     *
+     * @param element <code>Element</code> used in callbacks.
+     * @param namespaces <code>List</code> stack of Namespaces in scope.
+     */
+    private void element(Element element, NamespaceStack namespaces)
+                           throws JDOMException {
+        // used to check endPrefixMapping
+        int previouslyDeclaredNamespaces = namespaces.size();
+
+        // contentHandler.startPrefixMapping()
+        Attributes nsAtts = startPrefixMapping(element, namespaces);
+
+        // contentHandler.startElement()
+        startElement(element, nsAtts);
+
+        // handle content in the element
+        elementContent(element.getContent(), namespaces);
+
+        // update locator
+        if (locator != null) {
+          locator.setNode(element);
+        }
+
+        // contentHandler.endElement()
+        endElement(element);
+
+        // contentHandler.endPrefixMapping()
+        endPrefixMapping(namespaces, previouslyDeclaredNamespaces);
+    }
+
+    /**
+     * <p>
+     * This will invoke the <code>ContentHandler.startPrefixMapping</code>
+     * callback
+     * when a new namespace is encountered in the <code>Document</code>.
+     * </p>
+     *
+     * @param element <code>Element</code> used in callbacks.
+     * @param namespaces <code>List</code> stack of Namespaces in scope.
+     *
+     * @return <code>Attributes</code> declaring the namespaces local to
+     * <code>element</code> or <code>null</code>.
+     */
+    private Attributes startPrefixMapping(Element element,
+                                          NamespaceStack namespaces)
+                                                   throws JDOMException {
+        AttributesImpl nsAtts = null;   // The namespaces as xmlns attributes
+
+        Namespace ns = element.getNamespace();
+        if (ns != Namespace.XML_NAMESPACE) {
+            String prefix = ns.getPrefix();
+            String uri = namespaces.getURI(prefix);
+            if (!ns.getURI().equals(uri)) {
+                namespaces.push(ns);
+                nsAtts = this.addNsAttribute(nsAtts, ns);
+                try {
+                    contentHandler.startPrefixMapping(prefix, ns.getURI());
+                }
+                catch (SAXException se) {
+                   throw new JDOMException(
+                       "Exception in startPrefixMapping", se);
+                }
+            }
+        }
+
+        // Fire additional namespace declarations
+        List additionalNamespaces = element.getAdditionalNamespaces();
+        if (additionalNamespaces != null) {
+            Iterator itr = additionalNamespaces.iterator();
+            while (itr.hasNext()) {
+                ns = (Namespace)itr.next();
+                String prefix = ns.getPrefix();
+                String uri = namespaces.getURI(prefix);
+                if (!ns.getURI().equals(uri)) {
+                    namespaces.push(ns);
+                    nsAtts = this.addNsAttribute(nsAtts, ns);
+                    try {
+                        contentHandler.startPrefixMapping(prefix, ns.getURI());
+                    }
+                    catch (SAXException se) {
+                        throw new JDOMException(
+                            "Exception in startPrefixMapping", se);
+                    }
+                }
+            }
+        }
+        return nsAtts;
+    }
+
+    /**
+     * <p>
+     * This will invoke the <code>endPrefixMapping</code> callback in the
+     * <code>ContentHandler</code> when a namespace is goes out of scope
+     * in the <code>Document</code>.
+     * </p>
+     *
+     * @param namespaces <code>List</code> stack of Namespaces in scope.
+     * @param previouslyDeclaredNamespaces number of previously declared
+     * namespaces
+     */
+    private void endPrefixMapping(NamespaceStack namespaces,
+                                  int previouslyDeclaredNamespaces)
+                                                throws JDOMException {
+        while (namespaces.size() > previouslyDeclaredNamespaces) {
+            String prefix = namespaces.pop();
+            try {
+                contentHandler.endPrefixMapping(prefix);
+            }
+            catch (SAXException se) {
+                throw new JDOMException("Exception in endPrefixMapping", se);
+            }
+        }
+    }
+
+    /**
+     * <p>
+     * This will invoke the <code>startElement</code> callback
+     * in the <code>ContentHandler</code>.
+     * </p>
+     *
+     * @param element <code>Element</code> used in callbacks.
+     * @param nsAtts <code>List</code> of namespaces to declare with
+     * the element or <code>null</code>.
+     */
+    private void startElement(Element element, Attributes nsAtts)
+                      throws JDOMException {
+        String namespaceURI = element.getNamespaceURI();
+        String localName = element.getName();
+        String rawName = element.getQualifiedName();
+
+        // Allocate attribute list.
+        AttributesImpl atts = (nsAtts != null)?
+                              new AttributesImpl(nsAtts): new AttributesImpl();
+
+        List attributes = element.getAttributes();
+        Iterator i = attributes.iterator();
+        while (i.hasNext()) {
+            Attribute a = (Attribute) i.next();
+            atts.addAttribute(a.getNamespaceURI(),
+                              a.getName(),
+                              a.getQualifiedName(),
+                              getAttributeTypeName(a.getAttributeType()),
+                              a.getValue());
+        }
+
+        try {
+            contentHandler.startElement(namespaceURI, localName, rawName, atts);
+        }
+        catch (SAXException se) {
+            throw new JDOMException("Exception in startElement", se);
+        }
+    }
+
+    /**
+     * <p>
+     * This will invoke the <code>endElement</code> callback
+     * in the <code>ContentHandler</code>.
+     * </p>
+     *
+     * @param element <code>Element</code> used in callbacks.
+     */
+    private void endElement(Element element) throws JDOMException {
+        String namespaceURI = element.getNamespaceURI();
+        String localName = element.getName();
+        String rawName = element.getQualifiedName();
+
+        try {
+            contentHandler.endElement(namespaceURI, localName, rawName);
+        }
+        catch (SAXException se) {
+            throw new JDOMException("Exception in endElement", se);
+        }
+    }
+
+    /**
+     * <p>
+     * This will invoke the callbacks for the content of an element.
+     * </p>
+     *
+     * @param content element content as a <code>List</code> of nodes.
+     * @param namespaces <code>List</code> stack of Namespaces in scope.
+     */
+    private void elementContent(List content, NamespaceStack namespaces)
+                      throws JDOMException {
+        for (Iterator i=content.iterator(); i.hasNext(); ) {
+            Object obj = i.next();
+
+            if (obj instanceof Content) {
+                this.elementContent((Content)obj, namespaces);
+            }
+            else {
+                // Not a valid element child. This could happen with
+                // application-provided lists which may contain non
+                // JDOM objects.
+                handleError(new JDOMException(
+                                        "Invalid element content: " + obj));
+            }
+        }
+    }
+
+    /**
+     * <p>
+     * This will invoke the callbacks for the content of an element.
+     * </p>
+     *
+     * @param node a <code>Content</code> node.
+     * @param namespaces <code>List</code> stack of Namespaces in scope.
+     */
+    private void elementContent(Content node, NamespaceStack namespaces)
+                      throws JDOMException {
+        // update locator
+        if (locator != null) {
+          locator.setNode(node);
+        }
+
+        if (node instanceof Element) {
+            element((Element) node, namespaces);
+        }
+        else if (node instanceof CDATA) {
+            cdata(((CDATA) node).getText());
+        }
+        else if (node instanceof Text) {
+            // contentHandler.characters()
+            characters(((Text) node).getText());
+        }
+        else if (node instanceof ProcessingInstruction) {
+            // contentHandler.processingInstruction()
+            processingInstruction((ProcessingInstruction) node);
+        }
+        else if (node instanceof Comment) {
+            // lexicalHandler.comment()
+            comment(((Comment) node).getText());
+        }
+        else if (node instanceof EntityRef) {
+            // contentHandler.skippedEntity()
+            entityRef((EntityRef) node);
+        }
+        else {
+            // Not a valid element child. This could happen with
+            // application-provided lists which may contain non
+            // JDOM objects.
+            handleError(new JDOMException("Invalid element content: " + node));
+        }
+    }
+
+    /**
+     * <p>
+     * This will be called for each chunk of CDATA section encountered.
+     * </p>
+     *
+     * @param cdataText all text in the CDATA section, including whitespace.
+     */
+    private void cdata(String cdataText) throws JDOMException {
+        try {
+            if (lexicalHandler != null) {
+                lexicalHandler.startCDATA();
+                characters(cdataText);
+                lexicalHandler.endCDATA();
+            }
+            else {
+                characters(cdataText);
+            }
+        }
+        catch (SAXException se) {
+            throw new JDOMException("Exception in CDATA", se);
+        }
+    }
+
+    /**
+     * <p>
+     * This will be called for each chunk of character data encountered.
+     * </p>
+     *
+     * @param elementText all text in an element, including whitespace.
+     */
+    private void characters(String elementText) throws JDOMException {
+        char[] c = elementText.toCharArray();
+        try {
+            contentHandler.characters(c, 0, c.length);
+        }
+        catch (SAXException se) {
+            throw new JDOMException("Exception in characters", se);
+        }
+    }
+
+    /**
+     * <p>
+     *  This will be called for each chunk of comment data encontered.
+     * </p>
+     *
+     * @param commentText all text in a comment, including whitespace.
+     */
+    private void comment(String commentText) throws JDOMException {
+        if (lexicalHandler != null) {
+            char[] c = commentText.toCharArray();
+            try {
+                lexicalHandler.comment(c, 0, c.length);
+            } catch (SAXException se) {
+                throw new JDOMException("Exception in comment", se);
+            }
+        }
+    }
+
+    /**
+     * <p>
+     * This will invoke the <code>ContentHandler.skippedEntity</code>
+     * callback when an entity reference is encountered.
+     * </p>
+     *
+     * @param entity <code>EntityRef</code>.
+     */
+    private void entityRef(EntityRef entity) throws JDOMException {
+        if (entity != null) {
+            try {
+                // No need to worry about appending a '%' character as
+                // we do not support parameter entities
+                contentHandler.skippedEntity(entity.getName());
+            }
+            catch (SAXException se) {
+                throw new JDOMException("Exception in entityRef", se);
+            }
+        }
+    }
+
+
+    /**
+     * <p>
+     * Appends a namespace declaration in the form of a xmlns attribute to
+     * an attribute list, crerating this latter if needed.
+     * </p>
+     *
+     * @param atts <code>AttributeImpl</code> where to add the attribute.
+     * @param ns <code>Namespace</code> the namespace to declare.
+     *
+     * @return <code>AttributeImpl</code> the updated attribute list.
+     */
+    private AttributesImpl addNsAttribute(AttributesImpl atts, Namespace ns) {
+        if (this.declareNamespaces) {
+            if (atts == null) {
+                atts = new AttributesImpl();
+            }
+
+            String prefix = ns.getPrefix();
+            if (prefix.equals("")) {
+                atts.addAttribute("",                        // namespace
+                                  "",                        // local name
+                                  "xmlns",                   // qualified name
+                                  "CDATA",                   // type
+                                  ns.getURI());              // value
+            }
+            else {
+                atts.addAttribute("",                        // namespace
+                                  "",                        // local name
+                                  "xmlns:" + ns.getPrefix(), // qualified name
+                                  "CDATA",                   // type
+                                  ns.getURI());              // value
+            }
+        }
+        return atts;
+    }
+
+    /**
+     * <p>
+     * Returns the SAX 2.0 attribute type string from the type of
+     * a JDOM Attribute.
+     * </p>
+     *
+     * @param type <code>int</code> the type of the JDOM attribute.
+     *
+     * @return <code>String</code> the SAX 2.0 attribute type string.
+     *
+     * @see org.jdom.Attribute#getAttributeType
+     * @see org.xml.sax.Attributes#getType
+     */
+    private static String getAttributeTypeName(int type) {
+        if ((type < 0) || (type >= attrTypeToNameMap.length)) {
+            type = Attribute.UNDECLARED_TYPE;
+        }
+        return attrTypeToNameMap[type];
+    }
+
+    /**
+     * <p>
+     * Notifies the registered {@link ErrorHandler SAX error handler}
+     * (if any) of an input processing error. The error handler can
+     * choose to absorb the error and let the processing continue.
+     * </p>
+     *
+     * @param exception <code>JDOMException</code> containing the
+     *                  error information; will be wrapped in a
+     *                  {@link SAXParseException} when reported to
+     *                  the SAX error handler.
+     *
+     * @throws JDOMException if no error handler has been registered
+     *                       or if the error handler fired a
+     *                       {@link SAXException}.
+     */
+    private void handleError(JDOMException exception) throws JDOMException {
+        if (errorHandler != null) {
+            try {
+                errorHandler.error(new SAXParseException(
+                                exception.getMessage(), null, exception));
+            }
+            catch (SAXException se) {
+               if (se.getException() instanceof JDOMException) {
+                   throw (JDOMException)(se.getException());
+               }
+               else {
+                   throw new JDOMException(se.getMessage(), se);
+               }
+            }
+        }
+        else {
+            throw exception;
+        }
+    }
+
+    /**
+     * <p>
+     * Creates a SAX XMLReader.
+     * </p>
+     *
+     * @return <code>XMLReader</code> a SAX2 parser.
+     *
+     * @throws Exception if no parser can be created.
+     */
+    protected XMLReader createParser() throws Exception {
+        XMLReader parser = null;
+
+        // Try using JAXP...
+        // Note we need JAXP 1.1, and if JAXP 1.0 is all that's
+        // available then the getXMLReader call fails and we skip
+        // to the hard coded default parser
+        try {
+            Class factoryClass =
+                    Class.forName("javax.xml.parsers.SAXParserFactory");
+
+            // factory = SAXParserFactory.newInstance();
+            Method newParserInstance =
+                    factoryClass.getMethod("newInstance", null);
+            Object factory = newParserInstance.invoke(null, null);
+
+            // jaxpParser = factory.newSAXParser();
+            Method newSAXParser = factoryClass.getMethod("newSAXParser", null);
+            Object jaxpParser   = newSAXParser.invoke(factory, null);
+
+            // parser = jaxpParser.getXMLReader();
+            Class parserClass = jaxpParser.getClass();
+            Method getXMLReader =
+                    parserClass.getMethod("getXMLReader", null);
+            parser = (XMLReader)getXMLReader.invoke(jaxpParser, null);
+        } catch (ClassNotFoundException e) {
+            //e.printStackTrace();
+        } catch (InvocationTargetException e) {
+            //e.printStackTrace();
+        } catch (NoSuchMethodException e) {
+            //e.printStackTrace();
+        } catch (IllegalAccessException e) {
+            //e.printStackTrace();
+        }
+
+        // Check to see if we got a parser yet, if not, try to use a
+        // hard coded default
+        if (parser == null) {
+            parser = XMLReaderFactory.createXMLReader(
+                                "org.apache.xerces.parsers.SAXParser");
+        }
+        return parser;
+    }
+
+    /**
+     * <p>
+     * This will create a SAX XMLReader capable of parsing a DTD and
+     * configure it so that the DTD parsing events are routed to the
+     * handlers registered onto this SAXOutputter.
+     * </p>
+     *
+     * @return <code>XMLReader</code> a SAX2 parser.
+     *
+     * @throws JDOMException if no parser can be created.
+     */
+    private XMLReader createDTDParser() throws JDOMException {
+        XMLReader parser = null;
+
+        // Get a parser instance
+        try
+        {
+            parser = createParser();
+        }
+        catch (Exception ex1) {
+           throw new JDOMException("Error in SAX parser allocation", ex1);
+        }
+
+        // Register handlers
+        if (this.getDTDHandler() != null) {
+            parser.setDTDHandler(this.getDTDHandler());
+        }
+        if (this.getEntityResolver() != null) {
+            parser.setEntityResolver(this.getEntityResolver());
+        }
+        if (this.getLexicalHandler() != null) {
+            try {
+                parser.setProperty(LEXICAL_HANDLER_SAX_PROPERTY,
+                                   this.getLexicalHandler());
+            }
+            catch (SAXException ex1) {
+                try {
+                    parser.setProperty(LEXICAL_HANDLER_ALT_PROPERTY,
+                                       this.getLexicalHandler());
+                } catch (SAXException ex2) {
+                    // Forget it!
+                }
+            }
+        }
+        if (this.getDeclHandler() != null) {
+            try {
+                parser.setProperty(DECL_HANDLER_SAX_PROPERTY,
+                                   this.getDeclHandler());
+            }
+            catch (SAXException ex1) {
+                try {
+                    parser.setProperty(DECL_HANDLER_ALT_PROPERTY,
+                                       this.getDeclHandler());
+                } catch (SAXException ex2) {
+                    // Forget it!
+                }
+            }
+        }
+
+        // Absorb errors as much as possible, per Laurent
+        parser.setErrorHandler(new DefaultHandler());
+
+        return parser;
+    }
+
+    /**
+     * Returns a JDOMLocator object referencing the node currently
+     * being processed by this outputter.  The returned object is a
+     * snapshot of the  location information and can thus safely be
+     * memorized for later use.
+     * <p>
+     * This method allows direct access to the location information
+     * maintained by SAXOutputter without requiring to implement
+     * <code>XMLFilter</code>. (In SAX, locators are only available
+     * though the <code>ContentHandler</code> interface).</p>
+     * <p>
+     * Note that location information is only available while
+     * SAXOutputter is outputting nodes. Hence this method should
+     * only be used by objects taking part in the output processing
+     * such as <code>ErrorHandler</code>s.
+     *
+     * @return a JDOMLocator object referencing the node currently
+     *         being processed or <code>null</code> if no output
+     *         operation is being performed.
+     */
+    public JDOMLocator getLocator() {
+        return (locator != null)? new JDOMLocator(locator): null;
+    }
+}