【问题标题】:How to generate CDATA block using JAXB?如何使用 JAXB 生成 CDATA 块?
【发布时间】: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; }
    }

【问题讨论】:

  • 你能找到解决这个问题的方法吗?如果是,请分享,谢谢。

标签: java xml jaxb cdata


【解决方案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;
   }

}

更多信息

【讨论】:

  • 我不知道为什么这个回复收到了反对票。它是对问题的直接回应,并附有指向如何应用解决方案的详细说明的链接。 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 );
      }
   }
}
【解决方案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 {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 元素必须在编组时定义。
    【解决方案6】:

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

    1. 声明一个自定义简单类型 CDataString 扩展字符串以标识应通过 CDATA 处理的字段
    2. 创建一个自定义 CDataAdapter,用于解析和打印 CDataString 中的内容
    3. 使用 JAXB 绑定来链接 CDataString 和您的 CDataAdapter。 CdataAdapter 将在 Marshall/Unmarshall 时间向 CdataStrings 添加/删除/删除
    4. 声明一个在打印 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 的错误,我可以使用 stackoverflow.com/a/33917172/3161688 解决该错误。谢谢!
      【解决方案8】:

      从 Xerxes-J 2.9 开始,XMLSerializer 已被弃用。建议将其替换为 DOM Level 3 LSSerializer 或 JAXP 的 XML 转换 API。有没有人尝试过?

      【讨论】:

      【解决方案9】:

      请注意:根据 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”,因为在实现中有人正在解析它以寻找最后一个冒号。所以不要调用:

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

      它应该根据 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 字段出现时,其中包含 ,然后它就搞砸了。伙计们,这样做真的很糟糕。我在这里看过几次,但我不会真的推荐它。逃跑的方法就在那里,因为你逃跑了,而不是因为你根本不想逃跑。
        猜你喜欢
        • 2012-12-21
        • 2013-01-22
        • 1970-01-01
        • 2013-05-17
        • 1970-01-01
        • 2011-09-20
        • 2020-06-10
        • 2011-08-26
        相关资源
        最近更新 更多