/*
* Copyright 2008-2011 the original author or authors.
*
* 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 com.nominanuda.xml;
import java.io.IOException;
import java.io.Writer;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.ext.LexicalHandler;
import com.nominanuda.lang.Check;
import com.nominanuda.lang.Strings;
public class XmlSerializer implements ContentHandler, LexicalHandler {
private static final XmlHelper XML = new XmlHelper();
private boolean forrestMode = true;
private boolean stripComments = false;
//private boolean omitXmlDecl = true;
private boolean oneline = false;
//private boolean pretty = false;
private boolean xmlEscapeAposAndQuote = false;
private boolean docStarted = false;
private boolean docEnded = false;
private SAXThrowingWriter w;
private boolean withinCdata = false;
private Map<String, String> prefixMappings = new HashMap<String, String>();
private Map<String, String> pendingPrefixMappings = new LinkedHashMap<String, String>();
private boolean startTagJustSeen = false;
public XmlSerializer() {
}
public XmlSerializer(Writer w) {
setWriter(w);
}
public void startPrefixMapping(String prefix, String uri)
throws SAXException {
checkInDocFlow();
pendingPrefixMappings.put(prefix, uri);
prefixMappings.put(prefix, uri);
}
public void endPrefixMapping(String prefix) throws SAXException {
checkInDocFlow();
pendingPrefixMappings.remove(prefix);
prefixMappings.remove(prefix);
}
public void startDocument() throws SAXException {
assertFalse(docStarted);
docStarted = true;
}
public void endDocument() throws SAXException {
assertTrue(docStarted);
docEnded = true;
}
public void comment(char[] ch, int start, int length) throws SAXException {
checkInDocFlow();
if(! stripComments) {
preInsertContent(false);
w.write("<!--");
w.write(ch, start, length);
w.write("-->");
}
}
public void startCDATA() throws SAXException {
checkInDocFlow();
preInsertContent(false);
w.write("<![CDATA[");
withinCdata = true;
}
public void endCDATA() throws SAXException {
checkInDocFlow();
preInsertContent(false);
withinCdata = false;
w.write("]]>");
}
public void characters(char[] ch, int start, int length)
throws SAXException {
checkInDocFlow();
preInsertContent(false);
if(withinCdata) {
w.write(ch, start, length);
} else if(xmlEscapeAposAndQuote) {
w.write(XML.xmlEscape(new String(ch, start, length)));
} else {
w.write(XML.xmlEscapeNoAposAndQuote(new String(ch, start, length)));
}
}
public void startElement(String uri, String localName, String qName,
Attributes atts) throws SAXException {
checkInDocFlow();
preInsertContent(false);
w.write("<"+tagName(uri, localName, qName)+atts(atts)+consumePendingPrefixMappings());
startTagJustSeen = true;
}
private String tagName(String uri, String localName, String qName) {
if(qName != null) {
return qName;
} else if(Strings.nullOrEmpty(uri)) {
return localName;
} else {
for(Entry<String, String> entry : prefixMappings.entrySet()) {
if(uri.equals(entry.getValue())) {
return entry.getKey()+":"+localName;
}
}
Check.illegalstate.fail();
}
return Check.ifNullOrEmpty(qName, localName);
}
private String consumePendingPrefixMappings() {
if(pendingPrefixMappings.isEmpty()) {
return "";
} else {
StringBuilder sb = new StringBuilder();
for(Entry<String, String> prefAndUri : pendingPrefixMappings.entrySet()) {
sb.append(" ");
String prefix = prefAndUri.getKey();
String uri = prefAndUri.getValue();
if("".equals(prefix)) {
sb.append("xmlns=\"");
} else {
sb.append("xmlns:"+XML.xmlEscape(prefix)+"=\"");
}
sb.append(XML.xmlEscape(uri));
sb.append("\"");
}
pendingPrefixMappings.clear();
return sb.toString();
}
}
private String atts(Attributes atts) {
int len = atts.getLength();
StringBuilder sb = new StringBuilder();
for(int i = 0; i < len; i++) {
sb.append(" ");
sb.append(atts.getQName(i));
sb.append("=\"");
sb.append(XML.xmlEscape(atts.getValue(i)));
sb.append("\"");
}
return sb.toString();
}
public void endElement(String uri, String localName, String qName)
throws SAXException {
boolean emptyTag = startTagJustSeen;
preInsertContent(true);
checkInDocFlow();
if(emptyTag && allowSingletonTag(uri, localName, qName)) {
w.write("/>");
} else {
w.write("</"+tagName(uri, localName, qName)+">");
}
}
private boolean allowSingletonTag(String uri, String localName, String qName) {
return true;
}
public void ignorableWhitespace(char[] ch, int start, int length)
throws SAXException {
checkInDocFlow();
preInsertContent(true);
if(! oneline) {
w.write(ch, start, length);
}
}
public void processingInstruction(String target, String data)
throws SAXException {
checkInDocFlow();
preInsertContent(true);
}
//////////////////////////////////////////
private void preInsertContent(boolean isEndTag) throws SAXException {
if(startTagJustSeen && ! isEndTag) {
w.write(">");
}
startTagJustSeen = false;
}
private void checkInDocFlow() throws SAXException {
if(docStarted) {
assertFalse(docEnded);
} else {
assertTrue(forrestMode);
}
}
private void assertTrue(boolean cond) throws SAXException {
if(! cond) {
throw new SAXException();
}
}
private void assertFalse(boolean cond) throws SAXException {
if(cond) {
throw new SAXException();
}
}
//////////////////////////////////////////
public void startDTD(String name, String publicId, String systemId)
throws SAXException {
throw new SAXException("unimplemented");
}
public void startEntity(String name) throws SAXException {
throw new SAXException("unimplemented");
}
public void endDTD() throws SAXException {
throw new SAXException("unimplemented");
}
public void endEntity(String name) throws SAXException {
throw new SAXException("unimplemented");
}
public void skippedEntity(String name) throws SAXException {
throw new SAXException("unimplemented");
}
public void setDocumentLocator(Locator locator) {
//TODO Check.unsupportedoperation.fail();
}
private class SAXThrowingWriter {
private Writer w;
public SAXThrowingWriter(Writer writer) {
w = writer;
}
public void write(char[] cbuf, int off, int len) throws SAXException {
try {
w.write(cbuf, off, len);
} catch (IOException e) {
throw new SAXException(e);
}
}
public void write(String str) throws SAXException {
try {
w.write(str);
} catch (IOException e) {
throw new SAXException(e);
}
}
}
public void setWriter(Writer writer) {
w = new SAXThrowingWriter(writer);
}
}