如何让XStream生成缩进一致的CDATA内容XML输出?
解决XStream生成XML时CDATA内容缩进问题
使用XStream结合自定义CdataXppDriver生成XML时,CDATA块内的多行内容未按XML层级缩进,不符合格式要求。
当前输出
<java.util.Arrays_-ArrayList> <a class="sct.commons.xml.TestObject-array"> <sct.commons.xml.TestObject> <normalText>123</normalText> <cdataText><![CDATA[456]]></cdataText> </sct.commons.xml.TestObject> <sct.commons.xml.TestObject> <normalText>abc</normalText> <cdataText><![CDATA[def]]></cdataText> </sct.commons.xml.TestObject> <sct.commons.xml.TestObject> <normalText>some text</normalText> <cdataText><![CDATA[Lorem ipsum dolor sit amet consectetur adipisici elit sed eiusmod tempor incidunt ut labore et dolore magna aliqua ]]></cdataText> </sct.commons.xml.TestObject> </a> </java.util.Arrays_-ArrayList>
期望效果
最小方案(CDATA内多行缩进对齐)
<java.util.Arrays_-ArrayList> <a class="sct.commons.xml.TestObject-array"> <sct.commons.xml.TestObject> <normalText>123</normalText> <cdataText><![CDATA[456]]></cdataText> </sct.commons.xml.TestObject> <sct.commons.xml.TestObject> <normalText>abc</normalText> <cdataText><![CDATA[def]]></cdataText> </sct.commons.xml.TestObject> <sct.commons.xml.TestObject> <normalText>some text</normalText> <cdataText><![CDATA[Lorem ipsum dolor sit amet consectetur adipisici elit sed eiusmod tempor incidunt ut labore et dolore magna aliqua ]]></cdataText> </sct.commons.xml.TestObject> </a> </java.util.Arrays_-ArrayList>
最优方案(CDATA块独立换行+内部缩进)
<java.util.Arrays_-ArrayList> <a class="sct.commons.xml.TestObject-array"> <sct.commons.xml.TestObject> <normalText>123</normalText> <cdataText><![CDATA[456]]></cdataText> </sct.commons.xml.TestObject> <sct.commons.xml.TestObject> <normalText>abc</normalText> <cdataText><![CDATA[def]]></cdataText> </sct.commons.xml.TestObject> <sct.commons.xml.TestObject> <normalText>some text</normalText> <cdataText> <![CDATA[ Lorem ipsum dolor sit amet consectetur adipisici elit sed eiusmod tempor incidunt ut labore et dolore magna aliqua ]]> </cdataText> </sct.commons.xml.TestObject> </a> </java.util.Arrays_-ArrayList>
当前核心代码
XML生成代码
XStream xStream = new XStream(cdataXppDriver); String xml = xStream.toXML(model);
自定义CdataXppDriver代码
public class CdataXppDriver extends XppDriver { private final List<String> CDATA_FIELDS; public CdataXppDriver(List<String> cdataFields) { super(); CDATA_FIELDS = new ArrayList<>(); if (cdataFields != null) { CDATA_FIELDS.addAll(cdataFields); } } @Override public HierarchicalStreamWriter createWriter(Writer out) { return new PrettyPrintWriter(out, getNameCoder()) { boolean cdata = false; @Override public void startNode(String name) { super.startNode(name); cdata = CDATA_FIELDS.contains(name); } @Override protected void writeText(QuickWriter writer, String text) { if (cdata) { writer.write("<![CDATA["); writer.write(text); writer.write("]]>"); } else { super.writeText(writer, text); } } }; } }
解决方案
最小方案实现
修改PrettyPrintWriter的writeText方法,对CDATA内的多行文本添加当前层级的缩进:
@Override public HierarchicalStreamWriter createWriter(Writer out) { return new PrettyPrintWriter(out, getNameCoder()) { boolean cdata = false; // 获取当前CDATA内容的缩进字符串 private String getCurrentIndent() { StringBuilder indent = new StringBuilder(); for (int i = 0; i < getDepth(); i++) { indent.append(getIndent()); } // 额外增加一层缩进匹配cdataText节点的层级 indent.append(getIndent()); return indent.toString(); } @Override public void startNode(String name) { super.startNode(name); cdata = CDATA_FIELDS.contains(name); } @Override protected void writeText(QuickWriter writer, String text) { if (cdata) { writer.write("<![CDATA["); // 拆分多行文本并添加缩进 String[] lines = text.split(System.lineSeparator()); for (int i = 0; i < lines.length; i++) { if (i > 0) { writer.write(System.lineSeparator()); writer.write(getCurrentIndent()); } writer.write(lines[i]); } writer.write("]]>"); } else { super.writeText(writer, text); } } }; }
最优方案实现
不仅处理CDATA内部缩进,还将CDATA块独立换行,与父节点层级对齐:
@Override public HierarchicalStreamWriter createWriter(Writer out) { return new PrettyPrintWriter(out, getNameCoder()) { boolean cdata = false; // 获取当前节点的缩进字符串 private String getCurrentNodeIndent() { StringBuilder indent = new StringBuilder(); for (int i = 0; i < getDepth(); i++) { indent.append(getIndent()); } return indent.toString(); } // 获取CDATA内容的缩进字符串(比当前节点多一层) private String getCdataContentIndent() { return getCurrentNodeIndent() + getIndent(); } @Override public void startNode(String name) { super.startNode(name); cdata = CDATA_FIELDS.contains(name); } @Override protected void writeText(QuickWriter writer, String text) { if (cdata) { // 换行并添加当前节点缩进,写入CDATA开头 writer.write(System.lineSeparator()); writer.write(getCdataContentIndent()); writer.write("<![CDATA["); // 拆分多行文本并添加内容缩进 String[] lines = text.split(System.lineSeparator()); for (int i = 0; i < lines.length; i++) { writer.write(System.lineSeparator()); writer.write(getCdataContentIndent()); writer.write(lines[i]); } // 换行并添加内容缩进,写入CDATA结尾 writer.write(System.lineSeparator()); writer.write(getCdataContentIndent()); writer.write("]]>"); // 换行并添加当前节点缩进,闭合标签前的换行 writer.write(System.lineSeparator()); writer.write(getCurrentNodeIndent()); } else { super.writeText(writer, text); } } }; }
说明
getDepth():获取当前XML节点的层级深度,用于计算缩进次数getIndent():获取PrettyPrintWriter默认的缩进字符串(默认是两个空格,可通过构造函数自定义)- 两种方案都兼容单行CDATA内容的格式化,不会破坏原有单行输出的格式
内容的提问来源于stack exchange,提问作者Jochen
相关产品推荐
相关产品推荐

