/******************************************************************************* * Copyright (c) 2009, Adobe Systems Incorporated * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * · Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * · Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * · Neither the name of Adobe Systems Incorporated nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ package com.adobe.dp.xml.util; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.Stack; public class XMLSerializer implements SDocumentHandler { private OutputStream outStream; private boolean forgiving; private PrintWriter out; private StringBuffer prologue; private boolean hasEntities; private Stack openElements = new Stack(); private Map prefixMap = new HashMap(); private Map namespaceMap = new HashMap(); private String defaultNamespace; private boolean closeTag; private static char[] newLineChar = {'\n'}; private static String xmlns = "http://www.w3.org/XML/1998/namespace"; static class OpenElement { OpenElement(String namespace, String name) { this.name = name; this.namespace = namespace; } String name; String namespace; String prefix; Set localNamespaces; String savedDefaultNamespace; } public XMLSerializer(OutputStream out, boolean forgiving) { this.outStream = out; this.forgiving = forgiving; } public XMLSerializer(OutputStream out) { this.outStream = out; } private String makePrefixForNamespace(String namespace) { int index = namespace.lastIndexOf('/'); if (index < 0) index = namespace.lastIndexOf(':'); if (index > 0) { String prefix = namespace.substring(index + 1); if (prefix.matches("[a-zA-Z][-a-zA-Z0-9_]*")) return prefix; } return "p"; } private String getPrefix(String namespace) { if( namespace.equals(xmlns) ) return "xml"; // built-in String prefix = (String) namespaceMap.get(namespace); if (prefix != null) return prefix; String prefixBase = makePrefixForNamespace(namespace); prefix = prefixBase; int index = 1; while (prefixMap.containsKey(prefix)) { prefix = prefixBase + index; index++; } prefixMap.put(prefix, namespace); namespaceMap.put(namespace, prefix); OpenElement e = (OpenElement) openElements.peek(); if (e.localNamespaces == null) e.localNamespaces = new HashSet(); e.localNamespaces.add(prefix); return prefix; } private boolean sameNamespace(String ns1, String ns2) { if (ns1 == ns2) return true; if (ns1 == null || ns2 == null) return false; return ns1.equals(ns2); } private void closeTagIfNeeded() { if (closeTag) { out.print(">"); //newLine(); closeTag = false; } } private String escapeAttribute(String value) { value = StringUtil.replace(value, "&", "&"); value = StringUtil.replace(value, "<", "<"); value = StringUtil.replace(value, ">", ">"); value = StringUtil.replace(value, "\"", """); return value; } public void startDocument(String version, String encoding) { if (encoding == null) encoding = "utf-8"; try { out = new PrintWriter(new OutputStreamWriter(outStream, encoding)); } catch (UnsupportedEncodingException e) { throw new IllegalArgumentException("unsupported encoding: '" + encoding + "'"); } out.print("<?xml version=\""); out.print(version); out.print("\" encoding=\""); out.print(encoding); out.println("\"?>"); } public void processingInstruction(String name, String value) { out.print("<?"); out.print(name); out.print(" "); out.print(value); out.println("?>"); } public void endDocument() { if (!openElements.isEmpty()) { if (forgiving) { do { OpenElement e = (OpenElement) openElements.pop(); endElement(e.namespace, e.name); } while (!openElements.isEmpty()); } else { throw new RuntimeException("unended elements"); } } closeTagIfNeeded(); out.close(); } public void setDoctype(String doctype, String publicId, String systemId) { prologue = new StringBuffer("<!DOCTYPE "); prologue.append(doctype); if (publicId != null) { prologue.append(" PUBLIC \""); prologue.append(publicId); prologue.append("\""); } if (systemId != null) { prologue.append(" \""); prologue.append(systemId); prologue.append("\""); } } public void setPreferredPrefixMap(SMap preferredPrefixes) { SMapIterator iterator = preferredPrefixes.iterator(); while (iterator.hasItem()) { String prefix = iterator.getName(); String namespace = iterator.getValue().toString(); namespaceMap.put(namespace, prefix); prefixMap.put(prefix, namespace); iterator.nextItem(); } } public void defineEntity(String entityName, String value) { if (!hasEntities) { prologue.append(" [\n"); hasEntities = true; } prologue.append("<!ENTITY "); prologue.append(entityName); prologue.append(" \""); prologue.append(escapeAttribute(value)); prologue.append("\">\n"); } public void startElement(String namespace, String name, SMap attributes, boolean makeDefaultNamespace) { boolean rootElement = openElements.size() == 0; if (prologue != null && rootElement) { if (hasEntities) { prologue.append("]"); } prologue.append(">"); out.println(prologue); //newLine(); } closeTagIfNeeded(); OpenElement e = new OpenElement(namespace, name); openElements.push(e); out.print("<"); String newDefaultNS = null; if (!namespace.equals(defaultNamespace)) { if (makeDefaultNamespace) { e.savedDefaultNamespace = defaultNamespace; defaultNamespace = namespace; newDefaultNS = namespace; } else { e.prefix = getPrefix(namespace); out.print(e.prefix); out.print(":"); } } out.print(name); if (newDefaultNS != null) { out.print(" xmlns=\""); out.print(escapeAttribute(newDefaultNS)); out.print("\""); } if (attributes != null) { SMapIterator iterator = attributes.iterator(); while (iterator.hasItem()) { out.print(" "); String attributeNamespace = iterator.getNamespace(); if (attributeNamespace != null) { String attributePrefix = getPrefix(attributeNamespace); out.print(attributePrefix); out.print(":"); } String attributeName = iterator.getName(); out.print(attributeName); out.print("=\""); String value = escapeAttribute(iterator.getValue().toString()); out.print(value); out.print("\""); iterator.nextItem(); } } if (e.localNamespaces != null || rootElement) { Iterator prefixes; if (rootElement) prefixes = prefixMap.keySet().iterator(); else prefixes = e.localNamespaces.iterator(); while (prefixes.hasNext()) { String nsPrefix = (String) prefixes.next(); out.print(" xmlns:"); out.print(nsPrefix); out.print("=\""); String ns = (String) prefixMap.get(nsPrefix); out.print(escapeAttribute(ns)); out.print("\""); } } closeTag = true; } public void endElement(String namespace, String name) { if (openElements.isEmpty()) { if (forgiving) return; throw new RuntimeException("{" + namespace + "}" + name + ": element closed after document end"); } OpenElement e = (OpenElement) openElements.peek(); if (!sameNamespace(e.namespace, namespace) || !e.name.equals(name)) { if (forgiving) { int len = openElements.size(); int i; for (i = len - 2; i >= 0; i--) { e = (OpenElement) openElements.elementAt(i); if (sameNamespace(e.namespace, namespace) && e.name.equals(name)) break; } if (i < 0) return; while (true) { e = (OpenElement) openElements.peek(); if (sameNamespace(e.namespace, namespace) && e.name.equals(name)) break; endElement(e.namespace, e.name); } } else { throw new RuntimeException("Illegal nesting: {" + namespace + "}" + name + ", {" + e.namespace + "}" + e.name + " expected"); } } openElements.pop(); if (closeTag) { out.print("/>"); closeTag = false; } else { out.print("</"); if (e.prefix != null) { out.print(e.prefix); out.print(":"); } out.print(e.name); out.print(">"); } //newLine(); if (e.savedDefaultNamespace != null) defaultNamespace = e.savedDefaultNamespace; if (e.localNamespaces != null) { Iterator prefixes = e.localNamespaces.iterator(); while (prefixes.hasNext()) { String prefix = (String) prefixes.next(); String ns = (String) prefixMap.remove(prefix); namespaceMap.remove(ns); } } } public void entityReference(String entity) { closeTagIfNeeded(); out.print("&"); out.print(entity); out.print(";"); } public void newLine() { text(newLineChar, 0, 1); } public void text(char[] text, int offset, int len) { closeTagIfNeeded(); int end = offset + len; String ent = null; for (int i = offset; i < end; i++) { char c = text[i]; switch (c) { case '&': ent = "&"; break; case '<': ent = "<"; break; case '>': ent = ">"; break; default: continue; } if (i > offset) out.write(text, offset, i - offset); offset = i + 1; out.print(ent); } if (end > offset) out.write(text, offset, end - offset); } }