/******************************************************************************* * Copyright 2010 Simon Mieth * * 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.kabeja.xml; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.zip.GZIPOutputStream; import org.xml.sax.Attributes; import org.xml.sax.Locator; import org.xml.sax.SAXException; /** * <p> * This outputs a SAXStream to an OutputStream with the given encoding or * otherwise with the default encoding (utf-8). * </p> * <p> * <b>Note: </b> Not all features are implemented, so if you use this with other * SAXStreams others then the Kabeja-SAXStream you will get broken * XML-Documents. * </p> * * @author <a href="mailto:simon.mieth@gmx.de">Simon Mieth</a> * */ public class SAXPrettyOutputter extends AbstractSAXSerializer implements SAXSerializer { public static final String DEFAULT_ENCODING = "UTF-8"; public static final String SUFFIX = "svg"; public static final String SUFFIX_GZIP = "svgz"; public static final String MIMETYPE = "text/svg"; public static final String PROPERTY_ENCODING = "encoding"; public static final String PROPERTY_GZIP = "gzip"; private OutputStreamWriter out; private String encoding; private String dtd; private int indent = 0; private boolean parent = false; private ArrayList textContentList = new ArrayList(); protected HashMap rootxmlns = new HashMap(); protected boolean gzip = false; public SAXPrettyOutputter(OutputStream output, String encoding) { this.encoding = encoding; this.setOutput(output); } /** * */ public SAXPrettyOutputter(OutputStream out) { this(out, DEFAULT_ENCODING); } public SAXPrettyOutputter() { this.encoding = DEFAULT_ENCODING; } public void characters(char[] ch, int start, int length) throws SAXException { try { if (length > 0) { if (parent) { this.out.write(">"); parent = false; } char[] enc = encodeXML(new String(ch, 0, length)).toCharArray(); this.out.write(enc, start, enc.length); // textNode in this context textContentList.set(textContentList.size() - 1, new Boolean(true)); } } catch (IOException e) { e.printStackTrace(); } } public void endDocument() throws SAXException { try { this.out.flush(); this.out.close(); textContentList.clear(); } catch (IOException e) { e.printStackTrace(); } } public void endElement(String namespaceURI, String localName, String qName) throws SAXException { try { if (parent) { this.out.write("/>"); } else { // check for textNodes in this context Boolean b = (Boolean) textContentList.remove(textContentList.size() - 1); if (b.booleanValue()) { this.out.write("</" + qName + ">"); } else { // there was no textNode we can create a new line this.out.write('\n'); indentOutput(indent); this.out.write("</" + qName + ">"); } } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } indent--; parent = false; } public void endPrefixMapping(String prefix) throws SAXException { } public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { } public void processingInstruction(String target, String data) throws SAXException { } public void setDocumentLocator(Locator locator) { } public void skippedEntity(String name) throws SAXException { } public void startDocument() throws SAXException { indent = 0; try { this.out.write("<?xml version=\"1.0\" encoding=\"" + encoding + "\" ?>"); if (this.dtd != null) { this.out.write("\n<!DOCTYPE " + dtd + ">"); } } catch (IOException e) { e.printStackTrace(); } } public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException { this.indent++; try { if (this.parent) { // we are nested this.out.write(">"); } else { this.parent = true; } // first create a new line this.out.write('\n'); // indent the line this.indentOutput(indent); // the element this.out.write("<" + qName); int attrCount = atts.getLength(); for (int i = 0; i < attrCount; i++) { //we need a white space between the //attributes this.indentOutput(1); String uri = atts.getURI(i); String qname = atts.getQName(i); // if (uri.length() > 0) { // String prefix = qname.substring(0, qname.indexOf(':')); // out // .write(" xmlns:" + prefix + "=\"" + uri // + "\" "); // } String value = atts.getValue(i); if(value == null){ value=""; } this.out.write(qname + "=\"" + encodeXML(atts.getValue(i)) + "\""); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } // no text in this context now this.textContentList.add(Boolean.valueOf(false)); } public void startPrefixMapping(String prefix, String uri) throws SAXException { } /** * Indent the output * * @param indentSize */ private void indentOutput(int indentSize) { try { for (int i = 0; i < indentSize; i++) { this.out.write(' '); } } catch (IOException e) { e.printStackTrace(); } } public static String encodeXML(String text) { int length = text.length(); StringBuffer work = new StringBuffer(length); for (int i = 0; i < length; i++) { char c = text.charAt(i); if (c == '&') { work.append("&"); } else if (c == '<') { work.append("<"); } else if (c == '>') { work.append(">"); } else if (!Character.isIdentifierIgnorable(c)) { work.append(c); } } return work.toString(); } public void setDTD(String dtd) { this.dtd = dtd; } protected void queryXMLNS(Attributes atts) { for (int i = 0; i < atts.getLength(); i++) { String qname = atts.getQName(i); if (qname.startsWith("xmlns:")) { String prefix = atts.getLocalName(i); String uri = atts.getValue(i); rootxmlns.put(uri, prefix); } } } /* * (non-Javadoc) * * @see org.kabeja.xml.SAXSerializer#getMimeType() */ public String getMimeType() { return MIMETYPE; } /* * (non-Javadoc) * * @see org.kabeja.xml.SAXSerializer#getSuffix() */ public String getSuffix() { if (gzip) { return SUFFIX_GZIP; } else { return SUFFIX; } } /* * (non-Javadoc) * * @see org.kabeja.xml.SAXSerializer#setOutput(java.io.OutputStream) */ public void setOutput(OutputStream out) { OutputStream bout = null; try { if (gzip) { bout = new BufferedOutputStream(new GZIPOutputStream(out)); } else { bout = new BufferedOutputStream(out); } this.out = new OutputStreamWriter(bout, this.encoding); } catch (Exception e) { e.printStackTrace(); } } /* * (non-Javadoc) * * @see org.kabeja.xml.SAXSerializer#setProperties(java.util.Map) */ public void setProperties(Map properties) { this.properties = properties; if (properties.containsKey(PROPERTY_ENCODING)) { this.encoding = (String) properties.get(PROPERTY_ENCODING); } if (properties.containsKey(PROPERTY_GZIP)) { this.gzip = Boolean.parseBoolean((String) properties.get( PROPERTY_GZIP)); } } }