如何使用JAXB生成CDATA块?

Shr*_*ang 40 java xml 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>
Run Code Online (Sandbox Code Playgroud)

我想在CDATA中包装"source"arg,使其如下所示:

<command>
   <args>
      <arg name="test_id">1234</arg>
      <arg name="source"><[![CDATA[<html>EMAIL</html>]]></arg>
   </args>
</command>
Run Code Online (Sandbox Code Playgroud)

我怎样才能在下面的代码中实现这一点?

@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; }
    }
Run Code Online (Sandbox Code Playgroud)

bdo*_*han 28

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

}
Run Code Online (Sandbox Code Playgroud)

欲获得更多信息

  • 我不确定为什么这个回应得到了投票.它是对问题的直接回答,其中包含有关如何应用解决方案的详细说明的链接.JAXB是一个规范和兼容的实现,如MOXy包含处理诸如CDATA之类的东西的扩展. (4认同)
  • 当我试图解决这个问题时,这个链接已经不断出现了.我相信它的效果非常好,但令我困惑的一件事是我无法理解如何将解决方案插入我的客户端.每个例子都使用``main``方法来证明编组工作,但他们缺少一个如何在真实客户端使用它的部分.例如``JAXBContext jc = JAXBContext.newInstance(classes,props);``在wsdl2java生成的客户端go,因为这个JAXBContext是由jax-ws自动调用的,如果我已经正确理解的话. (4认同)
  • @locke - 这就是为什么Stack Overflow允许每个问题多个答案:). (4认同)
  • @blaiseDoughan - 我同意,大多数时候我们都在寻找能够与JAXB RI协同工作的解决方案,而无需引入和配置MOXy.我还没有找到解决方案,所有关于它的SO问题都有这个作为答案. (2认同)

a2n*_*ade 20

使用JAXB Marshaller#marshal(ContentHandler)编组成一个ContentHandler对象.只需覆盖characters您正在使用的ContentHandler实现的方法(例如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();
    }
}
Run Code Online (Sandbox Code Playgroud)

这比使用该方法要好得多,XMLSerializer.setCDataElements(...)因为您不必对任何元素列表进行硬编码.只有在需要时才会自动输出CDATA块.


小智 16

解决方案评论

  • fred的答案只是一种解决方法,当Marshaller链接到Schema时验证内容时会失败,因为您只修改字符串文字并且不创建CDATA部分.因此,如果您只将字符串从foo重写为<![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();
Run Code Online (Sandbox Code Playgroud)

那就是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 );
      }
   }
}
Run Code Online (Sandbox Code Playgroud)

  • 委托XMLStreamWriter:https://github.com/apache/cxf/blob/master/core/src/main/java/org/apache/cxf/staxutils/DelegatingXMLStreamWriter.java (3认同)

ra9*_*a9r 10

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

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;
    }
}
Run Code Online (Sandbox Code Playgroud)


Reg*_*ton 9

出于与Michael Ernst相同的原因,我对这里的大多数答案并不满意.我不能使用他的解决方案,因为我的要求是将CDATA标签放在一组定义的字段中 - 就像raiglstorfer的OutputFormat解决方案一样.

我的解决方案是编组DOM文档,然后执行null 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));
Run Code Online (Sandbox Code Playgroud)

更多信息:http://javacoalface.blogspot.co.uk/2012/09/outputting-cdata-sections-with-jaxb.html


小智 5

以下简单方法在JAX-B中添加了CDATA支持,它本身不支持CDATA:

  1. 声明一个自定义简单类型CDataString扩展字符串,以标识应通过CDATA处理的字段
  2. 创建一个自定义CDataAdapter ,用于解析和打印CDataString中的内容
  3. 使用JAXB绑定链接CDataString和CDataAdapter.在Marshall/Unmarshall时间,CdataAdapter将向/从CdataStrings添加/删除
  4. 声明一个自定义字符转义处理程序,它在打印CDATA字符串时不转义字符并将其设置为Marshaller CharacterEscapeEncoder

Et voila,任何CDataString元素都将在Marshall时封装.在解组时,会自动删除.


blu*_*row 5

@a2ndrade的答案的补充。

我在 JDK 8 中找到了一个要扩展的类。但注意到该类位于com.sunpackage.json 中。您可以复制一份代码,以防此类在将来的 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();
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

如何使用:

  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());
Run Code Online (Sandbox Code Playgroud)

结果示例:

<?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>
Run Code Online (Sandbox Code Playgroud)