如何使用 JAXB 生成 CDATA 块?
Posted
技术标签:
【中文标题】如何使用 JAXB 生成 CDATA 块?【英文标题】:How to generate CDATA block using JAXB? 【发布时间】:2011-03-09 08:38:40 【问题描述】:我正在使用 JAXB 将我的数据序列化为 XML。类代码很简单,如下所示。我想为某些 Args 的值生成包含 CDATA 块的 XML。例如,当前代码生成以下 XML:
<command>
<args>
<arg name="test_id">1234</arg>
<arg name="source"><html>EMAIL</html></arg>
</args>
</command>
我想将“源”arg 包装在 CDATA 中,如下所示:
<command>
<args>
<arg name="test_id">1234</arg>
<arg name="source"><[![CDATA[<html>EMAIL</html>]]></arg>
</args>
</command>
如何在下面的代码中实现这一点?
@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;
【问题讨论】:
你能找到解决这个问题的方法吗?如果是,请分享,谢谢。 【参考方案1】:注意:我是EclipseLink JAXB (MOXy) 的负责人,也是JAXB (JSR-222) 专家组的成员。
如果您使用 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;
更多信息
http://bdoughan.blogspot.com/2010/07/cdata-cdata-run-run-data-run.html http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html【讨论】:
我不知道为什么这个回复收到了反对票。它是对问题的直接回应,并附有指向如何应用解决方案的详细说明的链接。 JAXB 是一种规范,并且兼容的实现(例如 MOXy)包含处理 CDATA 之类的扩展。 当我试图解决这个问题时,这个链接经常弹出。我相信它工作得很好,但让我困惑的一件事是我无法理解如何将解决方案插入我的客户端。每个示例都使用main
方法来证明编组是有效的,但是他们缺少如何在真实客户端中使用它的部分。例如,wsdl2java 生成的客户端中的JAXBContext jc = JAXBContext.newInstance(classes, props);
应该去哪里,因为这个 JAXBContext 是由 jax-ws 自动调用的,如果我理解正确的话。
@locke - 这就是 Stack Overflow 允许每个问题有多个答案的原因 :)。
@blaiseDoughan - 我同意,大多数时候我们正在寻找一种无需引入和配置 MOXy 即可与 JAXB RI 一起使用的解决方案。我还没有找到解决方案,所有关于它的 SO 问题都有这个答案。
@Quantas - MOXy 是 Oracle WebLogic 中的默认 JAXB 提供程序,并且作为 GlassFish 中的默认 JAXB 提供程序可用,因此该解决方案可直接用于这些用户。此外,正如您所见,没有一个解决方案是容易的。由于 JAXB (JSR-222) 是一个规范(我是专家组的成员),因此切换提供程序是人们获得他们正在寻找的扩展的一种选择。我已经发布了 2000 多个 JAXB 问题的答案,并且我尽可能尝试发布纯 JAXB 解决方案,但我不会将免责声明放在答案的顶部。【参考方案2】:
使用 JAXB 的 Marshaller#marshal(ContentHandler)
编组为 ContentHandler
对象。只需覆盖您正在使用的 ContentHandler 实现上的 characters
方法(例如 JDOM 的 SAXHandler
、Apache 的 XMLSerializer
等):
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();
这比使用XMLSerializer.setCDataElements(...)
方法好得多,因为您不必对任何元素列表进行硬编码。它仅在需要时自动输出 CDATA 块。
【讨论】:
干净简单。我将对其进行一些测试。谢谢! 我不能扩展一个 DataWriter 类并使用这个过程吗?我是默认的 contentHandler,所以我无法扩展它并使用它来解决我的问题。 @Apoorvasahay 最后,我在 JDK 8 中找到了类中的一个类。com.sun.xml.internal.txw2.output.XMLWriter
。有关详细信息,请参阅我的分析器。【参考方案3】:
解决方案审查:
fred 的答案只是一种解决方法,当 Marshaller 链接到 Schema 时验证内容时会失败,因为您只修改字符串文字而不创建 CDATA 部分。因此,如果您只将字符串从 foo 重写为 ,则 Xerces 将使用 15 而不是 3 识别字符串的长度。李> MOXy 解决方案是特定于实现的,并且不仅仅适用于 JDK 的类。 getSerializer 解决方案引用已弃用的 XMLSerializer 类。 解决方案 LSSerializer 实在是太痛苦了。我通过使用 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();
这就是 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 );
【讨论】:
委派XMLStreamWriter: github.com/apache/cxf/blob/master/core/src/main/java/org/apache/…【参考方案4】:这是上述网站引用的代码示例:
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;
【讨论】:
【参考方案5】:出于与 Michael Ernst 相同的原因,我对这里的大多数答案并不满意。我不能使用他的解决方案,因为我的要求是将 CDATA 标记放在一组定义的字段中 - 就像在 raiglstorfer 的 OutputFormat 解决方案中一样。
我的解决方案是编组到 DOM 文档,然后执行 null XSL 转换以进行输出。 Transformer 允许您设置哪些元素包含在 CDATA 标记中。
Document document = ...
jaxbMarshaller.marshal(jaxbObject, document);
Transformer nullTransformer = TransformerFactory.newInstance().newTransformer();
nullTransformer.setOutputProperty(OutputKeys.INDENT, "yes");
nullTransformer.setOutputProperty(OutputKeys.CDATA_SECTION_ELEMENTS, "myElement myNamespacemyOtherElement");
nullTransformer.transform(new DOMSource(document), new StreamResult(writer/stream));
更多信息在这里:http://javacoalface.blogspot.co.uk/2012/09/outputting-cdata-sections-with-jaxb.html
【讨论】:
这很好用。这是一个简单的解决方案,尽管 CDATA 元素必须在编组时定义。【参考方案6】:以下简单方法在 JAX-B 中添加 CDATA 支持,而 JAX-B 本身不支持 CDATA:
-
声明一个自定义简单类型 CDataString 扩展字符串以标识应通过 CDATA 处理的字段
创建一个自定义 CDataAdapter,用于解析和打印 CDataString 中的内容
使用 JAXB 绑定来链接 CDataString 和您的 CDataAdapter。 CdataAdapter 将在 Marshall/Unmarshall 时间向 CdataStrings 添加/删除/删除
声明一个在打印 CDATA 字符串时不转义字符的自定义字符转义处理程序,并将其设置为 Marshaller CharacterEscapeEncoder
等等,任何 CDataString 元素都将在 Marshall 时间被封装。在解组时,将自动删除。
【讨论】:
【参考方案7】:补充@a2ndrade
的回答。
我在 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();
使用方法:
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());
结果示例:
<?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>
【讨论】:
感谢您的回答@bluearrow。我按照这些步骤操作,但遇到了关于 com.sun.xml.internal.txw2.output.XMLWriter 的错误,我可以使用 ***.com/a/33917172/3161688 解决该错误。谢谢!【参考方案8】:从 Xerxes-J 2.9 开始,XMLSerializer 已被弃用。建议将其替换为 DOM Level 3 LSSerializer 或 JAXP 的 XML 转换 API。有没有人尝试过?
【讨论】:
我正在尝试做,我找到了一个链接:mirthcorp.com/community/fisheye/rdiff/Mirth/trunk/server/src/…【参考方案9】:请注意:根据 javax.xml.transform.Transformer.setOutputProperty(...) 的文档,当指示来自另一个命名空间的元素时,您应该使用限定名称的语法。根据JavaDoc(Java 1.6 rt.jar):
"(...) 例如,如果 URI 和本地名称是从用 定义的元素中获得的,那么限定名称将是 "http://xyz.foo.com/yada/baz.htmlfoo.注意没有使用前缀。”
这不起作用 - 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.htmlfoo")
它应该根据 JavaDoc 工作,但最终被解析为“http”和“//xyz.foo.com/yada/baz.html”,你必须调用
transformer.setOutputProperty(OutputKeys.CDATA_SECTION_ELEMENTS, "http://xyz.foo.com/yada/baz.html:foo")
至少在 Java 1.6 中。
【讨论】:
【参考方案10】:以下代码将阻止对 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());
它还会保留UTF-8
作为您的编码。
【讨论】:
然后,当其他一些非 CDATA 字段出现时,其中包含 ,然后它就搞砸了。伙计们,这样做真的很糟糕。我在这里看过几次,但我不会真的推荐它。逃跑的方法就在那里,因为你逃跑了,而不是因为你根本不想逃跑。以上是关于如何使用 JAXB 生成 CDATA 块?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 JAXB 使用 Jersey 1.6 生成 JSON 输出
如何使用 JAXB 使用 Jersey 1.6 生成 JSON 输出