/*
* Copyright 2010 Outerthought bvba
*
* 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.
*/
package org.lilyproject.util.xml;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Map;
import org.w3c.dom.Element;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.helpers.AttributesImpl;
import org.xml.sax.helpers.NamespaceSupport;
/**
* Helper class for generating pretty-printed XML, with some higher-level methods
* than what SAX's ContentHandler provides.
*
* <p>Hint: for attributes, use LinkedHashMap if you want to maintain the order.
*/
public class XmlProducer {
private ContentHandler result;
private int indentLevel = 0;
private static final int INDENT = 2;
private Attributes EMPTY_ATTRS = new AttributesImpl();
private NamespaceSupport namespaceSupport = new NamespaceSupport();
private static ThreadLocal LOCAL = new ThreadLocal() {
@Override
protected Object initialValue() {
return SAXTransformerFactory.newInstance();
}
};
private static final char[] LINE_SEP = new char[] {'\n'};
private static int LINE_SEP_LENGTH = LINE_SEP.length;
public XmlProducer(OutputStream outputStream) throws SAXException, TransformerConfigurationException {
TransformerHandler serializer = getTransformerHandler();
serializer.getTransformer().setOutputProperty(OutputKeys.METHOD, "xml");
serializer.getTransformer().setOutputProperty(OutputKeys.ENCODING, "UTF-8");
serializer.setResult(new StreamResult(outputStream));
this.result = serializer;
result.startDocument();
newLine();
}
private static TransformerHandler getTransformerHandler() throws TransformerConfigurationException {
return ((SAXTransformerFactory)LOCAL.get()).newTransformerHandler();
}
public void flush() throws SAXException {
result.endDocument();
}
public void declarePrefix(String prefix, String uri) throws SAXException {
result.startPrefixMapping(prefix, uri);
namespaceSupport.declarePrefix(prefix, uri);
}
public void startElement(String name) throws SAXException {
startElement(name, (Map<String, String>)null);
}
public void startElement(String uri, String name) throws SAXException {
startElement(uri, name, null);
}
public void startElement(String name, Map<String, String> attrs) throws SAXException {
startElement(null, name, attrs);
}
public void startElement(String uri, String name, Map<String, String> attrs) throws SAXException {
outputSpaces(indentLevel * INDENT);
outputStartElement(uri, name, attrs);
newLine();
indentLevel++;
}
private void outputSpaces(int count) throws SAXException {
char[] spaces = spaces(count).toCharArray();
result.characters(spaces, 0, spaces.length);
}
public void endElement(String name) throws SAXException {
endElement(null, name);
}
public void endElement(String uri, String name) throws SAXException {
indentLevel--;
outputSpaces(indentLevel * INDENT);
closeElement(uri, name);
newLine();
}
private void outputStartElement(String uri, String name, Map<String, String> attrs) throws SAXException {
Attributes attributes;
if (attrs == null) {
attributes = EMPTY_ATTRS;
} else {
attributes = new MapAttributes(attrs);
}
if (uri == null) {
uri = "";
}
String prefix = getPrefixWithColon(uri);
result.startElement(uri, name, prefix + name, attributes);
namespaceSupport.pushContext();
}
private String getPrefixWithColon(String uri) {
if (uri.length() > 0) {
String prefix = namespaceSupport.getPrefix(uri);
if (prefix == null) {
return "";
} else {
return prefix + ":";
}
}
return "";
}
public void emptyElement(String name, Map<String, String> attrs) throws SAXException {
emptyElement(null, name, attrs);
}
public void emptyElement(String uri, String name, Map<String, String> attrs) throws SAXException {
outputSpaces(indentLevel * INDENT);
outputStartElement(uri, name, attrs);
closeElement(uri, name);
newLine();
}
public void simpleElement(String name, String value) throws SAXException {
simpleElement(null, name, value, null);
}
public void simpleElement(String uri, String name, String value) throws SAXException {
simpleElement(uri, name, value, null);
}
public void simpleElement(String name, String value, Map<String, String> attrs) throws SAXException {
simpleElement(null, name, value, attrs);
}
public void simpleElement(String uri, String name, String value, Map<String, String> attrs) throws SAXException {
outputSpaces(indentLevel * INDENT);
outputStartElement(uri, name, attrs);
result.characters(value.toCharArray(), 0, value.length());
closeElement(uri, name);
newLine();
}
public void closeElement(String uri, String name) throws SAXException {
namespaceSupport.popContext();
if (uri == null) {
uri = "";
}
String prefix = getPrefixWithColon(uri);
result.endElement(uri, name, prefix + name);
}
public void newLine() throws SAXException {
result.characters(LINE_SEP, 0, LINE_SEP_LENGTH);
}
public void embedXml(InputStream is) throws SAXException, ParserConfigurationException, IOException {
StripDocumentHandler handler = new StripDocumentHandler(result, (LexicalHandler)result);
XMLReader reader = LocalSAXParserFactory.newXmlReader();
reader.setContentHandler(handler);
reader.setProperty("http://xml.org/sax/properties/lexical-handler", handler);
reader.parse(new InputSource(is));
newLine();
}
public void embedXml(Element element) throws TransformerException, SAXException {
Transformer transformer = LocalTransformerFactory.get().newTransformer();
transformer.transform(new DOMSource(element), new SAXResult(new StripDocumentHandler(result)));
newLine();
}
static class MapAttributes implements Attributes {
private Map<String, String> attrs;
private Map.Entry<String, String>[] attrList;
MapAttributes(Map<String, String> attrs) {
this.attrs = attrs;
this.attrList = attrs.entrySet().toArray(new Map.Entry[0]);
}
@Override
public int getLength() {
return attrs.size();
}
@Override
public String getURI(int index) {
return "";
}
@Override
public String getLocalName(int index) {
return attrList[index].getKey();
}
@Override
public String getQName(int index) {
return attrList[index].getKey();
}
@Override
public String getType(int index) {
return "CDATA";
}
@Override
public String getValue(int index) {
return attrList[index].getValue();
}
@Override
public int getIndex(String uri, String localName) {
if (uri.length() != 0) {
return -1;
}
for (int i = 0; i < attrList.length; i++) {
if (attrList[i].getKey().equals(localName)) {
return i;
}
}
return -1;
}
@Override
public int getIndex(String qName) {
return getIndex(null, qName);
}
@Override
public String getType(String uri, String localName) {
return "CDATA";
}
@Override
public String getType(String qName) {
return "CDATA";
}
@Override
public String getValue(String uri, String localName) {
if (uri.length() != 0) {
return null;
}
return attrs.get(localName);
}
@Override
public String getValue(String qName) {
return attrs.get(qName);
}
}
private static String spaces(int count) {
if (count == 2) {
return " ";
} else if (count == 4) {
return " ";
} else if (count == 6) {
return " ";
} else if (count == 8) {
return " ";
} else {
StringBuilder spaces = new StringBuilder(count);
for (int i = 0; i < count; i++) {
spaces.append(' ');
}
return spaces.toString();
}
}
}