如何使用JAXB生成CDATA块?

我正在使用JAXB将我的数据序列化为XML。 类代码很简单,如下所示。 我想要生成包含CDATA块的XML,以获得某些Args的值。 例如,当前的代码产生这个XML:

<command> <args> <arg name="test_id">1234</arg> <arg name="source">&lt;html>EMAIL&lt;/html></arg> </args> </command> 

我想包装CDATA中的“source”参数,使其如下所示:

 <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; } } 

注意:我是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; } } 

了解更多信息

使用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块。

解决scheme回顾

  • fred的答案只是一个解决方法,在Marshaller链接到Schema时validation内容将失败,因为您只修改string字面值而不创buildCDATA节。 所以如果你只重写string从foo<![CDATA [foo]]>string的长度被Xerces用15而不是3来识别。
  • MOXY解决scheme是特定于实现的,不能仅与JDK的类一起工作。
  • getSerializer的解决scheme引用了弃用的XMLSerializer类。
  • 解决schemeLSSerializer只是一个痛苦。

我通过使用XMLStreamWriter实现修改了a2ndrade的解决scheme。 这个解决scheme工作得很好。

 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 ); } } } 

以下是上述网站引用的代码示例:

 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; } } 

出于和Michael Ernst相同的原因,我对这里的大部分答案都不满意。 我无法使用他的解决scheme,因为我的要求是将CDATA标签放在一组定义的字段中 – 就像在raiglstorfer的OutputFormat解决scheme中一样。

我的解决scheme是封送到一个DOM文档,然后做空XSL转换做输出。 变形金刚允许你设置哪些元素被包裹在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)); 

进一步的信息在这里: http : //javacoalface.blogspot.co.uk/2012/09/outputting-cdata-sections-with-jaxb.html

以下简单的方法在本地不支持CDATA的JAX-B中添加CDATA支持:

  1. 声明一个自定义简单types的CDataString扩展string来标识应通过CDATA处理的字段
  2. 创build一个parsing和打印CDataString中的内容的自定义 CDataAdapter
  3. 使用JAXB绑定来链接CDataString和你的CDataAdapter 。 CdataAdapter将在Marshall / Unmarshall时间添加/删除CdataStrings
  4. 声明一个自定义字符转义处理程序 ,在打印CDATAstring时不会转义字符,并将其设置为Marshaller CharacterEscapeEncoder

Et瞧,任何CDataString元素将在马歇尔时间封装。 在unmarshall时间,会自动被删除。

@a2ndrade的答案补充。

我发现一个类可以在JDK 8中扩展。但是注意到这个类是在com.sun包中。 如果在将来的JDK中删除这个类,您可以创build一个代码副本。

 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> 

从Xerxes-J 2.9起,XMLSerializer已被弃用。 build议将其replace为DOM Level 3 LSSerializer或JAXP的XML Transformation API。 有没有人试过方法?

以下代码将阻止编码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作为你的编码。

只是一个警告:根据javax.xml.transform.Transformer.setOutputProperty(…)的文档,当从另一个命名空间指示一个元素时,应该使用限定名的语法。 根据JavaDoc(Java 1.6 rt.jar):

“(…)例如,如果从定义的元素获得URI和本地名称,则限定名称将是”{ http://xyz.foo.com/yada/baz.html } foo“。 请注意,不使用前缀。“

那么这是行不通的 – 来自Java 1.6的实现类rt.jar,意思是com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl当声明属于不同名称空间的元素时,才正确解释它们作为“ http://xyz.foo.com/yada/baz.html:foo ”,因为在实现中有人正在parsing它寻找最后的冒号。 所以,而不是调用:

 transformer.setOutputProperty(OutputKeys.CDATA_SECTION_ELEMENTS, "{http://xyz.foo.com/yada/baz.html}foo") 

它应该根据JavaDoc工作,但最终被parsing为“http”和“//xyz.foo.com/yada/baz.html”,您必须调用

 transformer.setOutputProperty(OutputKeys.CDATA_SECTION_ELEMENTS, "http://xyz.foo.com/yada/baz.html:foo") 

至less在Java 1.6中。