Details
-
Type: Improvement
-
Status: Closed
-
Resolution: Fixed
-
Affects Version/s: None
-
Fix Version/s: None
-
Component/s: None
-
Labels:None
Description
I'd like to be able to have XStream.toXML (Object, Writer) use my own PrettyPrintWriter extension.
There are two problems, though.
1. XStream#toXML (Object, Writer) uses a hard-wired PrettyPrintWriter in that method.
2. PrettyPrintWriter#setValue uses private booleans and methods, not accessible to sub-classes.
My suggestion is to be able to write this:
public class MyPrettyPrintWriter extends PrettyPrintWriter
{
....
public void setValue (String text)
{
readyForNewLine = false;
tagIsEmpty = false;
finishTag ();
boolean hasXml = false;
if (text.indexOf ('<') != -1)
{ hasXml = true; writer.write ("<[CDATA["); }writer.write (text);
if (hasXml)
{ writer.write ("]]>"); } }
}
But, I have to duplicate quite a bit of code to achieve this; in other words, copy PrettyPrintWriter.java to MyPrettyPrintWriter.java, just to overload #setValue(), that's excessive.
And I still have to overwrite XStream#toXML (Object, Writer).
It would be better if the properties / methods in PrettyPrintWriter mentioned above were protected.
And, it be really cool if there was a XStream#toXml (Object, HierarchicalStreamWriter).
That would be proper Object Orientation.
Thanks
Activity
I know this was closed but I have had some similar issues with CDATA. I understand your solution but it puts CDATA on every single element, and I want more control over when the CDATA tag is used. The bug reporter's code which checks for a '<' and then adds CDATA is a nice addition as a little insurance but as I mentioned, I want more fine grained control. This is what I have done - I created a CdataConverter which can be annotated onto a field with the @XSteamConverter annotation and then modified PrettyPrintWriter to include CDATA handling. I also added the insurance check as well. I also made a modification for to allow single quotes for attributes set with a boolean flag.
Here is the code written in groovy:
package xxx import com.thoughtworks.xstream.converters.Converter; import com.thoughtworks.xstream.converters.MarshallingContext; import com.thoughtworks.xstream.converters.UnmarshallingContext; import com.thoughtworks.xstream.io.HierarchicalStreamReader; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; /** * @author * */ class CdataConverter implements Converter { /* (non-Javadoc) * @see com.thoughtworks.xstream.converters.Converter#marshal(java.lang.Object, com.thoughtworks.xstream.io.HierarchicalStreamWriter, com.thoughtworks.xstream.converters.MarshallingContext) */ @Override public void marshal ( Object text, HierarchicalStreamWriter writer, MarshallingContext context ) { MyPrettyPrintWriter cdataWriter = (MyPrettyPrintWriter)writer.underlyingWriter() cdataWriter.setUseCdata true writer.setValue((String)text) cdataWriter.setUseCdata false } /* (non-Javadoc) * @see com.thoughtworks.xstream.converters.Converter#unmarshal(com.thoughtworks.xstream.io.HierarchicalStreamReader, com.thoughtworks.xstream.converters.UnmarshallingContext) */ @Override public Object unmarshal ( HierarchicalStreamReader reader, UnmarshallingContext context ) { return reader.getValue() } /* (non-Javadoc) * @see com.thoughtworks.xstream.converters.ConverterMatcher#canConvert(java.lang.Class) */ @Override public boolean canConvert ( Class clazz ) { return clazz == String.class } }
/* * Copyright (C) 2004, 2005, 2006 Joe Walnes. * Copyright (C) 2006, 2007, 2008 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 xxx import java.io.Writer; import com.thoughtworks.xstream.core.util.FastStack; import com.thoughtworks.xstream.core.util.QuickWriter; import com.thoughtworks.xstream.io.StreamException; import com.thoughtworks.xstream.io.xml.AbstractXmlWriter; import com.thoughtworks.xstream.io.xml.XmlFriendlyReplacer; /** * Copied code from xstream 1.3.1 PrettyPrintWriter * I have altered the code to allow for CDATA handling * and single or double quotes. If PrettyPrintWriter had * allowed me to overwrite the writeText method to add single * quoting, I would not have needed to copy the whole class. * I am submitting this change to the xstream maintainers. */ /** * A simple writer that outputs XML in a pretty-printed indented stream. * <p> * By default, the chars <code><pre> * & < > " ' \r * </pre></code> are escaped and replaced with a suitable XML entity. To alter this behavior, override * the the {@link #writeText(com.thoughtworks.xstream.core.util.QuickWriter, String)} and * {@link #writeAttributeValue(com.thoughtworks.xstream.core.util.QuickWriter, String)} methods. * </p> * <p> * 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 * <a href="http://www.w3.org/TR/2006/REC-xml-20060816/#charsets">1.0</a> or * <a href="http://www.w3.org/TR/2006/REC-xml11-20060816/#charsets">1.1</a>. 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). * </p> * * @author Joe Walnes * @author Jörg Schaible */ public class BroadHopPrettyPrintWriter 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 String newLine; private boolean useSingleQuote = false; private String CDATA_START = "<![CDATA["; private String CDATA_END = "]]>"; private boolean useCdata; 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 = "</".toCharArray(); private MyPrettyPrintWriter( Writer writer, int mode, char[] lineIndenter, XmlFriendlyReplacer replacer, String newLine) { super(replacer); this.writer = new QuickWriter(writer); this.lineIndenter = lineIndenter; this.newLine = newLine; this.mode = mode; if (mode < XML_QUIRKS || mode > XML_1_1) { throw new IllegalArgumentException("Not a valid XML mode"); } } /** * @since 1.2 * @deprecated since 1.3 */ public MyPrettyPrintWriter( Writer writer, char[] lineIndenter, String newLine, XmlFriendlyReplacer replacer) { this(writer, XML_QUIRKS, lineIndenter, replacer, newLine); } /** * @since 1.3 */ public MyPrettyPrintWriter( Writer writer, int mode, char[] lineIndenter, XmlFriendlyReplacer replacer) { this(writer, mode, lineIndenter, replacer, "\n"); } /** * @deprecated since 1.3 */ public MyPrettyPrintWriter(Writer writer, char[] lineIndenter, String newLine) { this(writer, lineIndenter, newLine, new XmlFriendlyReplacer()); } /** * @since 1.3 */ public MyPrettyPrintWriter(Writer writer, int mode, char[] lineIndenter) { this(writer, mode, lineIndenter, new XmlFriendlyReplacer()); } public MyPrettyPrintWriter(Writer writer, char[] lineIndenter) { this(writer, lineIndenter, "\n"); } /** * @deprecated since 1.3 */ public MyPrettyPrintWriter(Writer writer, String lineIndenter, String newLine) { this(writer, lineIndenter.toCharArray(), newLine); } /** * @since 1.3 */ public MyPrettyPrintWriter(Writer writer, int mode, String lineIndenter) { this(writer, mode, lineIndenter.toCharArray()); } public MyPrettyPrintWriter(Writer writer, String lineIndenter) { this(writer, lineIndenter.toCharArray()); } /** * @since 1.3 */ public MyPrettyPrintWriter(Writer writer, int mode, XmlFriendlyReplacer replacer) { this(writer, mode, new char[]{' ', ' '}, replacer); } public MyPrettyPrintWriter(Writer writer, XmlFriendlyReplacer replacer) { this(writer, new char[]{' ', ' '}, "\n", replacer); } /** * @since 1.3 */ public MyPrettyPrintWriter(Writer writer, int mode) { this(writer, mode, new char[]{' ', ' '}); } public MyPrettyPrintWriter(Writer writer) { this(writer, new char[]{' ', ' '}); } public void startNode(String name) { String escapedName = escapeXmlName(name); tagIsEmpty = false; finishTag(); writer.write('<'); writer.write(escapedName); elementStack.push(escapedName); tagInProgress = true; depth++ ; readyForNewLine = true; tagIsEmpty = true; } public void startNode(String name, Class clazz) { startNode(name); } public void setValue(String text) { readyForNewLine = false; tagIsEmpty = false; finishTag(); if (text.indexOf ('<') != -1) { useCdata = true; } writeText(writer, text); useCdata = false; } /*public void addAttribute(String key, String value) { writer.write(' '); writer.write(escapeXmlName(key)); writer.write('='); writer.write('\"'); writeAttributeValue(writer, value); writer.write('\"'); }*/ public void addAttribute(String key, String value) { writer.write(' '); writer.write(escapeXmlName(key)); writer.write('='); if(useSingleQuote) { writer.write("'"); } else { writer.write('\"'); } writeAttributeValue(writer, value); if(useSingleQuote) { writer.write("'"); } else { writer.write('\"'); } } protected void writeAttributeValue(QuickWriter writer, String text) { writeText(text); } protected void writeText(QuickWriter writer, String text) { if (!useCdata) { writeText(text); } else { writer.write(CDATA_START); writer.write(validateCdata(text)); writer.write(CDATA_END); } } protected String validateCdata(String text) { if(text.contains(CDATA_END)) throw new StreamException("invalid character sequence for CDATA: ]]>"); return text; } /* protected void writeText(QuickWriter writer, String text) { writeText(text); } */ private void writeText(String text) { 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': 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') { 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("&#x"); this.writer.write(Integer.toHexString(c)); this.writer.write(';'); } } } } 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); } } public void flush() { writer.flush(); } public void close() { writer.close(); } protected String getNewLine() { return newLine; } public boolean useSingleQuote() { return useSingleQuote; } public void setUseSingleQuote(boolean useSingleQuote) { this.useSingleQuote = useSingleQuote; } public boolean useCdata() { return useCdata; } public void setUseCdata(boolean useSingleQuote) { this.useCdata = useSingleQuote; } }
The quickest way to implement your CDATA requirement would have been:
XStream xstream = new XStream(
{ writer.write("<[CDATA["); writer.write(text); writer.write("]]>"); }new XppDriver() {
public HierarchicalStreamWriter createWriter(Writer out) {
return new PrettyPrintWriter(out) {
protected void writeText(QuickWriter writer, String text)
}
}
}
);
I agree, that it is not too obvious, but there are a quite lot of XML output options, that we could take into account. To support any case with some kind of parameter, XStream would become even more difficult. Please comment, if this does not work for you or you have special arguments for a direct CDATA support (that would have to be implemented for any supported XML writer).