Java 如何使用 JAXB 生成 CDATA 块?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3136375/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to generate CDATA block using JAXB?
提问by Shreerang
I am using JAXB to serialize my data to XML. The class code is simple as given below. I want to produce XML that contains CDATA blocks for the value of some Args. For example, current code produces this XML:
我正在使用 JAXB 将我的数据序列化为 XML。类代码很简单,如下所示。我想为某些 Args 的值生成包含 CDATA 块的 XML。例如,当前代码生成此 XML:
<command>
<args>
<arg name="test_id">1234</arg>
<arg name="source"><html>EMAIL</html></arg>
</args>
</command>
I want to wrap the "source" arg in CDATA such that it looks like below:
我想将“源”参数包装在 CDATA 中,如下所示:
<command>
<args>
<arg name="test_id">1234</arg>
<arg name="source"><[![CDATA[<html>EMAIL</html>]]></arg>
</args>
</command>
How can I achieve this in the below code?
我怎样才能在下面的代码中实现这一点?
@XmlRootElement(name="command")
public class Command {
@XmlElementWrapper(name="args")
protected List<Arg> arg;
}
@XmlRootElement(name="arg")
public class Arg {
@XmlAttribute
public String name;
@XmlValue
public String value;
public Arg() {};
static Arg make(final String name, final String value) {
Arg a = new Arg();
a.name=name; a.value=value;
return a; }
}
回答by bdoughan
Note:I'm the EclipseLink JAXB (MOXy)lead and a member of the JAXB (JSR-222)expert group.
注意:我是EclipseLink JAXB (MOXy) 的负责人和JAXB (JSR-222)专家组的成员。
If you are using MOXy as your JAXB provider then you can leverage the @XmlCDATA
extension:
如果您使用 MOXy 作为您的 JAXB 提供程序,那么您可以利用@XmlCDATA
扩展:
package blog.cdata;
import javax.xml.bind.annotation.XmlRootElement;
import org.eclipse.persistence.oxm.annotations.XmlCDATA;
@XmlRootElement(name="c")
public class Customer {
private String bio;
@XmlCDATA
public void setBio(String bio) {
this.bio = bio;
}
public String getBio() {
return bio;
}
}
For More Information
想要查询更多的信息
回答by ra9r
Here is the code sample referenced by the site mentioned above:
这是上面提到的站点引用的代码示例:
import java.io.File;
import java.io.StringWriter;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.xml.serialize.OutputFormat;
import org.apache.xml.serialize.XMLSerializer;
import org.w3c.dom.Document;
public class JaxbCDATASample {
public static void main(String[] args) throws Exception {
// unmarshal a doc
JAXBContext jc = JAXBContext.newInstance("...");
Unmarshaller u = jc.createUnmarshaller();
Object o = u.unmarshal(...);
// create a JAXB marshaller
Marshaller m = jc.createMarshaller();
// get an Apache XMLSerializer configured to generate CDATA
XMLSerializer serializer = getXMLSerializer();
// marshal using the Apache XMLSerializer
m.marshal(o, serializer.asContentHandler());
}
private static XMLSerializer getXMLSerializer() {
// configure an OutputFormat to handle CDATA
OutputFormat of = new OutputFormat();
// specify which of your elements you want to be handled as CDATA.
// The use of the '^' between the namespaceURI and the localname
// seems to be an implementation detail of the xerces code.
// When processing xml that doesn't use namespaces, simply omit the
// namespace prefix as shown in the third CDataElement below.
of.setCDataElements(
new String[] { "ns1^foo", // <ns1:foo>
"ns2^bar", // <ns2:bar>
"^baz" }); // <baz>
// set any other options you'd like
of.setPreserveSpace(true);
of.setIndenting(true);
// create the serializer
XMLSerializer serializer = new XMLSerializer(of);
serializer.setOutputByteStream(System.out);
return serializer;
}
}
回答by NBW
As of Xerxes-J 2.9, XMLSerializer has been deprecated. The suggestion is to replace it with DOM Level 3 LSSerializer or JAXP's Transformation API for XML. Has anyone tried approach?
从 Xerxes-J 2.9 开始,不推荐使用 XMLSerializer。建议将其替换为 DOM Level 3 LSSerializer 或 JAXP 的 XML 转换 API。有没有人尝试过方法?
回答by a2ndrade
Use JAXB's Marshaller#marshal(ContentHandler)
to marshal into a ContentHandler
object. Simply override the characters
method on the ContentHandler implementation you are using (e.g. JDOM's SAXHandler
, Apache's XMLSerializer
, etc):
使用 JAXBMarshaller#marshal(ContentHandler)
来封送ContentHandler
对象。只需覆盖characters
您正在使用的 ContentHandler 实现上的方法(例如 JDOM 的SAXHandler
ApacheXMLSerializer
等):
public class CDataContentHandler extends (SAXHandler|XMLSerializer|Other...) {
// see http://www.w3.org/TR/xml/#syntax
private static final Pattern XML_CHARS = Pattern.compile("[<>&]");
public void characters(char[] ch, int start, int length) throws SAXException {
boolean useCData = XML_CHARS.matcher(new String(ch,start,length)).find();
if (useCData) super.startCDATA();
super.characters(ch, start, length);
if (useCData) super.endCDATA();
}
}
This is much betterthan using the XMLSerializer.setCDataElements(...)
method because you don't have to hardcode any list of elements. It automatically outputs CDATA blocks only when one is required.
这比使用该方法要好得多,XMLSerializer.setCDataElements(...)
因为您不必硬编码任何元素列表。它仅在需要时才自动输出 CDATA 块。
回答by fred
The following simple method adds CDATA support in JAX-B which does not support CDATA natively :
以下简单方法在 JAX-B 中添加了 CDATA 支持,而 JAX-B 本身不支持 CDATA:
- declare a custom simple type CDataStringextending string to identify the fields that should be handled via CDATA
- Create a custom CDataAdapterthat parses and print content in CDataString
- use JAXB bindings to link CDataString and you CDataAdapter. the CdataAdapter will add/remove to/from CdataStrings at Marshall/Unmarshall time
- Declare a custom character escape handlerthat does not escape character when printing CDATA strings and set this as the Marshaller CharacterEscapeEncoder
- 声明一个自定义的简单类型 CDataString扩展字符串来标识应该通过 CDATA 处理的字段
- 创建一个自定义 CDataAdapter来解析和打印 CDataString 中的内容
- 使用JAXB 绑定来链接 CDataString 和 CDataAdapter。CdataAdapter 将在 Marshall/Unmarshall 时间添加/删除 CdataStrings
- 声明一个在打印 CDATA 字符串时不转义字符的自定义字符转义处理程序,并将其设置为 Marshaller CharacterEscapeEncoder
Et voila, any CDataString element will be encapsulated with at Marshall time. At unmarshall time, the will automatically be removed.
瞧,任何 CDataString 元素都将在 Marshall 时间被封装。在解组时,将自动删除。
回答by Michael Ernst
Solution Review:
解决方案:
- The answer of fred is just a workaround which will fail while validating the content when the Marshaller is linked to a Schema because you modify only the string literal and do not create CDATA sections. So if you only rewrite the String from footo <![CDATA[foo]]>the length of the string is recognized by Xerces with 15 instead of 3.
- The MOXy solution is implementation specific and does not work only with the classes of the JDK.
- The solution with the getSerializer references to the deprecated XMLSerializer class.
- The solution LSSerializer is just a pain.
- fred 的答案只是一种解决方法,当 Marshaller 链接到架构时,它会在验证内容时失败,因为您只修改字符串文字而不创建 CDATA 部分。因此,如果您只将字符串从foo重写为<![CDATA[foo]]>,则 Xerces 会使用 15 而不是 3 识别字符串的长度。
- MOXy 解决方案是特定于实现的,不仅适用于 JDK 的类。
- 使用 getSerializer 引用已弃用的 XMLSerializer 类的解决方案。
- 解决方案 LSSerializer 只是一个痛苦。
I modified the solution of a2ndrade by using a XMLStreamWriterimplementation. This solution works very well.
我通过使用XMLStreamWriter实现修改了 a2ndrade 的解决方案。此解决方案效果很好。
XMLOutputFactory xof = XMLOutputFactory.newInstance();
XMLStreamWriter streamWriter = xof.createXMLStreamWriter( System.out );
CDataXMLStreamWriter cdataStreamWriter = new CDataXMLStreamWriter( streamWriter );
marshaller.marshal( jaxbElement, cdataStreamWriter );
cdataStreamWriter.flush();
cdataStreamWriter.close();
Thats the CDataXMLStreamWriter implementation. The delegate class simply delegates all method calls to the given XMLStreamWriter implementation.
这就是 CDataXMLStreamWriter 实现。委托类只是将所有方法调用委托给给定的 XMLStreamWriter 实现。
import java.util.regex.Pattern;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
/**
* Implementation which is able to decide to use a CDATA section for a string.
*/
public class CDataXMLStreamWriter extends DelegatingXMLStreamWriter
{
private static final Pattern XML_CHARS = Pattern.compile( "[&<>]" );
public CDataXMLStreamWriter( XMLStreamWriter del )
{
super( del );
}
@Override
public void writeCharacters( String text ) throws XMLStreamException
{
boolean useCData = XML_CHARS.matcher( text ).find();
if( useCData )
{
super.writeCData( text );
}
else
{
super.writeCharacters( text );
}
}
}
回答by Reg Whitton
For the same reasons as Michael Ernst I wasn't that happy with most of the answers here. I could not use his solution as my requirement was to put CDATA tags in a defined set of fields - as in raiglstorfer's OutputFormat solution.
出于与 Michael Ernst 相同的原因,我对这里的大多数答案并不满意。我无法使用他的解决方案,因为我的要求是将 CDATA 标记放在一组定义的字段中 - 正如在 raiglstorfer 的 OutputFormat 解决方案中一样。
My solution is to marshal to a DOM document, and then do a null XSL transform to do the output. Transformers allow you to set which elements are wrapped in CDATA tags.
我的解决方案是编组到 DOM 文档,然后进行空 XSL 转换以进行输出。Transformers 允许您设置哪些元素包含在 CDATA 标签中。
Document document = ...
jaxbMarshaller.marshal(jaxbObject, document);
Transformer nullTransformer = TransformerFactory.newInstance().newTransformer();
nullTransformer.setOutputProperty(OutputKeys.INDENT, "yes");
nullTransformer.setOutputProperty(OutputKeys.CDATA_SECTION_ELEMENTS, "myElement {myNamespace}myOtherElement");
nullTransformer.transform(new DOMSource(document), new StreamResult(writer/stream));
Further info here: http://javacoalface.blogspot.co.uk/2012/09/outputting-cdata-sections-with-jaxb.html
更多信息:http: //javacoalface.blogspot.co.uk/2012/09/outputting-cdata-sections-with-jaxb.html
回答by Paulius Matulionis
The following code will prevent from encoding CDATA elements:
以下代码将阻止对 CDATA 元素进行编码:
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
StringWriter stringWriter = new StringWriter();
PrintWriter printWriter = new PrintWriter(stringWriter);
DataWriter dataWriter = new DataWriter(printWriter, "UTF-8", new CharacterEscapeHandler() {
@Override
public void escape(char[] buf, int start, int len, boolean b, Writer out) throws IOException {
out.write(buf, start, len);
}
});
marshaller.marshal(data, dataWriter);
System.out.println(stringWriter.toString());
It will also keep UTF-8
as your encoding.
它也将保留UTF-8
为您的编码。
回答by zetzer
Just a word of warning: according to documentation of the javax.xml.transform.Transformer.setOutputProperty(...) you should use the syntax of qualified names, when indicating an element from another namespace. According to JavaDoc (Java 1.6 rt.jar):
只是一个警告:根据 javax.xml.transform.Transformer.setOutputProperty(...) 的文档,当指示来自另一个命名空间的元素时,您应该使用限定名称的语法。根据 JavaDoc(Java 1.6 rt.jar):
"(...) For example, if a URI and local name were obtained from an element defined with , then the qualified name would be "{http://xyz.foo.com/yada/baz.html}foo. Note that no prefix is used."
"(...) 例如,如果从使用 定义的元素获取 URI 和本地名称,则限定名称将为 "{ http://xyz.foo.com/yada/baz.html}foo. 请注意,没有使用前缀。”
Well this doesn't work - the implementing class from Java 1.6 rt.jar, meaning com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl interprets elements belonging to a different namespace only then correctly, when they are declared as "http://xyz.foo.com/yada/baz.html:foo", because in the implementation someone is parsing it looking for the last colon. So instead of invoking:
好吧,这不起作用 - Java 1.6 rt.jar 中的实现类,这意味着 com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl 只有在声明属于不同命名空间的元素时才能正确解释它们如“ http://xyz.foo.com/yada/baz.html:foo”,因为在实现中有人解析它寻找最后一个冒号。因此,而不是调用:
transformer.setOutputProperty(OutputKeys.CDATA_SECTION_ELEMENTS, "{http://xyz.foo.com/yada/baz.html}foo")
which should work according to JavaDoc, but ends up being parsed as "http" and "//xyz.foo.com/yada/baz.html", you must invoke
这应该根据 JavaDoc 工作,但最终被解析为“http”和“//xyz.foo.com/yada/baz.html”,您必须调用
transformer.setOutputProperty(OutputKeys.CDATA_SECTION_ELEMENTS, "http://xyz.foo.com/yada/baz.html:foo")
At least in Java 1.6.
至少在 Java 1.6 中。
回答by bluearrow
Supplement of @a2ndrade
's answer.
@a2ndrade
的答案的补充。
I find one class to extend in JDK 8. But noted that the class is in com.sun
package. You can make one copy of the code in case this class may be removed in future JDK.
我在 JDK 8 中找到了一个要扩展的类。但注意到该类在com.sun
包中。您可以制作一份代码副本,以防将来 JDK 中删除此类。
public class CDataContentHandler extends com.sun.xml.internal.txw2.output.XMLWriter {
public CDataContentHandler(Writer writer, String encoding) throws IOException {
super(writer, encoding);
}
// see http://www.w3.org/TR/xml/#syntax
private static final Pattern XML_CHARS = Pattern.compile("[<>&]");
public void characters(char[] ch, int start, int length) throws SAXException {
boolean useCData = XML_CHARS.matcher(new String(ch, start, length)).find();
if (useCData) {
super.startCDATA();
}
super.characters(ch, start, length);
if (useCData) {
super.endCDATA();
}
}
}
How to use:
如何使用:
JAXBContext jaxbContext = JAXBContext.newInstance(...class);
Marshaller marshaller = jaxbContext.createMarshaller();
StringWriter sw = new StringWriter();
CDataContentHandler cdataHandler = new CDataContentHandler(sw,"utf-8");
marshaller.marshal(gu, cdataHandler);
System.out.println(sw.toString());
Result example:
结果示例:
<?xml version="1.0" encoding="utf-8"?>
<genericUser>
<password><![CDATA[dskfj>><<]]></password>
<username>UNKNOWN::UNKNOWN</username>
<properties>
<prop2>v2</prop2>
<prop1><![CDATA[v1><]]></prop1>
</properties>
<timestamp/>
<uuid>cb8cbc487ee542ec83e934e7702b9d26</uuid>
</genericUser>