/*
* Copyright 1999-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.
*
* CVS $Id: SAXEventGenerator.java,v 1.28 2004/02/08 03:50:14 vgritsenko Exp $
*/
package org.apache.xindice.xml.sax;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xindice.util.ByteArrayInput;
import org.apache.xindice.xml.SymbolTable;
import org.apache.xindice.xml.XMLCompressedInput;
import org.apache.xindice.xml.dom.CompressedDocument;
import org.apache.xindice.xml.dom.DOMCompressor;
import org.apache.xindice.xml.dom.DocumentImpl;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.ContentHandler;
import org.xml.sax.DTDHandler;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.AttributesImpl;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* SAXEventGenerator
*
* @version CVS $Revision: 1.28 $, $Date: 2004/02/08 03:50:14 $
*/
public final class SAXEventGenerator implements XMLReader {
private static final Log log = LogFactory.getLog(SAXEventGenerator.class);
private static final int XMLNS_MAP_INCREMENT = 5;
/**
* This is a SAX feature that controls how namespaces are reported in SAX.
* In accordance with the SAX2 specification by default this feature is <em>on</em>.
*
* @see <a href="http://sax.sourceforge.net/?selected=namespaces">sax.sourceforge.net/?selected=namespaces</a>
*/
public static final String SAX_NAMESPACES_FEATURE
= "http://xml.org/sax/features/namespaces";
/**
* This is a SAX feature that controls how namespaces are reported in SAX.
* In accordance with the SAX2 specification by default this feature is <em>off</em>.
*
* @see <a href="http://sax.sourceforge.net/?selected=namespaces">sax.sourceforge.net/?selected=namespaces</a>
*/
public static final String SAX_NAMESPACE_PREFIXES_FEATURE
= "http://xml.org/sax/features/namespace-prefixes";
/*
* State of the SAX_NAMESPACES_FEATURE. True indicates namespace URIs and unprefixed local
* names for element and attribute names will be available.
*
* For SAX2 the default is true.
*/
private boolean hasSaxNamespaces = true;
/*
* State of the SAX_NAMESPACE_PREFIXES_FEATURE. True indicates XML 1.0 names (with prefixes)
* and attributes (including xmlns* attributes) will be available.
*
* For SAX2 the default is off.
*/
private boolean hasSaxNamespacesPrefixes;
private SymbolTable symbols;
private byte[] data;
private int pos;
private int len;
private Map properties = new HashMap();
private ContentHandler content;
private CompressionHandler comp;
private ErrorHandler errors;
private EntityResolver entities;
private DTDHandler dtd;
private boolean interrupt;
public SAXEventGenerator(SymbolTable symbols, byte[] data) {
this(symbols, data, 0, data.length);
}
public SAXEventGenerator(SymbolTable symbols, byte[] data, int pos, int len) {
this.symbols = symbols;
this.data = data;
this.pos = pos;
this.len = len;
}
public SAXEventGenerator(Document doc) {
try {
if (doc instanceof CompressedDocument) {
CompressedDocument cDoc = new DocumentImpl(doc);
symbols = cDoc.getSymbols();
data = cDoc.getDataBytes();
pos = cDoc.getDataPos();
len = cDoc.getDataLen();
} else {
symbols = new SymbolTable();
data = DOMCompressor.Compress(doc, symbols);
pos = 0;
len = data.length;
}
} catch (Exception e) {
// This shouldn't happen
if (log.isErrorEnabled()) {
log.error("No message", e);
}
}
}
/**
* Return SAX feature setting for the SAX generated by this class.
*
* @param name feature name. Supported features are the standard SAX namespace
* reporting features as documented in the specification.
* @return whether feature is turned on
* @see <a href="http://sax.sourceforge.net/?selected=namespaces">sax.sourceforge.net/?selected=namespaces</a>
*/
public boolean getFeature(String name) throws SAXNotRecognizedException, SAXNotSupportedException {
if (SAX_NAMESPACES_FEATURE.equals(name)) {
return hasSaxNamespaces;
} else if (SAX_NAMESPACE_PREFIXES_FEATURE.equals(name)) {
return hasSaxNamespacesPrefixes;
} else {
return false;
}
}
/**
* Set SAX feature for the SAX generated by this class.
*
* @param name feature name. Supported features are the standard SAX namespace
* reporting features as documented in the specification.
* @param value specified the status of the feature.
* @see <a href="http://sax.sourceforge.net/?selected=namespaces">sax.sourceforge.net/?selected=namespaces</a>
*/
public void setFeature(String name, boolean value) throws SAXNotRecognizedException, SAXNotSupportedException {
if (SAX_NAMESPACES_FEATURE.equals(name)) {
hasSaxNamespaces = value;
} else if (SAX_NAMESPACE_PREFIXES_FEATURE.equals(name)) {
hasSaxNamespacesPrefixes = value;
}
}
public Object getProperty(String name) throws SAXNotRecognizedException, SAXNotSupportedException {
return properties.get(name);
}
public void setProperty(String name, Object value) throws SAXNotRecognizedException, SAXNotSupportedException {
properties.put(name, value);
if (name.equals(CompressionHandler.HANDLER)
&& value instanceof CompressionHandler) {
comp = (CompressionHandler) value;
}
}
public void setEntityResolver(EntityResolver resolver) {
entities = resolver;
}
public EntityResolver getEntityResolver() {
return entities;
}
public void setDTDHandler(DTDHandler handler) {
dtd = handler;
}
public DTDHandler getDTDHandler() {
return dtd;
}
public void setContentHandler(ContentHandler handler) {
content = handler;
}
public ContentHandler getContentHandler() {
return content;
}
public void setErrorHandler(ErrorHandler handler) {
errors = handler;
}
public ErrorHandler getErrorHandler() {
return errors;
}
public void parse(InputSource input) throws IOException, SAXException {
}
public void parse(String systemId) throws IOException, SAXException {
}
private final String getLocalName(String qname) {
int idx = qname.indexOf(":");
if (idx != -1) {
return qname.substring(idx + 1);
} else {
return qname;
}
}
private final boolean isNSAttr(final String qName) {
return (("xmlns".equals(qName)) || qName.startsWith("xmlns:"));
}
public boolean processContainer(boolean element, int pos, int len) throws IOException, SAXException {
ByteArrayInput bis = new ByteArrayInput(data, pos, len);
XMLCompressedInput in = new XMLCompressedInput(bis, symbols);
String elemName = null;
String localName = null;
String nsURI = null;
String[] mappedPrefixes = null;
int nsMapCount = 0;
if (element) {
in.readSignature();
in.readContentSize();
short elemSymbol = in.readShort();
elemName = symbols.getName(elemSymbol);
localName = getLocalName(elemName);
nsURI = symbols.getNamespaceURI(elemSymbol);
int attrCount = in.readAttributeCount();
AttributesImpl attrs = new AttributesImpl();
for (int i = 0; i < attrCount; i++) {
short symbol = in.readShort();
short strLen = in.readShort();
byte[] b = new byte[strLen];
in.read(b);
String attrName = symbols.getName(symbol);
String attrURI = symbols.getNamespaceURI(symbol);
String lclName = getLocalName(attrName);
String attrValue = new String(b, "UTF8");
// look for and buffer newly mapped namespace prefixes
if (isNSAttr(attrName)) {
// create the buffer if needed
if (mappedPrefixes == null) {
mappedPrefixes = new String[XMLNS_MAP_INCREMENT];
}
// check the buffer's capacity
if (nsMapCount >= mappedPrefixes.length) {
String[] newBuf = new String[mappedPrefixes.length + XMLNS_MAP_INCREMENT];
System.arraycopy(mappedPrefixes, 0, newBuf, 0, mappedPrefixes.length);
mappedPrefixes = newBuf;
}
// The prefix is the attr "local name", unless this is the
// default namespace, in which case the prefix is the empty
// string
String prefix = ("xmlns".equals(attrName) ? "" : lclName);
// Prefix mappings MAY always be reported, regardless of
// feature settings.
content.startPrefixMapping(prefix, attrValue);
mappedPrefixes[nsMapCount++] = prefix;
if (hasSaxNamespacesPrefixes) {
// According to SAX, the local name should be EMPTY
attrs.addAttribute("", "", attrName, "CDATA", attrValue);
}
} else {
// Regular attribute
attrs.addAttribute(attrURI != null ? attrURI : "",
lclName, attrName, "", attrValue);
}
}
if (comp != null) {
comp.symbolID(elemSymbol);
comp.dataLocation(pos, len);
}
content.startElement(nsURI != null ? nsURI : "", localName, elemName, attrs);
} else {
in.readInt();
}
while (!interrupt && bis.available() > 0) {
pos = bis.getPos();
in.readSignature(); // Read signature
len = in.readContentSize();
if (len == 0) {
len = 1;
}
int type = in.getNodeType();
switch (type) {
case Node.ELEMENT_NODE:
processContainer(true, pos, len);
break;
case Node.TEXT_NODE:
case Node.PROCESSING_INSTRUCTION_NODE:
case Node.CDATA_SECTION_NODE:
case Node.COMMENT_NODE:
{
ByteArrayInput tbis = new ByteArrayInput(data, pos, len);
XMLCompressedInput tin = new XMLCompressedInput(tbis, symbols);
tin.readSignature(); // Skip The Signature
if (type == Node.TEXT_NODE) {
tin.readContentSize();
} else {
tin.readInt();
}
byte[] buf = new byte[tbis.available()];
tin.read(buf);
String value = new String(buf, "UTF-8");
switch (type) {
case Node.TEXT_NODE:
char[] c = value.toCharArray();
content.characters(c, 0, c.length);
break;
case Node.PROCESSING_INSTRUCTION_NODE:
int i = value.indexOf(' ');
content.processingInstruction(value.substring(0, i), value.substring(i + 1));
break;
case Node.CDATA_SECTION_NODE:
case Node.COMMENT_NODE:
// TODO: This
break;
default:
if (log.isWarnEnabled()) {
log.warn("invalid type : " + type);
}
}
break;
}
case Node.ENTITY_REFERENCE_NODE:
// TODO: This
break;
case Node.NOTATION_NODE:
break;
default:
if (log.isWarnEnabled()) {
log.warn("invalid node type : " + type);
}
}
bis.setPos(pos);
bis.skip(len);
}
if (element && !interrupt) {
// First, close element ...
content.endElement(nsURI != null ? nsURI : "", localName, elemName);
// ... and then any mapped NS prefixes
for (int i = 0; i < nsMapCount; i++) {
content.endPrefixMapping(mappedPrefixes[i]);
}
}
return !interrupt;
}
public boolean start() throws IOException, SAXException {
if (comp != null) {
comp.symbols(symbols);
comp.dataBytes(data);
}
content.startDocument();
boolean result = processContainer(false, pos, len);
if (result) {
content.endDocument();
}
return result;
}
/*public boolean start() {
try {
return processDocument();
}
catch ( Exception e ) {
org.apache.xindice.Debug.printStackTrace(e);
throw(e);
return false;
}
}*/
public void stop() {
interrupt = true;
}
}