/* * eXist Open Source Native XML Database * Copyright (C) 2001-06 Wolfgang M. Meier * wolfgang@exist-db.org * http://exist.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * $Id$ */ package org.exist.util.serializer; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Stack; import org.exist.dom.QName; import org.exist.memtree.ReferenceNode; import org.w3c.dom.Attr; import org.w3c.dom.CharacterData; import org.w3c.dom.Comment; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.ProcessingInstruction; import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; import org.xml.sax.ext.LexicalHandler; import org.xml.sax.helpers.AttributesImpl; import org.xml.sax.helpers.NamespaceSupport; /** * General purpose class to stream a DOM node to SAX. * * @author Wolfgang Meier (wolfgang@exist-db.org) */ public class DOMStreamer { private ContentHandler contentHandler = null; private LexicalHandler lexicalHandler = null; private NamespaceSupport nsSupport = new NamespaceSupport(); private HashMap namespaceDecls = new HashMap(); private Stack stack = new Stack(); public DOMStreamer() { super(); } public DOMStreamer(ContentHandler contentHandler, LexicalHandler lexicalHandler) { this.contentHandler = contentHandler; this.lexicalHandler = lexicalHandler; } public void setContentHandler(ContentHandler handler) { contentHandler = handler; } public void setLexicalHandler(LexicalHandler handler) { lexicalHandler = handler; } /** * Reset internal state for reuse. Registered handlers will be set * to null. * */ public void reset() { nsSupport.reset(); namespaceDecls.clear(); stack.clear(); contentHandler = null; lexicalHandler = null; } /** * Serialize the given node and all its descendants to SAX. * * @param node * @throws SAXException */ public void serialize(Node node) throws SAXException { serialize(node, false); } /** * Serialize the given node and all its descendants to SAX. If * callDocumentEvents is set to false, startDocument/endDocument * events will not be fired. * * @param node * @param callDocumentEvents * @throws SAXException */ public void serialize(Node node, boolean callDocumentEvents) throws SAXException { if(callDocumentEvents) contentHandler.startDocument(); Node top = node; while (node != null) { startNode(node); Node nextNode = node.getFirstChild(); //TODO : make it happy if (node instanceof ReferenceNode) nextNode = null; while (nextNode == null) { endNode(node); if (top != null && top.equals(node)) break; nextNode = node.getNextSibling(); if (nextNode == null) { node = node.getParentNode(); if (node == null || (top != null && top.equals(node))) { endNode(node); nextNode = null; break; } } } node = nextNode; } if(callDocumentEvents) contentHandler.endDocument(); } protected void startNode(Node node) throws SAXException { String cdata; switch (node.getNodeType()) { case Node.DOCUMENT_NODE : case Node.DOCUMENT_FRAGMENT_NODE : break; case Node.ELEMENT_NODE : namespaceDecls.clear(); nsSupport.pushContext(); String uri = node.getNamespaceURI(); String prefix = node.getPrefix(); if (uri == null) uri = ""; if (prefix == null) prefix = ""; if (nsSupport.getURI(prefix) == null) { namespaceDecls.put(prefix, uri); nsSupport.declarePrefix(prefix, uri); } // check attributes for required namespace declarations NamedNodeMap attrs = node.getAttributes(); Attr nextAttr; String attrName; for (int i = 0; i < attrs.getLength(); i++) { nextAttr = (Attr) attrs.item(i); attrName = nextAttr.getName(); if (attrName.equals("xmlns")) { if (nsSupport.getURI("") == null) { uri = nextAttr.getValue(); namespaceDecls.put("", uri); nsSupport.declarePrefix("", uri); } } else if (attrName.startsWith("xmlns:")) { prefix = attrName.substring(6); if (nsSupport.getURI(prefix) == null) { uri = nextAttr.getValue(); namespaceDecls.put(prefix, uri); nsSupport.declarePrefix(prefix, uri); } } else if (attrName.indexOf(':') > 0) { prefix = nextAttr.getPrefix(); uri = nextAttr.getNamespaceURI(); if (nsSupport.getURI(prefix) == null) { namespaceDecls.put(prefix, uri); nsSupport.declarePrefix(prefix, uri); } } } ElementInfo info = new ElementInfo(node); String[] declaredPrefixes = null; if(namespaceDecls.size() > 0) declaredPrefixes = new String[namespaceDecls.size()]; // output all namespace declarations Map.Entry nsEntry; int j = 0; for (Iterator i = namespaceDecls.entrySet().iterator(); i.hasNext(); j++) { nsEntry = (Map.Entry) i.next(); declaredPrefixes[j] = (String) nsEntry.getKey(); contentHandler.startPrefixMapping( declaredPrefixes[j], (String) nsEntry.getValue()); } info.prefixes = declaredPrefixes; stack.push(info); // output attributes AttributesImpl saxAttrs = new AttributesImpl(); String attrNS, attrLocalName; for (int i = 0; i < attrs.getLength(); i++) { nextAttr = (Attr) attrs.item(i); attrNS = nextAttr.getNamespaceURI(); if(attrNS == null) attrNS = ""; attrLocalName = nextAttr.getLocalName(); if(attrLocalName == null) attrLocalName = QName.extractLocalName(nextAttr.getNodeName()); saxAttrs.addAttribute( attrNS, attrLocalName, nextAttr.getNodeName(), "CDATA", nextAttr.getValue()); } String localName = node.getLocalName(); if(localName == null) localName = QName.extractLocalName(node.getNodeName()); contentHandler.startElement(node.getNamespaceURI(), localName, node.getNodeName(), saxAttrs); break; case Node.TEXT_NODE : cdata = ((CharacterData) node).getData(); contentHandler.characters(cdata.toCharArray(), 0, cdata.length()); break; case Node.CDATA_SECTION_NODE : cdata = ((CharacterData) node).getData(); if(lexicalHandler != null) lexicalHandler.startCDATA(); contentHandler.characters(cdata.toCharArray(), 0, cdata.length()); if(lexicalHandler != null) lexicalHandler.endCDATA(); break; case Node.ATTRIBUTE_NODE : break; case Node.PROCESSING_INSTRUCTION_NODE : contentHandler.processingInstruction( ((ProcessingInstruction) node).getTarget(), ((ProcessingInstruction) node).getData()); break; case Node.COMMENT_NODE : if(lexicalHandler != null) { cdata = ((Comment) node).getData(); lexicalHandler.comment(cdata.toCharArray(), 0, cdata.length()); } break; default : //TODO : what kind of default here ? -pb System.out.println("Found node: " + node.getNodeType()); break; } } protected void endNode(Node node) throws SAXException { if (node == null) return; if (node.getNodeType() == Node.ELEMENT_NODE) { ElementInfo info = (ElementInfo)stack.pop(); nsSupport.popContext(); contentHandler.endElement(node.getNamespaceURI(), node.getLocalName(), node.getNodeName()); if(info.prefixes != null) { for(int i = 0; i < info.prefixes.length; i++) { contentHandler.endPrefixMapping(info.prefixes[i]); } } } } private class ElementInfo { Node element; String[] prefixes = null; public ElementInfo(Node element) { this.element = element; } } }