package org.mindswap.swoop.utils.rdfapi; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; import java.net.URI; import java.net.URISyntaxException; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.Stack; import java.util.TreeMap; import java.util.TreeSet; /** * @author ronwalf * * An implementation of XMLWriter to write nicely indented xml */ public class PrettyXMLWriter implements XMLWriter { protected static String INDENT = " "; protected int level; protected Writer writer; private String encoding; private Map entities; private Map namespaces; private Set nextNamespaces; private Stack tags; private Stack bases; protected URI base; private boolean inTag = false; private boolean firstAttribute = false; private boolean dataWritten = false; private boolean wasEmpty = true; private int tagLength = 0; public PrettyXMLWriter(Writer writer) { this.writer = writer; encoding = "UTF-8"; reset(); } public PrettyXMLWriter(Writer writer, String encoding) { this.writer = writer; this.encoding = encoding; reset(); } /* (non-Javadoc) * @see org.mindswap.swoop.serializers.XMLWriter#addAttribute(java.net.URI, java.lang.String, java.lang.String) */ public void addAttribute(String ns, String local, String value) throws IOException { indentAttribute(); write(" "); writeQName(ns, local); write("=\""+replaceEntities(sanitize(value, true))+"\""); } /* (non-Javadoc) * @see org.mindswap.swoop.serializers.XMLWriter#addAttribute(java.net.URI, java.lang.String, java.net.URI) */ public void addAttribute(String ns, String local, URI value) throws IOException { if (base == null) { addAttribute(ns, local, value.toString()); } else { addAttribute(ns, local, base.relativize(value).toString()); } } /* (non-Javadoc) * @see org.mindswap.swoop.serializers.XMLWriter#addEntity(java.lang.String, java.lang.String) */ public void addEntity(String name, String value) { if ((name != null) && (value != null) && !value.equals("")) entities.put(name, value); } /* (non-Javadoc) * @see org.mindswap.swoop.serializers.XMLWriter#addNamespace(java.lang.String, java.net.URI) */ public void addNamespace(String name, String namespace) { namespaces.put(namespace, name); nextNamespaces.add(namespace); } private void addNamespaces() throws IOException { Map additions = new TreeMap(); for (Iterator nameIter = nextNamespaces.iterator(); nameIter.hasNext();) { String namespace = (String) nameIter.next(); String prefix = (String) namespaces.get(namespace); additions.put(prefix, namespace); } nextNamespaces.clear(); for (Iterator prefixIter = additions.keySet().iterator(); prefixIter.hasNext();) { String prefix = (String) prefixIter.next(); String namespace = (String) additions.get(prefix); writeAttribute("xmlns:"+prefix, namespace.toString()); } } /* (non-Javadoc) * @see org.mindswap.swoop.serializers.XMLWriter#endDocument() */ public void endDocument() throws IOException { while (!tags.empty()) { endElement(); } write("\n"); } /* (non-Javadoc) * @see org.mindswap.swoop.serializers.XMLWriter#endElement() */ public void endElement() throws IOException { if (inTag) { finishTag(true); tags.pop(); level--; wasEmpty = true; } else { level--; if (dataWritten) dataWritten = false; else indent(); String[] tag = (String[]) tags.pop(); write("</"); writeQName(tag[0], tag[1]); write(">"); wasEmpty = false; } base = (URI) bases.pop(); /* bases.pop(); if (bases.isEmpty()) base = null; else base = (URI) bases.peek(); */ } private void finishTag(boolean empty) throws IOException { addNamespaces(); if (empty) { write("/>"); } else { write(">"); } inTag = false; level++; } protected String getName(String namespace) { String name = (String) namespaces.get(namespace); if (name == null) { int i = 0; for (i=0; namespaces.containsValue("a"+i);i++) {} name = "a"+i; namespaces.put(namespace, name); nextNamespaces.add(namespace); } return name; } protected void indent() throws IOException { indent(0); } protected void indent(int extra) throws IOException { write("\n"); for (int i=0; i < level; i++) { write(INDENT); } for (int i=0; i < extra; i++) { write(" "); } } protected void indentAttribute() throws IOException { if (firstAttribute) { firstAttribute = false; } else { indent(tagLength+1); } } protected String replaceEntities(String value) { for (Iterator entityIter = entities.keySet().iterator(); entityIter.hasNext();) { String entityName = (String) entityIter.next(); String entityValue = (String) entities.get(entityName); if (value.indexOf(entityValue) >= 0) { //System.out.println("Replacing "+entityValue); value = value.replaceAll(entityValue, "&"+entityName+";"); //break; } } return value; } protected void reset() { inTag = false; wasEmpty = true; base = null; bases = new Stack(); entities = new TreeMap(); namespaces = new HashMap(); nextNamespaces = new TreeSet(); tags = new Stack(); } protected static String sanitize(String str) { return sanitize(str, false); } protected static String sanitize(String str, boolean quote) { StringBuffer result = new StringBuffer(); char str_chars[] = str.toCharArray(); for (int index=0; index < str_chars.length; index++) { char character = str_chars[index]; if (character=='&') result.append("&"); else if (character=='<') result.append("<"); else if (quote && character=='"') result.append("""); else result.append(character); } return result.toString(); } /* (non-Javadoc) * @see org.mindswap.swoop.serializers.XMLWriter#setBase() */ public void setBase(URI base) { this.base = base; } /* (non-Javadoc) * @see org.mindswap.swoop.serializers.XMLWriter#startDocument() */ public void startDocument() throws IOException { write("<?xml version=\"1.0\" encoding=\""+encoding+"\"?>\n"); if (!entities.isEmpty()) { write("<!DOCTYPE rdf:RDF [\n"); for (Iterator entIter = entities.keySet().iterator(); entIter.hasNext();) { String name = (String) entIter.next(); String value = (String) entities.get(name); write(INDENT+"<!ENTITY "+name+" \""+sanitize(value, true)+"\">\n"); } write("]>"); } } /* (non-Javadoc) * @see org.mindswap.swoop.serializers.XMLWriter#startElement(java.net.URI, java.lang.String) */ public void startElement(String ns, String local) throws IOException { if (inTag) { finishTag(false); } else if (!wasEmpty && (level < 2)) { write("\n"); } indent(); write("<"); String tag = writeQName(ns, local); tagLength = tag.length(); tags.push(new String[]{ns, local}); inTag = true; firstAttribute = true; if (base != null && (bases.isEmpty() || base != bases.peek())) { writeAttribute("xml:base", base.toString()); } bases.push(base); } protected void write(String data) throws IOException { writer.write(data); } protected void writeAttribute(String name, String value) throws IOException { indentAttribute(); value = sanitize(value, true); value = replaceEntities(value); write(" "+name+"=\""+value+"\""); } public void writeComment(String comment) throws IOException { if (inTag) { finishTag(false); } write("\n\n<!-- "+sanitize(comment)+" -->"); wasEmpty=true; } /* (non-Javadoc) * @see org.mindswap.swoop.serializers.XMLWriter#writeData(java.lang.String) */ public void writeData(String data) throws IOException { writeData(data, false); } /* (non-Javadoc) * @see org.mindswap.swoop.serializers.XMLWriter#writeData(java.lang.String, boolean) */ public void writeData(String data, boolean raw) throws IOException { if (inTag) { finishTag(false); } if (raw) write(data); else write(sanitize(data)); dataWritten = true; } protected String writeQName(String ns, String local) throws IOException { String tag = local; if (ns != null) { String name = getName(ns); tag = name + ":" + local; } write(tag); return tag; } /** * Simple test driver * @param args - Ignored. * @throws URISyntaxException */ public static void main(String args[]) throws IOException, URISyntaxException { Writer writer = new OutputStreamWriter(System.out); XMLWriter xml = new PrettyXMLWriter(writer); URI base1 = new URI("http://www.example.com/"); URI base2 = new URI("http://www.example.com/ns2/"); URI ns1 = new URI("http://www.example.com/ns1/"); URI ns2 = new URI("http://www.example.com/ns2/"); xml.addEntity("ex", "http://www.example.com/"); xml.startDocument(); xml.addNamespace("ns1", ns1.toString()); xml.setBase(base1); xml.startElement(ns1.toString(), "Foo"); xml.startElement(ns2.toString(), "Bar"); xml.addAttribute(ns2.toString(), "attr1", "pleep"); xml.addAttribute(ns2.toString(), "attr2", ns2.resolve("plorb")); xml.setBase(base2); xml.startElement(ns2.toString(), "BarPlorb"); xml.addAttribute(ns2.toString(), "attr3", ns2.resolve("plorb")); xml.endElement(); xml.endElement(); xml.startElement(ns2.toString(), "Baz"); xml.writeData("Foobar"); xml.endDocument(); writer.close(); } }