package net.contrapunctus.rngzip.util; import java.io.OutputStream; import java.io.PrintStream; import java.io.UnsupportedEncodingException; import java.util.LinkedList; import java.util.Stack; import org.xml.sax.Attributes; import org.xml.sax.ContentHandler; import org.xml.sax.Locator; import org.xml.sax.helpers.AttributesImpl; public class PrettyXMLWriter implements ContentHandler { private PrintStream out; private int tab; private Stack<LinkedList<Node>> stack; private abstract class Node { abstract void write(int depth); boolean isText() { return false; } } // class Node private class Element extends Node { String qname; AttributesImpl atts; LinkedList<Node> children; Element(String qn, Attributes at) { qname = qn; atts = new AttributesImpl(at); // must copy children = new LinkedList<Node>(); } void write(int depth) { indent(depth); out.print('<'); out.print(qname); SimpleXMLWriter.outputAttrs(out, atts); if(children.isEmpty()) { out.print("/>"); } else { out.print('>'); int d = adjust(depth); for(Node n : children) { n.write(d); } if( d >= 0 ) indent(depth); out.print("</"); out.print(qname); out.print('>'); } } void indent(int depth) { if(depth >= 0) { out.println(); for(int i = 0; i < depth*tab; i++) { out.print(' '); } } } int adjust(int depth) { for(Node n : children) { if(n.isText()) return -1; } return depth+1; } } // class Element private class Text extends Node { char[] ch; int start, len; Text(char[] ch, int start, int len) { this.ch = ch; this.start = start; this.len = len; } void write(int depth) { SimpleXMLWriter.quotedOutput(out, ch, start, len, false); } boolean isText() { return true; } } // class Text public PrettyXMLWriter(OutputStream os, int tab) { try { this.out = new PrintStream(os, false, "UTF-8"); } catch(UnsupportedEncodingException x) { assert false : x; // all implementations must support UTF-8 } this.tab = tab; } public PrettyXMLWriter(PrintStream out, int tab) { this.out = out; this.tab = tab; } // Receive notification of the beginning of a document. public void startDocument() { stack = new Stack<LinkedList<Node>>(); stack.add(new LinkedList<Node>()); } // Receive notification of the end of a document. public void endDocument() { out.print("<?xml version=\"1.1\" encoding=\"UTF-8\"?>"); LinkedList<Node> list = stack.pop(); assert stack.empty(); assert list.size() == 1; list.getFirst().write(0); out.println(); out.flush(); } // Receive notification of character data. public void characters(char[] ch, int start, int len) { stack.peek().add(new Text(ch, start, len)); } // Receive notification of the beginning of an element. public void startElement(String uri, String localName, String qName, Attributes atts) { Element e = new Element(qName, atts); stack.peek().add(e); stack.push(e.children); } // Receive notification of the end of an element. public void endElement(String uri, String localName, String qName) { stack.pop(); } // Begin the scope of a prefix-URI Namespace mapping. public void startPrefixMapping(String prefix, String uri) { throw new UnsupportedOperationException ("SimpleXMLWriter.startPrefixMapping"); } // End the scope of a prefix-URI mapping. public void endPrefixMapping(String prefix) { // error already issued in startPrefixMapping } // Receive notification of ignorable whitespace in element content. public void ignorableWhitespace(char[] ch, int start, int length) { // ignore it } // Receive notification of a processing instruction. public void processingInstruction(String target, String data) { throw new UnsupportedOperationException ("SimpleXMLWriter.processingInstruction"); } // Receive an object for locating the origin of SAX document events. public void setDocumentLocator(Locator locator) { // ignore it } // Receive notification of a skipped entity. public void skippedEntity(String name) { // ignore it } }