/*******************************************************************************
* Copyright (c) 2007, 2015 David Green and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* David Green - initial API and implementation
*******************************************************************************/
package org.eclipse.mylyn.wikitext.util;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
import com.google.common.escape.Escaper;
import com.google.common.xml.XmlEscapers;
/**
* A default implementation of {@link XmlStreamWriter} that creates XML character output.
*
* @author David Green
* @since 3.0
*/
public class DefaultXmlStreamWriter extends XmlStreamWriter {
private final Escaper attributeEscaper = XmlEscapers.xmlAttributeEscaper();
private final Escaper contentEscaper = XmlEscapers.xmlContentEscaper();
private PrintWriter out;
private final Map<String, String> prefixToUri = new HashMap<String, String>();
private final Map<String, String> uriToPrefix = new HashMap<String, String>();
private boolean inEmptyElement = false;
private boolean inStartElement = false;
private final Stack<String> elements = new Stack<String>();
private char xmlHeaderQuoteChar = '\'';
public DefaultXmlStreamWriter(OutputStream out) throws UnsupportedEncodingException {
this.out = createUtf8PrintWriter(out);
}
public DefaultXmlStreamWriter(Writer out) {
this.out = new PrintWriter(out);
}
public DefaultXmlStreamWriter(Writer out, char xmlHeaderQuoteChar) {
this.out = new PrintWriter(out);
this.xmlHeaderQuoteChar = xmlHeaderQuoteChar;
}
protected PrintWriter createUtf8PrintWriter(java.io.OutputStream out) throws UnsupportedEncodingException {
return new java.io.PrintWriter(new OutputStreamWriter(out, "UTF8")); //$NON-NLS-1$
}
@Override
public void close() {
if (out != null) {
closeElement();
flush();
}
out = null;
}
@Override
public void flush() {
if (out != null) {
out.flush();
}
}
@Override
public String getPrefix(String uri) {
return uriToPrefix.get(uri);
}
@Override
public String getNamespaceURI(String prefix) {
return prefixToUri.get(prefix);
}
public Object getProperty(String name) throws IllegalArgumentException {
return null;
}
@Override
public void setDefaultNamespace(String uri) {
setPrefix("", uri); //$NON-NLS-1$
}
@Override
public void setPrefix(String prefix, String uri) {
prefixToUri.put(prefix, uri);
uriToPrefix.put(uri, prefix);
}
@Override
public void writeAttribute(String localName, String value) {
out.write(' ');
out.write(localName);
out.write("=\""); //$NON-NLS-1$
if (value != null) {
attrEncode(value);
}
out.write("\""); //$NON-NLS-1$
}
@Override
public void writeAttribute(String namespaceURI, String localName, String value) {
String prefix = uriToPrefix.get(namespaceURI);
writeAttribute(prefix, namespaceURI, localName, value);
}
@Override
public void writeAttribute(String prefix, String namespaceURI, String localName, String value) {
out.write(' ');
if (prefix != null && prefix.length() > 0) {
out.write(prefix);
out.write(':');
}
out.write(localName);
out.write("=\""); //$NON-NLS-1$
if (value != null) {
attrEncode(value);
}
out.write("\""); //$NON-NLS-1$
}
private void attrEncode(String value) {
if (value == null) {
return;
}
printEscaped(out, value, true);
}
private void encode(String text) {
if (text == null) {
return;
}
printEscaped(out, text, false);
}
@Override
public void writeCData(String data) {
closeElement();
out.write("<![CDATA["); //$NON-NLS-1$
out.write(data);
out.write("]]>"); //$NON-NLS-1$
}
@Override
public void writeCharacters(String text) {
closeElement();
encode(text);
}
public void writeCharactersUnescaped(String text) {
closeElement();
out.print(text);
}
@Override
public void writeLiteral(String literal) {
writeCharactersUnescaped(literal);
}
@Override
public void writeCharacters(char[] text, int start, int len) {
closeElement();
encode(new String(text, start, len));
}
@Override
public void writeComment(String data) {
closeElement();
out.write("<!-- "); //$NON-NLS-1$
out.write(data);
out.write(" -->"); //$NON-NLS-1$
}
@Override
public void writeDTD(String dtd) {
out.write(dtd);
}
@Override
public void writeDefaultNamespace(String namespaceURI) {
writeAttribute("xmlns", namespaceURI); //$NON-NLS-1$
}
private void closeElement() {
if (inEmptyElement) {
out.write("/>"); //$NON-NLS-1$
inEmptyElement = false;
} else if (inStartElement) {
out.write(">"); //$NON-NLS-1$
inStartElement = false;
}
}
@Override
public void writeEmptyElement(String localName) {
closeElement();
inEmptyElement = true;
out.write('<');
out.write(localName);
}
@Override
public void writeEmptyElement(String namespaceURI, String localName) {
closeElement();
inEmptyElement = true;
String prefix = uriToPrefix.get(namespaceURI);
out.write('<');
if (prefix != null && prefix.length() > 0) {
out.write(prefix);
out.write(':');
}
out.write(localName);
}
@Override
public void writeEmptyElement(String prefix, String localName, String namespaceURI) {
closeElement();
inEmptyElement = true;
out.write('<');
if (prefix != null && prefix.length() > 0) {
out.write(prefix);
out.write(':');
}
out.write(localName);
}
@Override
public void writeEndDocument() {
if (!elements.isEmpty()) {
throw new IllegalStateException(elements.size() + " elements not closed"); //$NON-NLS-1$
}
}
@Override
public void writeEndElement() {
closeElement();
if (elements.isEmpty()) {
throw new IllegalStateException();
}
String name = elements.pop();
out.write('<');
out.write('/');
out.write(name);
out.write('>');
}
@Override
public void writeEntityRef(String name) {
closeElement();
out.write('&');
out.write(name);
out.write(';');
}
@Override
public void writeNamespace(String prefix, String namespaceURI) {
if (prefix == null || prefix.length() == 0 || prefix.equals("xmlns")) { //$NON-NLS-1$
writeDefaultNamespace(namespaceURI);
} else {
writeAttribute("xmlns:" + prefix, namespaceURI); //$NON-NLS-1$
}
}
@Override
public void writeProcessingInstruction(String target) {
closeElement();
}
@Override
public void writeProcessingInstruction(String target, String data) {
closeElement();
}
@Override
public void writeStartDocument() {
out.write(processXmlHeader("<?xml version='1.0' ?>")); //$NON-NLS-1$
}
@Override
public void writeStartDocument(String version) {
out.write(processXmlHeader("<?xml version='" + version + "' ?>")); //$NON-NLS-1$ //$NON-NLS-2$
}
@Override
public void writeStartDocument(String encoding, String version) {
out.write(processXmlHeader("<?xml version='" + version + "' encoding='" + encoding + "' ?>")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
@Override
public void writeStartElement(String localName) {
closeElement();
inStartElement = true;
elements.push(localName);
out.write('<');
out.write(localName);
}
@Override
public void writeStartElement(String namespaceURI, String localName) {
String prefix = uriToPrefix.get(namespaceURI);
writeStartElement(prefix, localName, namespaceURI);
}
@Override
public void writeStartElement(String prefix, String localName, String namespaceURI) {
closeElement();
inStartElement = true;
out.write('<');
if (prefix != null && prefix.length() > 0) {
out.write(prefix);
out.write(':');
elements.push(prefix + ':' + localName);
} else {
elements.push(localName);
}
out.write(localName);
}
/**
*
*/
public char getXmlHeaderQuoteChar() {
return xmlHeaderQuoteChar;
}
/**
*
*/
public void setXmlHeaderQuoteChar(char xmlHederQuoteChar) {
this.xmlHeaderQuoteChar = xmlHederQuoteChar;
}
/**
* @deprecated use {@link #getXmlHeaderQuoteChar()}
*/
@Deprecated
public char getXmlHederQuoteChar() {
return getXmlHeaderQuoteChar();
}
/**
* @deprecated use {@link #setXmlHeaderQuoteChar(char)}
*/
@Deprecated
public void setXmlHederQuoteChar(char xmlHederQuoteChar) {
setXmlHeaderQuoteChar(xmlHederQuoteChar);
}
private String processXmlHeader(String header) {
return xmlHeaderQuoteChar == '\'' ? header : header.replace('\'', xmlHeaderQuoteChar);
}
private void printEscaped(PrintWriter writer, CharSequence s, boolean attribute) {
Escaper escaper = attribute ? attributeEscaper : contentEscaper;
writer.write(escaper.escape(s.toString()));
}
/**
* @deprecated
*/
@Deprecated
protected static String getEntityRef(int ch, boolean attribute) {
// Encode special XML characters into the equivalent character
// references.
// These five are defined by default for all XML documents.
switch (ch) {
case '<':
return "lt"; //$NON-NLS-1$
case '>':
if (!attribute) {
// bug 302291: text containing CDATA produces invalid HTML
return "gt"; //$NON-NLS-1$
}
case '"':
if (attribute) {
return "quot"; //$NON-NLS-1$
}
break;
case '&':
return "amp"; //$NON-NLS-1$
// WARN: there is no need to encode apostrophe, and doing so has an
// adverse
// effect on XHTML documents containing javascript with some browsers.
// case '\'':
// return "apos";
}
return null;
}
/**
* @deprecated
*/
@Deprecated
protected static boolean isUtf8Printable(char ch) {
// fall-back method here.
if ((ch >= ' ' && ch <= 0x10FFFF && ch != 0xF7) || ch == '\n' || ch == '\r' || ch == '\t') {
// If the character is not printable, print as character reference.
// Non printables are below ASCII space but not tab or line
// terminator, ASCII delete, or above a certain Unicode threshold.
return true;
}
return false;
}
}