Sunday 31 July 2011

Produce a SAX stream from a DOM Document

 
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* $Id: DOM2SAX.java 627367 2008-02-13 12:03:30Z maxberger $ */


import java.util.List;
import java.util.Map;
import java.util.Stack;

import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;

import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.helpers.AttributesImpl;

/**
 * Helper class that produces a SAX stream from a DOM Document.
 * <p>
 * Part of the code here copied and adapted from Apache Xalan-J, 
 * src/org/apache/xalan/xsltc/trax/DOM2SAX.java
 */
public class DOM2SAX {

    private static final String EMPTYSTRING = "";
    private static final String XMLNS_PREFIX = "xmlns";

    private ContentHandler contentHandler;
    private LexicalHandler lexicalHandler;
    
    private Map prefixes = new java.util.HashMap();
    
    /**
     * Main constructor
     @param handler the ContentHandler to send SAX events to
     */
    public DOM2SAX(ContentHandler handler) {
        this.contentHandler = handler;
        if (handler instanceof LexicalHandler) {
            this.lexicalHandler = (LexicalHandler)handler;
        }
    }
    
    /**
     * Writes the given document using the given ContentHandler.
     @param doc DOM document
     @param fragment if false no startDocument() and endDocument() calls are issued.
     @throws SAXException In case of a problem while writing XML
     */
    public void writeDocument(Document doc, boolean fragmentthrows SAXException {
        if (!fragment) {
            contentHandler.startDocument();
        }
        for (Node n = doc.getFirstChild(); n != null;
                n = n.getNextSibling()) {
            writeNode(n);
        }
        if (!fragment) {
            contentHandler.endDocument();
        }
    }

    /**
     * Begin the scope of namespace prefix. Forward the event to the SAX handler
     * only if the prefix is unknown or it is mapped to a different URI.
     */
    private boolean startPrefixMapping(String prefix, String uri)
            throws SAXException {
        boolean pushed = true;
        Stack uriStack = (Stack)prefixes.get(prefix);

        if (uriStack != null) {
            if (uriStack.isEmpty()) {
                contentHandler.startPrefixMapping(prefix, uri);
                uriStack.push(uri);
            else {
                final String lastUri = (StringuriStack.peek();
                if (!lastUri.equals(uri)) {
                    contentHandler.startPrefixMapping(prefix, uri);
                    uriStack.push(uri);
                else {
                    pushed = false;
                }
            }
        else {
            contentHandler.startPrefixMapping(prefix, uri);
            uriStack = new Stack();
            prefixes.put(prefix, uriStack);
            uriStack.push(uri);
        }
        return pushed;
    }

    /*
     * End the scope of a name prefix by popping it from the stack and passing
     * the event to the SAX Handler.
     */
    private void endPrefixMapping(String prefixthrows SAXException {
        final Stack uriStack = (Stack)prefixes.get(prefix);

        if (uriStack != null) {
            contentHandler.endPrefixMapping(prefix);
            uriStack.pop();
        }
    }

    /**
     * If the DOM was created using a DOM 1.0 API, the local name may be null.
     * If so, get the local name from the qualified name before generating the
     * SAX event.
     */
    private static String getLocalName(Node node) {
        final String localName = node.getLocalName();

        if (localName == null) {
            final String qname = node.getNodeName();
            final int col = qname.lastIndexOf(':');
            return (col > 0? qname.substring(col + 1: qname;
        }
        return localName;
    }

    /**
     * Writes a node using the given writer.
     @param node node to serialize
     @throws SAXException In case of a problem while writing XML
     */
    private void writeNode(Node node
                throws SAXException {
        if (node == null) {
            return;
        }

        switch (node.getNodeType()) {
        case Node.ATTRIBUTE_NODE: // handled by ELEMENT_NODE
        case Node.DOCUMENT_FRAGMENT_NODE:
        case Node.DOCUMENT_TYPE_NODE:
        case Node.ENTITY_NODE:
        case Node.ENTITY_REFERENCE_NODE:
        case Node.NOTATION_NODE:
            // These node types are ignored!!!
            break;
        case Node.CDATA_SECTION_NODE:
            final String cdata = node.getNodeValue();
            if (lexicalHandler != null) {
                lexicalHandler.startCDATA();
                contentHandler.characters(cdata.toCharArray()0, cdata.length());
                lexicalHandler.endCDATA();
            else {
                // in the case where there is no lex handler, we still
                // want the text of the cdate to make its way through.
                contentHandler.characters(cdata.toCharArray()0, cdata.length());
            }
            break;

        case Node.COMMENT_NODE: // should be handled!!!
            if (lexicalHandler != null) {
                final String value = node.getNodeValue();
                lexicalHandler.comment(value.toCharArray()0, value.length());
            }
            break;
        case Node.DOCUMENT_NODE:
            contentHandler.startDocument();
            Node next = node.getFirstChild();
            while (next != null) {
                writeNode(next);
                next = next.getNextSibling();
            }
            contentHandler.endDocument();
            break;

        case Node.ELEMENT_NODE:
            String prefix;
            List pushedPrefixes = new java.util.ArrayList();
            final AttributesImpl attrs = new AttributesImpl();
            final NamedNodeMap map = node.getAttributes();
            final int length = map.getLength();

            // Process all namespace declarations
            for (int i = 0; i < length; i++) {
                final Node attr = map.item(i);
                final String qnameAttr = attr.getNodeName();

                // Ignore everything but NS declarations here
                if (qnameAttr.startsWith(XMLNS_PREFIX)) {
                    final String uriAttr = attr.getNodeValue();
                    final int colon = qnameAttr.lastIndexOf(':');
                    prefix = (colon > 0? qnameAttr.substring(colon + 1)
                            : EMPTYSTRING;
                    if (startPrefixMapping(prefix, uriAttr)) {
                        pushedPrefixes.add(prefix);
                    }
                }
            }

            // Process all other attributes
            for (int i = 0; i < length; i++) {
                final Node attr = map.item(i);
                final String qnameAttr = attr.getNodeName();

                // Ignore NS declarations here
                if (!qnameAttr.startsWith(XMLNS_PREFIX)) {
                    final String uriAttr = attr.getNamespaceURI();

                    // Uri may be implicitly declared
                    if (uriAttr != null) {
                        final int colon = qnameAttr.lastIndexOf(':');
                        prefix = (colon > 0? qnameAttr.substring(0, colon)
                                : EMPTYSTRING;
                        if (startPrefixMapping(prefix, uriAttr)) {
                            pushedPrefixes.add(prefix);
                        }
                    }

                    // Add attribute to list
                    attrs.addAttribute(attr.getNamespaceURI(),
                            getLocalName(attr), qnameAttr, "CDATA", attr
                                    .getNodeValue());
                }
            }

            // Now process the element itself
            final String qname = node.getNodeName();
            final String uri = node.getNamespaceURI();
            final String localName = getLocalName(node);

            // Uri may be implicitly declared
            if (uri != null) {
                final int colon = qname.lastIndexOf(':');
                prefix = (colon > 0? qname.substring(0, colon: EMPTYSTRING;
                if (startPrefixMapping(prefix, uri)) {
                    pushedPrefixes.add(prefix);
                }
            }

            // Generate SAX event to start element
            contentHandler.startElement(uri, localName, qname, attrs);

            // Traverse all child nodes of the element (if any)
            next = node.getFirstChild();
            while (next != null) {
                writeNode(next);
                next = next.getNextSibling();
            }

            // Generate SAX event to close element
            contentHandler.endElement(uri, localName, qname);

            // Generate endPrefixMapping() for all pushed prefixes
            final int nPushedPrefixes = pushedPrefixes.size();
            for (int i = 0; i < nPushedPrefixes; i++) {
                endPrefixMapping((String)pushedPrefixes.get(i));
            }
            break;

        case Node.PROCESSING_INSTRUCTION_NODE:
            contentHandler.processingInstruction(node.getNodeName(), node.getNodeValue());
            break;

        case Node.TEXT_NODE:
            final String data = node.getNodeValue();
            contentHandler.characters(data.toCharArray()0, data.length());
            break;
        default:
            //nop
        }
    }

    
}

   
  

Sax to DOM converter

  
/*   Copyright 2004 The Apache Software Foundation
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *  limitations under the License.
 */

// Revised from xmlbeans

import org.w3c.dom.Node;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.ProcessingInstruction;
import org.w3c.dom.Comment;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.ContentHandler;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.ext.LexicalHandler;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.DocumentBuilderFactory;
import java.util.Stack;
import java.util.Vector;

public class Sax2Dom
        extends DefaultHandler
        implements ContentHandler, LexicalHandler
{
    public static final String EMPTYSTRING = "";
    public static final String XML_PREFIX = "xml";
    public static final String XMLNS_PREFIX = "xmlns";
    public static final String XMLNS_STRING = "xmlns:";
    public static final String XMLNS_URI = "http://www.w3.org/2000/xmlns/";

    private Node _root = null;
    private Document _document = null;
    private Stack _nodeStk = new Stack();
    private Vector _namespaceDecls = null;

    public Sax2Dom() throws ParserConfigurationException
    {
        final DocumentBuilderFactory factory =
                DocumentBuilderFactory.newInstance();
        _document = factory.newDocumentBuilder().newDocument();
        _root = _document;
    }

    public Sax2Dom(Node rootthrows ParserConfigurationException
    {
        _root = root;
        if (root instanceof Document)
        {
            _document = (Documentroot;
        }
        else if (root != null)
        {
            _document = root.getOwnerDocument();
        }
        else
        {
            final DocumentBuilderFactory factory =
                    DocumentBuilderFactory.newInstance();
            _document = factory.newDocumentBuilder().newDocument();
            _root = _document;
        }
    }

    public Node getDOM()
    {
        return _root;
    }

    public void characters(char[] ch, int start, int length)
    {
        final Node last = (Node_nodeStk.peek();

        // No text nodes can be children of root (DOM006 exception)
        if (last != _document)
        {
            final String text = new String(ch, start, length);
            last.appendChild(_document.createTextNode(text));
        }
    }

    public void startDocument()
    {
        _nodeStk.push(_root);
    }

    public void endDocument()
    {
        _nodeStk.pop();
    }

    public void startElement(String namespace, String localName, String qName,
                             Attributes attrs)
    {
        final Element tmp = (Element_document.createElementNS(namespace, qName);

        // Add namespace declarations first
        if (_namespaceDecls != null)
        {
            final int nDecls = _namespaceDecls.size();
            for (int i = 0; i < nDecls; i++)
            {
                final String prefix = (String_namespaceDecls.elementAt(i++);

                if (prefix == null || prefix.equals(EMPTYSTRING))
                {
                    tmp.setAttributeNS(XMLNS_URI, XMLNS_PREFIX,
                            (String_namespaceDecls.elementAt(i));
                }
                else
                {
                    tmp.setAttributeNS(XMLNS_URI, XMLNS_STRING + prefix,
                            (String_namespaceDecls.elementAt(i));
                }
            }
            _namespaceDecls.clear();
        }

        // Add attributes to element
        final int nattrs = attrs.getLength();
        for (int i = 0; i < nattrs; i++)
        {
            if (attrs.getLocalName(i== null)
            {
                tmp.setAttribute(attrs.getQName(i), attrs.getValue(i));
            }
            else
            {
                tmp.setAttributeNS(attrs.getURI(i), attrs.getQName(i),
                        attrs.getValue(i));
            }
        }

        // Append this new node onto current stack node
        Node last = (Node_nodeStk.peek();
        last.appendChild(tmp);

        // Push this node onto stack
        _nodeStk.push(tmp);
    }

    public void endElement(String namespace, String localName, String qName)
    {
        _nodeStk.pop();
    }

    public void startPrefixMapping(String prefix, String uri)
    {
        if (_namespaceDecls == null)
        {
            _namespaceDecls = new Vector(2);
        }
        _namespaceDecls.addElement(prefix);
        _namespaceDecls.addElement(uri);
    }

    public void endPrefixMapping(String prefix)
    {
        // do nothing
    }

    /**
     * This class is only used internally so this method should never
     * be called.
     */
    public void ignorableWhitespace(char[] ch, int start, int length)
    {
    }

    /**
     * adds processing instruction node to DOM.
     */
    public void processingInstruction(String target, String data)
    {
        final Node last = (Node_nodeStk.peek();
        ProcessingInstruction pi = _document.createProcessingInstruction(
                target, data);
        if (pi != nulllast.appendChild(pi);
    }

    /**
     * This class is only used internally so this method should never
     * be called.
     */
    public void setDocumentLocator(Locator locator)
    {
    }

    /**
     * This class is only used internally so this method should never
     * be called.
     */
    public void skippedEntity(String name)
    {
    }


    /**
     * Lexical Handler method to create comment node in DOM tree.
     */
    public void comment(char[] ch, int start, int length)
    {
        final Node last = (Node_nodeStk.peek();
        Comment comment = _document.createComment(new String(ch, start, length));
        if (comment != nulllast.appendChild(comment);
    }

    // Lexical Handler methods- not implemented
    public void startCDATA()
    {
    }

    public void endCDATA()
    {
    }

    public void startEntity(java.lang.String name)
    {
    }

    public void endEntity(String name)
    {
    }

    public void startDTD(String name, String publicId, String systemId)
            throws SAXException
    {
    }

    public void endDTD()
    {
    }
}