com.feilong.lib.xstream.io.xml.PrettyPrintWriter Maven / Gradle / Ivy
Show all versions of feilong Show documentation
/*
* Copyright (C) 2004, 2005, 2006 Joe Walnes.
* Copyright (C) 2006, 2007, 2008, 2009, 2011, 2013 XStream Committers.
* All rights reserved.
*
* The software in this package is published under the terms of the BSD
* style license a copy of which has been included with this distribution in
* the LICENSE.txt file.
*
* Created on 07. March 2004 by Joe Walnes
*/
package com.feilong.lib.xstream.io.xml;
import java.io.Writer;
import com.feilong.lib.xstream.core.util.FastStack;
import com.feilong.lib.xstream.core.util.QuickWriter;
import com.feilong.lib.xstream.io.StreamException;
import com.feilong.lib.xstream.io.naming.NameCoder;
/**
* A simple writer that outputs XML in a pretty-printed indented stream.
*
* By default, the chars
* & < > " ' \r
* are escaped
* and replaced with a suitable XML entity. To alter this behavior, override
* the {@link #writeText(com.feilong.lib.xstream.core.util.QuickWriter, String)} and
* {@link #writeAttributeValue(com.feilong.lib.xstream.core.util.QuickWriter, String)} methods.
*
*
* The XML specification requires XML parsers to drop CR characters completely. This implementation
* will therefore use only a LF for line endings, never the platform encoding. You can overwrite the
* {@link #getNewLine()} method for a different behavior.
*
*
* Note: Depending on the XML version some characters cannot be written. Especially a 0
* character is never valid in XML, neither directly nor as entity nor within CDATA. However, this writer
* works by default in a quirks mode, where it will write any character at least as character entity (even
* a null character). You may switch into XML_1_1 mode (which supports most characters) or XML_1_0
* that does only support a very limited number of control characters. See XML specification for version
* 1.0 or
* 1.1. If a character is
* not supported, a {@link StreamException} is thrown. Select a proper parser implementation that
* respects the version in the XML header (the Xpp3 parser will also read character entities of normally
* invalid characters).
*
*
* @author Joe Walnes
* @author Jörg Schaible
*/
public class PrettyPrintWriter extends AbstractXmlWriter{
public static int XML_QUIRKS = -1;
public static int XML_1_0 = 0;
public static int XML_1_1 = 1;
private final QuickWriter writer;
private final FastStack elementStack = new FastStack(16);
private final char[] lineIndenter;
private final int mode;
private boolean tagInProgress;
protected int depth;
private boolean readyForNewLine;
private boolean tagIsEmpty;
private final String newLine;
private static final char[] NULL = "".toCharArray();
private static final char[] AMP = "&".toCharArray();
private static final char[] LT = "<".toCharArray();
private static final char[] GT = ">".toCharArray();
private static final char[] CR = "
".toCharArray();
private static final char[] QUOT = """.toCharArray();
private static final char[] APOS = "'".toCharArray();
private static final char[] CLOSE = " XML_1_1){
throw new IllegalArgumentException("Not a valid XML mode");
}
}
/**
* @since 1.4
*/
public PrettyPrintWriter(Writer writer, int mode, char[] lineIndenter, NameCoder nameCoder){
this(writer, mode, lineIndenter, nameCoder, "\n");
}
/**
* @since 1.3
*/
public PrettyPrintWriter(Writer writer, int mode, char[] lineIndenter){
this(writer, mode, lineIndenter, new XmlFriendlyNameCoder());
}
public PrettyPrintWriter(Writer writer, char[] lineIndenter){
this(writer, XML_QUIRKS, lineIndenter);
}
/**
* @since 1.3
*/
public PrettyPrintWriter(Writer writer, int mode, String lineIndenter){
this(writer, mode, lineIndenter.toCharArray());
}
public PrettyPrintWriter(Writer writer, String lineIndenter){
this(writer, lineIndenter.toCharArray());
}
/**
* @since 1.4
*/
public PrettyPrintWriter(Writer writer, int mode, NameCoder nameCoder){
this(writer, mode, new char[] { ' ', ' ' }, nameCoder);
}
/**
* @since 1.4
*/
public PrettyPrintWriter(Writer writer, NameCoder nameCoder){
this(writer, XML_QUIRKS, new char[] { ' ', ' ' }, nameCoder, "\n");
}
/**
* @since 1.3
*/
public PrettyPrintWriter(Writer writer, int mode){
this(writer, mode, new char[] { ' ', ' ' });
}
public PrettyPrintWriter(Writer writer){
this(writer, new char[] { ' ', ' ' });
}
@Override
public void startNode(String name){
String escapedName = encodeNode(name);
tagIsEmpty = false;
finishTag();
writer.write('<');
writer.write(escapedName);
elementStack.push(escapedName);
tagInProgress = true;
depth++;
readyForNewLine = true;
tagIsEmpty = true;
}
@Override
public void startNode(String name,Class clazz){
startNode(name);
}
@Override
public void setValue(String text){
readyForNewLine = false;
tagIsEmpty = false;
finishTag();
writeText(writer, text);
}
@Override
public void addAttribute(String key,String value){
writer.write(' ');
writer.write(encodeAttribute(key));
writer.write('=');
writer.write('\"');
writeAttributeValue(writer, value);
writer.write('\"');
}
protected void writeAttributeValue(QuickWriter writer,String text){
writeText(text, true);
}
protected void writeText(QuickWriter writer,String text){
writeText(text, false);
}
private void writeText(String text,boolean isAttribute){
int length = text.length();
for (int i = 0; i < length; i++){
char c = text.charAt(i);
switch (c) {
case '\0':
if (mode == XML_QUIRKS){
this.writer.write(NULL);
}else{
throw new StreamException("Invalid character 0x0 in XML stream");
}
break;
case '&':
this.writer.write(AMP);
break;
case '<':
this.writer.write(LT);
break;
case '>':
this.writer.write(GT);
break;
case '"':
this.writer.write(QUOT);
break;
case '\'':
this.writer.write(APOS);
break;
case '\r':
this.writer.write(CR);
break;
case '\t':
case '\n':
if (!isAttribute){
this.writer.write(c);
break;
}
default:
if (Character.isDefined(c) && !Character.isISOControl(c)){
if (mode != XML_QUIRKS){
if (c > '\ud7ff' && c < '\ue000'){
throw new StreamException("Invalid character 0x" + Integer.toHexString(c) + " in XML stream");
}
}
this.writer.write(c);
}else{
if (mode == XML_1_0){
if (c < 9 || c == '\u000b' || c == '\u000c' || c == '\u000e' || (c >= '\u000f' && c <= '\u001f')){
throw new StreamException("Invalid character 0x" + Integer.toHexString(c) + " in XML 1.0 stream");
}
}
if (mode != XML_QUIRKS){
if (c == '\ufffe' || c == '\uffff'){
throw new StreamException("Invalid character 0x" + Integer.toHexString(c) + " in XML stream");
}
}
this.writer.write("");
this.writer.write(Integer.toHexString(c));
this.writer.write(';');
}
}
}
}
@Override
public void endNode(){
depth--;
if (tagIsEmpty){
writer.write('/');
readyForNewLine = false;
finishTag();
elementStack.popSilently();
}else{
finishTag();
writer.write(CLOSE);
writer.write((String) elementStack.pop());
writer.write('>');
}
readyForNewLine = true;
if (depth == 0){
writer.flush();
}
}
private void finishTag(){
if (tagInProgress){
writer.write('>');
}
tagInProgress = false;
if (readyForNewLine){
endOfLine();
}
readyForNewLine = false;
tagIsEmpty = false;
}
protected void endOfLine(){
writer.write(getNewLine());
for (int i = 0; i < depth; i++){
writer.write(lineIndenter);
}
}
@Override
public void flush(){
writer.flush();
}
@Override
public void close(){
writer.close();
}
/**
* Retrieve the line terminator.
*
* This method returns always a line feed, since according the XML specification any parser
* must ignore a carriage return. Overload this method, if you need different behavior.
*
* @return the line terminator
* @since 1.3
*/
protected String getNewLine(){
return newLine;
}
}