// You can redistribute this software and/or modify it under the terms of
// the Ozone Library License version 1 published by ozone-db.org.
//
// The original code and portions created by SMB are
// Copyright (C) 1997-@year@ by SMB GmbH. All rights reserved.
//
// $Id: SAXChunkConsumer.java,v 1.1 2001/12/18 10:31:31 per_nyfelt Exp $
package org.ozoneDB.xml.util;
import java.io.IOException;
import java.io.Serializable;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Text;
import org.w3c.dom.ProcessingInstruction;
import org.w3c.dom.EntityReference;
import org.w3c.dom.Comment;
import org.w3c.dom.CDATASection;
import org.xml.sax.SAXException;
import org.xml.sax.ContentHandler;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.Locator;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.AttributesImpl;
import org.ozoneDB.DxLib.DxHashMap;
import org.ozoneDB.DxLib.DxDeque;
import org.ozoneDB.OzoneObject;
/**
* Objects of this class can be used to convert a sequence of
* {@link SAXEventChunk}s back into a DOM tree or SAX events.
* WARNING: Don't reuse instances of this class. Create a new instance for each
* chunk set (chunks containing information about the same XML tree).
* @version $Revision: 1.1 $ $Date: 2001/12/18 10:31:31 $
* @author <a href="http://www.smb-tec.com">SMB</a>
*/
public final class SAXChunkConsumer implements ContentHandler, LexicalHandler, Serializable {
private final static boolean debug = false;
private final Node appendTo;
protected Node startNode = null;
protected Node currentNode = null;
protected final Document domFactory;
protected final boolean domLevel2;
protected final ContentHandler contentHandler;
protected final LexicalHandler lexicalHandler;
protected final ChunkInputStream chunkInput;
protected final CompiledXMLInputStream cxmlInput;
protected int processLevel;
protected ModifiableNodeList resultNodeList;
/**
* Use this chunk consumer to produce a DOM tree out of the consumed
* chunks.
* @param _node The node where the newly created DOM tree will be appended to
* @throws IllegalArgumentException if the given node was null
*/
public SAXChunkConsumer(Document domFactory, Node appendTo) throws IOException{
if (domFactory == null) {
throw new IllegalArgumentException("provided DOM factory node was null!");
}
this.contentHandler = this;
this.lexicalHandler = this;
this.appendTo = appendTo;
this.processLevel = 0;
this.resultNodeList = new ModifiableNodeList();
this.domFactory = domFactory;
// this.domLevel2 = domFactory.getImplementation().hasFeature("XML", "2.0");
this.domLevel2 = false;
this.chunkInput = new ChunkInputStream(null);
this.cxmlInput = new CompiledXMLInputStream(this.chunkInput);
}
/**
* Use this chunk consumer to produce SAX events out of the consumed chunks.
* @param _contentHandler the handler the SAX events will be send to
* @throws IllegalArgumentException if the given content handler was null
*/
public SAXChunkConsumer(ContentHandler contentHandler) throws IOException{
if (contentHandler == null) {
throw new IllegalArgumentException("provided SAX content handler was null");
}
this.contentHandler = contentHandler;
this.lexicalHandler = (contentHandler instanceof LexicalHandler)
? (LexicalHandler)contentHandler
: null;
this.appendTo = null;
this.chunkInput = new ChunkInputStream(null);
this.cxmlInput = new CompiledXMLInputStream(this.chunkInput);
this.domLevel2 = false;
this.domFactory = null;
}
/**
* Takes a chunk and produces corresponding SAX events
* @param _chunk the chunk containing definitions of events to be thrown
* @throws IllegalArgumentException if the given chunk was null
*/
public final void processChunk(byte[] chunkData) throws SAXException, IOException {
if (chunkData == null) {
throw new IllegalArgumentException("provided event chunk was null");
}
this.chunkInput.setBuffer(chunkData);
while (this.chunkInput.available() > 0) {
switch (cxmlInput.readEvent()) {
case CXMLContentHandler.START_DOCUMENT:
if (debug) {
System.out.println(this.getClass().getName() + ": startDocument()");
}
this.contentHandler.startDocument();
break;
case CXMLContentHandler.END_DOCUMENT:
this.contentHandler.endDocument();
return;
case CXMLContentHandler.START_PREFIX_MAPPING:
this.contentHandler.startPrefixMapping(cxmlInput.readString(), cxmlInput.readString());
break;
case CXMLContentHandler.END_PREFIX_MAPPING:
this.contentHandler.endPrefixMapping(cxmlInput.readString());
break;
case CXMLContentHandler.START_ELEMENT:
if (debug) {
System.out.println(this.getClass().getName() + ": startElement()");
}
int attributes = cxmlInput.readAttributes();
AttributesImpl atts = new AttributesImpl();
for (int i = 0; i < attributes; i++) {
// atts.addAttribute(cxmlInput.readString(), cxmlInput.readString(), cxmlInput.readString(),
// cxmlInput.readString(), cxmlInput.readString());
atts.addAttribute(cxmlInput.readString(), cxmlInput.readString(), cxmlInput.readString(),
cxmlInput.readString(), new String( cxmlInput.readChars() ) );
}
String namespace = cxmlInput.readString();
String localname = cxmlInput.readString();
String qname = cxmlInput.readString();
if (debug) {
System.out.println(this.getClass().getName() + "namespace=" + namespace + ", localname=" + localname + ", qname=" + qname);
}
this.contentHandler.startElement( namespace, localname, qname , atts);
break;
case CXMLContentHandler.END_ELEMENT:
this.contentHandler.endElement(cxmlInput.readString(), cxmlInput.readString(), cxmlInput.readString());
break;
case CXMLContentHandler.CHARACTERS:
char[] chars = cxmlInput.readChars();
this.contentHandler.characters(chars, 0, chars.length);
break;
case CXMLContentHandler.IGNORABLE_WHITESPACE:
char[] spaces = cxmlInput.readChars();
this.contentHandler.characters(spaces, 0, spaces.length);
break;
case CXMLContentHandler.PROCESSING_INSTRUCTION:
this.contentHandler.processingInstruction(cxmlInput.readString(), cxmlInput.readString());
break;
case CXMLContentHandler.COMMENT:
if (this.lexicalHandler != null) {
char[] comment = cxmlInput.readChars();
this.lexicalHandler.comment(comment, 0, comment.length);
}
break;
case CXMLContentHandler.START_CDATA:
if (this.lexicalHandler != null) {
this.lexicalHandler.startCDATA();
}
break;
case CXMLContentHandler.END_CDATA:
if (this.lexicalHandler != null) {
this.lexicalHandler.endCDATA();
}
break;
default:
throw new IOException("parsing error: event not supported: ");
}
}
}
/**
* @return the last created result node, if this consumer was initialized
* for DOM creation.
*/
public final Node getResultNode() {
return startNode;
}
/**
* @return the result node list, if this consumer was initialized for
* DOM creation.
*/
public final NodeList getResultNodeList() {
return resultNodeList;
}
//
// SAX content handler implemenation
//
/**
* Received notification of the beginning of the document.
*/
public final void startDocument() {
if (this.startNode == null) {
this.startNode = (this.appendTo != null) ? this.appendTo : this.domFactory;
}
if (debug) {
System.out.println(this.getClass().getName() + ": startDocument()");
}
if (this.startNode.getNodeType() != Node.DOCUMENT_NODE) {
throw new IllegalStateException("A document can't be appended to another node!");
}
if (this.startNode.hasChildNodes()) {
throw new RuntimeException("The given DOM document must not have children if a whole document shall be converted!");
}
if (this.processLevel != 0) {
throw new RuntimeException("startDocument event must not occur within other start-end-event pairs!");
}
this.resultNodeList.addNode(this.startNode);
this.currentNode = this.startNode;
this.processLevel++;
}
/**
* Received notification of the end of the document.
*/
public final void endDocument() {
if (debug) {
System.out.println(this.getClass().getName() + ": endDocument()");
}
this.currentNode = this.currentNode.getParentNode();
this.processLevel--;
}
/**
* Receive notification of the start of an element.
* Note: Namespace handling has to be revised.
*/
public final void startElement(String _namespaceURI, String _localName, String _rawName, Attributes _atts) {
if (debug) {
System.out.println(this.getClass().getName() + ": startElement(...)");
}
Element newElem;
if (domLevel2) {
newElem = domFactory.createElementNS(_namespaceURI, _rawName);
//add the attributes
for (int i = 0; i < _atts.getLength(); i++) {
newElem.setAttributeNS(_atts.getURI(i), _atts.getQName(i), _atts.getValue(i));
}
} else {
newElem = domFactory.createElement(_rawName);
//add the attributes
for (int i = 0; i < _atts.getLength(); i++) {
newElem.setAttribute(_atts.getQName(i), _atts.getValue(i));
}
}
if (this.processLevel == 0) {
this.resultNodeList.addNode(newElem);
this.startNode = newElem;
this.currentNode = newElem;
if (this.appendTo != null) {
this.appendTo.appendChild(newElem);
}
} else {
this.currentNode.appendChild(newElem);
this.currentNode = newElem;
}
this.processLevel++;
}
/**
* Receive notification of the end of an element.
*/
public final void endElement(String _namespaceURI, String _localName, String _rawName) {
if (debug) {
System.out.println(this.getClass().getName() + ": endElement(...)");
}
this.currentNode = this.currentNode.getParentNode();
this.processLevel--;
}
/**
* Begin the scope of a prefix-URI Namespace mapping.
*/
public final void startPrefixMapping(String _prefix, String _uri) {
return;
}
/**
* End the scope of a prefix-URI mapping.
*/
public final void endPrefixMapping(String prefix) {
// Ozone is not yet namespace aware, so we ignore the prefix mappings
}
/**
* Receive notification of character data inside an element.
*/
public final void characters(char[] ch, int start, int length) {
if ((this.currentNode != null)
&& (this.currentNode.getNodeType() == Node.CDATA_SECTION_NODE)) {
//we receive a CDATA section
StringBuffer chars = new StringBuffer();
chars.append(ch, start, length);
chars.append(this.currentNode.getNodeValue());
this.currentNode.setNodeValue(chars.toString());
} else {
//just normal characters
Text textNode = this.domFactory.createTextNode(new String(ch, start, length));
if (debug) {
System.out.println(this.getClass().getName() + ": characters(...)");
}
if (this.processLevel == 0) {
this.resultNodeList.addNode(textNode);
this.startNode = textNode;
this.currentNode = textNode;
if (this.appendTo != null) {
this.appendTo.appendChild(textNode);
}
} else {
this.currentNode.appendChild(textNode);
}
}
}
/**
* Receive notification of a processing instruction.
*/
public final void processingInstruction(String _target, String _data) {
ProcessingInstruction piNode = domFactory.createProcessingInstruction(_target, _data);
if (debug) {
System.out.println(this.getClass().getName() + ": processingInstruction(...)");
}
if (this.processLevel == 0) {
this.resultNodeList.addNode(piNode);
this.startNode = piNode;
this.currentNode = piNode;
if (this.appendTo != null) {
this.appendTo.appendChild(piNode);
}
} else {
this.currentNode.appendChild(piNode);
}
}
/**
* Receive notification of a skipped entity.
*/
public final void skippedEntity(java.lang.String _name) {
EntityReference erNode = domFactory.createEntityReference(_name);
if (debug) {
System.out.println(this.getClass().getName() + ": skippedEntity(...)");
}
if (this.processLevel == 0) {
this.resultNodeList.addNode(erNode);
this.startNode = erNode;
this.currentNode = erNode;
if (this.appendTo != null) {
this.appendTo.appendChild(erNode);
}
} else {
this.currentNode.appendChild(erNode);
}
}
/**
* Receive notification of ignorable whitespace in element content.
*/
public final void ignorableWhitespace(char[] _ch, int _start, int _length) {
Text whitespace = domFactory.createTextNode(new String(_ch, _start, _length));
if (debug) {
System.out.println(this.getClass().getName() + ": ignorableWhitespace(...)");
}
if (this.processLevel == 0) {
this.resultNodeList.addNode(whitespace);
this.startNode = whitespace;
this.currentNode = whitespace;
if (this.appendTo != null) {
this.appendTo.appendChild(whitespace);
}
} else {
this.currentNode.appendChild(whitespace);
}
}
/**
* Receive an object for locating the origin of SAX document events.
*/
public final void setDocumentLocator(Locator locator) {
// we don't care about the origin of the document
}
//
// SAX LexicalHandler implemenation
//
public void comment( char[] ch, int start, int length ) throws SAXException {
Comment commentNode = this.domFactory.createComment(new String(ch, start, length));
if (debug) {
System.out.println(this.getClass().getName() + ": comment(...)");
}
if (this.processLevel == 0) {
this.resultNodeList.addNode(commentNode);
this.startNode = commentNode;
this.currentNode = commentNode;
if (this.appendTo != null) {
this.appendTo.appendChild(commentNode);
}
} else {
this.currentNode.appendChild(commentNode);
}
}
public void startCDATA() throws SAXException {
CDATASection cdata = this.domFactory.createCDATASection("");
if (debug) {
System.out.println(this.getClass().getName() + ": startCDATA(...)");
}
if (this.processLevel == 0) {
this.resultNodeList.addNode(cdata);
this.startNode = cdata;
this.currentNode = cdata;
if (this.appendTo != null) {
this.appendTo.appendChild(cdata);
}
} else {
this.currentNode.appendChild(cdata);
this.currentNode = cdata;
}
this.processLevel++;
}
public void endCDATA() throws SAXException {
if (debug) {
System.out.println(this.getClass().getName() + ": endCDATA(...)");
}
this.currentNode = this.currentNode.getParentNode();
this.processLevel--;
}
public void startDTD(String name, String publicId, String systemId)
throws SAXException {
//FIXME(?)
//not handled
}
public void endDTD() throws SAXException {
//FIXME(?)
//not handled
}
public void startEntity(String name) throws SAXException {
//FIXME(?)
//not handled
}
public void endEntity(String name) throws SAXException {
//FIXME(?)
//not handled
}
}