/* eXist Native XML Database
* Copyright (C) 2000-03, Wolfgang M. Meier (wolfgang@exist-db.org)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This library 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 Library General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* $Id$
*/
package org.exist.util.serializer;
import org.exist.dom.QName;
import org.exist.dom.StoredNode;
import org.exist.util.XMLString;
import org.exist.util.serializer.json.JSONWriter;
import org.w3c.dom.Document;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.helpers.NamespaceSupport;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.TransformerException;
import java.io.Writer;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
public class SAXSerializer implements ContentHandler, LexicalHandler, Receiver {
private final static Properties defaultProperties = new Properties();
static {
defaultProperties.setProperty(OutputKeys.ENCODING, "UTF-8");
defaultProperties.setProperty(OutputKeys.INDENT, "false");
}
private final static int XML_WRITER = 0;
private final static int XHTML_WRITER = 1;
private final static int TEXT_WRITER = 2;
private final static int JSON_WRITER = 3;
private final static int HTML5_WRITER = 4;
private XMLWriter writers[] = {
new IndentingXMLWriter(),
new XHTMLWriter(),
new TEXTWriter(),
new JSONWriter(),
new HTML5Writer()
};
protected XMLWriter receiver;
protected Properties outputProperties = defaultProperties;
protected NamespaceSupport nsSupport = new NamespaceSupport();
protected HashMap namespaceDecls = new HashMap();
protected HashMap optionalNamespaceDecls = new HashMap();
public SAXSerializer() {
super();
receiver = writers[XML_WRITER];
}
public SAXSerializer(Writer writer, Properties outputProperties) {
super();
setOutput(writer, outputProperties);
}
public void setOutput(Writer writer, Properties properties) {
if (properties == null)
outputProperties = defaultProperties;
else
outputProperties = properties;
String method = outputProperties.getProperty("method", "xml");
if ("xhtml".equalsIgnoreCase(method))
receiver = writers[XHTML_WRITER];
else if("text".equalsIgnoreCase(method))
receiver = writers[TEXT_WRITER];
else if ("json".equalsIgnoreCase(method))
receiver = writers[JSON_WRITER];
else if ("html5".equalsIgnoreCase(method))
receiver = writers[HTML5_WRITER];
else
receiver = writers[XML_WRITER];
receiver.setWriter(writer);
receiver.setOutputProperties(outputProperties);
}
public Writer getWriter() {
return receiver.writer;
}
public void setReceiver(XMLWriter receiver) {
this.receiver = receiver;
}
public void reset() {
nsSupport.reset();
namespaceDecls.clear();
optionalNamespaceDecls.clear();
for (int i = 0; i < writers.length; i++)
writers[i].reset();
}
/* (non-Javadoc)
* @see org.xml.sax.ContentHandler#setDocumentLocator(org.xml.sax.Locator)
*/
public void setDocumentLocator(Locator arg0) {
}
/* (non-Javadoc)
* @see org.xml.sax.ContentHandler#startDocument()
*/
public void startDocument() throws SAXException {
try
{
receiver.startDocument();
}
catch (TransformerException e) {
throw new SAXException(e.getMessage(), e);
}
}
/* (non-Javadoc)
* @see org.xml.sax.ContentHandler#endDocument()
*/
public void endDocument() throws SAXException {
try
{
receiver.endDocument();
}
catch (TransformerException e) {
throw new SAXException(e.getMessage(), e);
}
}
/* (non-Javadoc)
* @see org.xml.sax.ContentHandler#startPrefixMapping(java.lang.String, java.lang.String)
*/
public void startPrefixMapping(String prefix, String namespaceURI)
throws SAXException {
if(prefix == null)
prefix = "";
String ns = nsSupport.getURI(prefix);
if(ns == null || (!ns.equals(namespaceURI))) {
optionalNamespaceDecls.put(prefix, namespaceURI);
}
}
/* (non-Javadoc)
* @see org.xml.sax.ContentHandler#endPrefixMapping(java.lang.String)
*/
public void endPrefixMapping(String prefix) throws SAXException {
optionalNamespaceDecls.remove(prefix);
}
/* (non-Javadoc)
* @see org.xml.sax.ContentHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
*/
public void startElement(
String namespaceURI,
String localName,
String qname,
Attributes attribs)
throws SAXException {
try {
namespaceDecls.clear();
nsSupport.pushContext();
receiver.startElement(qname);
String elemPrefix = "";
int p = qname.indexOf(':');
if (p > 0)
elemPrefix = qname.substring(0, p);
if (namespaceURI == null)
namespaceURI = "";
if (nsSupport.getURI(elemPrefix) == null) {
namespaceDecls.put(elemPrefix, namespaceURI);
nsSupport.declarePrefix(elemPrefix, namespaceURI);
}
// check attributes for required namespace declarations
String attrName;
String uri;
if(attribs != null) {
for (int i = 0; i < attribs.getLength(); i++) {
attrName = attribs.getQName(i);
if (attrName.equals("xmlns")) {
if (nsSupport.getURI("") == null) {
uri = attribs.getValue(i);
namespaceDecls.put("", uri);
nsSupport.declarePrefix("", uri);
}
} else if (attrName.startsWith("xmlns:")) {
String prefix = attrName.substring(6);
if (nsSupport.getURI(prefix) == null) {
uri = attribs.getValue(i);
namespaceDecls.put(prefix, uri);
nsSupport.declarePrefix(prefix, uri);
}
} else if ((p = attrName.indexOf(':')) > 0) {
String prefix = attrName.substring(0, p);
uri = attribs.getURI(i);
if (nsSupport.getURI(prefix) == null) {
namespaceDecls.put(prefix, uri);
nsSupport.declarePrefix(prefix, uri);
}
}
}
}
Map.Entry nsEntry;
for (Iterator i = optionalNamespaceDecls.entrySet().iterator(); i.hasNext();) {
nsEntry = (Map.Entry) i.next();
String prefix = (String) nsEntry.getKey();
uri = (String) nsEntry.getValue();
receiver.namespace(prefix, uri);
nsSupport.declarePrefix(prefix, uri); //nsSupport.declarePrefix(prefix, namespaceURI);
}
// output all namespace declarations
for (Iterator i = namespaceDecls.entrySet().iterator(); i.hasNext(); ) {
nsEntry = (Map.Entry) i.next();
String prefix = (String) nsEntry.getKey();
uri = (String) nsEntry.getValue();
if(!optionalNamespaceDecls.containsKey(prefix)) {
receiver.namespace(prefix, uri);
}
}
//cancels current xmlns if relevant
if ("".equals(elemPrefix) && !namespaceURI.equals(receiver.getDefaultNamespace())) {
receiver.namespace("", namespaceURI);
nsSupport.declarePrefix("", namespaceURI);
}
optionalNamespaceDecls.clear();
// output attributes
for (int i = 0; i < attribs.getLength(); i++) {
if (!attribs.getQName(i).startsWith("xmlns"))
receiver.attribute(
attribs.getQName(i),
attribs.getValue(i));
}
} catch (TransformerException e) {
throw new SAXException(e.getMessage(), e);
}
}
/* (non-Javadoc)
* @see org.exist.util.serializer.Receiver#startElement(org.exist.dom.QName)
*/
public void startElement(QName qname, AttrList attribs) throws SAXException {
try {
namespaceDecls.clear();
nsSupport.pushContext();
receiver.startElement(qname);
String prefix = qname.getPrefix();
String namespaceURI = qname.getNamespaceURI();
if(prefix == null)
prefix = "";
if (namespaceURI == null)
namespaceURI = "";
if (nsSupport.getURI(prefix) == null) {
namespaceDecls.put(prefix, namespaceURI);
nsSupport.declarePrefix(prefix, namespaceURI);
}
// check attributes for required namespace declarations
QName attrQName;
String uri;
if(attribs != null) {
for (int i = 0; i < attribs.getLength(); i++) {
attrQName = attribs.getQName(i);
if (attrQName.getLocalName().equals("xmlns")) {
if (nsSupport.getURI("") == null) {
uri = attribs.getValue(i);
namespaceDecls.put("", uri);
nsSupport.declarePrefix("", uri);
}
} else if (attrQName.getPrefix() != null && attrQName.getPrefix().length() > 0) {
prefix = attrQName.getPrefix();
if(prefix.equals("xmlns:")) {
if (nsSupport.getURI(prefix) == null) {
uri = attribs.getValue(i);
prefix = attrQName.getLocalName();
namespaceDecls.put(prefix, uri);
nsSupport.declarePrefix(prefix, uri);
}
} else {
if (nsSupport.getURI(prefix) == null) {
uri = attrQName.getNamespaceURI();
namespaceDecls.put(prefix, uri);
nsSupport.declarePrefix(prefix, uri);
}
}
}
}
}
Map.Entry nsEntry;
String optPrefix;
for (Iterator i = optionalNamespaceDecls.entrySet().iterator(); i.hasNext();) {
nsEntry = (Map.Entry) i.next();
optPrefix = (String) nsEntry.getKey();
uri = (String) nsEntry.getValue();
receiver.namespace(optPrefix, uri);
nsSupport.declarePrefix(optPrefix, uri);
}
// output all namespace declarations
for (Iterator i = namespaceDecls.entrySet().iterator(); i.hasNext();) {
nsEntry = (Map.Entry) i.next();
optPrefix = (String) nsEntry.getKey();
if (optPrefix.equals("xmlns")) {
continue;
}
uri = (String) nsEntry.getValue();
if(!optionalNamespaceDecls.containsKey(optPrefix)) {
receiver.namespace(optPrefix, uri);
}
}
optionalNamespaceDecls.clear();
//cancels current xmlns if relevant
if ("".equals(prefix) && !namespaceURI.equals(receiver.getDefaultNamespace())) {
receiver.namespace("", namespaceURI);
nsSupport.declarePrefix("", namespaceURI);
}
if(attribs != null) {
// output attributes
for (int i = 0; i < attribs.getLength(); i++) {
if (!attribs.getQName(i).getLocalName().startsWith("xmlns"))
receiver.attribute(
attribs.getQName(i),
attribs.getValue(i));
}
}
} catch (TransformerException e) {
throw new SAXException(e.getMessage(), e);
}
}
/* (non-Javadoc)
* @see org.xml.sax.ContentHandler#endElement(java.lang.String, java.lang.String, java.lang.String)
*/
public void endElement(String namespaceURI, String localName, String qname)
throws SAXException {
try {
nsSupport.popContext();
receiver.endElement(qname);
receiver.setDefaultNamespace(nsSupport.getURI(""));
} catch (TransformerException e) {
throw new SAXException(e.getMessage(), e);
}
}
/* (non-Javadoc)
* @see org.exist.util.serializer.Receiver#endElement(org.exist.dom.QName)
*/
public void endElement(QName qname) throws SAXException {
try {
nsSupport.popContext();
receiver.endElement(qname);
receiver.setDefaultNamespace(nsSupport.getURI(""));
} catch (TransformerException e) {
throw new SAXException(e.getMessage(), e);
}
}
/* (non-Javadoc)
* @see org.exist.util.serializer.Receiver#attribute(org.exist.dom.QName, java.lang.String)
*/
public void attribute(QName qname, String value) throws SAXException {
// ignore namespace declaration attributes
if((qname.getPrefix() != null && qname.getPrefix().equals("xmlns")) ||
qname.getLocalName().equals("xmlns"))
return;
try {
receiver.attribute(qname, value);
} catch (TransformerException e) {
throw new SAXException(e.getMessage(), e);
}
}
/* (non-Javadoc)
* @see org.xml.sax.ContentHandler#characters(char[], int, int)
*/
public void characters(char[] ch, int start, int len) throws SAXException {
try {
receiver.characters(ch, start, len);
} catch (TransformerException e) {
throw new SAXException(e.getMessage(), e);
}
}
public void characters(CharSequence seq) throws SAXException {
try {
receiver.characters(seq);
} catch (TransformerException e) {
throw new SAXException(e.getMessage(), e);
}
}
/* (non-Javadoc)
* @see org.xml.sax.ContentHandler#ignorableWhitespace(char[], int, int)
*/
public void ignorableWhitespace(char[] ch, int start, int len)
throws SAXException {
try {
receiver.characters(ch, start, len);
} catch (TransformerException e) {
throw new SAXException(e.getMessage(), e);
}
}
/* (non-Javadoc)
* @see org.xml.sax.ContentHandler#processingInstruction(java.lang.String, java.lang.String)
*/
public void processingInstruction(String target, String data)
throws SAXException {
try {
receiver.processingInstruction(target, data);
} catch (TransformerException e) {
throw new SAXException(e.getMessage(), e);
}
}
public void cdataSection(char[] ch, int start, int len) throws SAXException {
try {
receiver.cdataSection(ch, start, len);
} catch (TransformerException e) {
throw new SAXException(e.getMessage(), e);
}
}
/* (non-Javadoc)
* @see org.xml.sax.ContentHandler#skippedEntity(java.lang.String)
*/
public void skippedEntity(String arg0) throws SAXException {
}
/* (non-Javadoc)
* @see org.xml.sax.ext.LexicalHandler#startDTD(java.lang.String, java.lang.String, java.lang.String)
*/
public void startDTD(String arg0, String arg1, String arg2)
throws SAXException {
}
/* (non-Javadoc)
* @see org.xml.sax.ext.LexicalHandler#endDTD()
*/
public void endDTD() throws SAXException {
}
public void documentType(String name, String publicId, String systemId)
throws SAXException {
try {
receiver.documentType(name, publicId, systemId);
} catch (TransformerException e) {
throw new SAXException(e.getMessage(), e);
}
}
public void highlightText(CharSequence seq) {
// not supported with this receiver
}
/* (non-Javadoc)
* @see org.xml.sax.ext.LexicalHandler#startEntity(java.lang.String)
*/
public void startEntity(String arg0) throws SAXException {
}
/* (non-Javadoc)
* @see org.xml.sax.ext.LexicalHandler#endEntity(java.lang.String)
*/
public void endEntity(String arg0) throws SAXException {
}
/* (non-Javadoc)
* @see org.xml.sax.ext.LexicalHandler#startCDATA()
*/
public void startCDATA() throws SAXException {
}
/* (non-Javadoc)
* @see org.xml.sax.ext.LexicalHandler#endCDATA()
*/
public void endCDATA() throws SAXException {
}
/* (non-Javadoc)
* @see org.xml.sax.ext.LexicalHandler#comment(char[], int, int)
*/
public void comment(char[] ch, int start, int len) throws SAXException {
try {
receiver.comment(new XMLString(ch, start, len));
} catch (TransformerException e) {
throw new SAXException(e.getMessage(), e);
}
}
public void setCurrentNode(StoredNode node) {
// just ignore.
}
public Document getDocument() {
//just ignore.
return null;
}
}