如何使用 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">&lt;html>EMAIL&lt;/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 从 XML 模式生成 Java 枚举?

如何使用 JAXB 使用 Jersey 1.6 生成 JSON 输出

如何使用 JAXB 使用 Jersey 1.6 生成 JSON 输出

使用 CData 雪花源导出数据的 SSIS 块超时错误

如何指定用于 JAXB xjc 生成的类的 XmlAccessorType

如何使用 gradle 从 WSDL 和 XSD 生成类,相当于 maven-jaxb2-plugin