如何使用JAXB生成CDATA块?

发布于 2024-09-07 21:59:38 字数 1108 浏览 12 评论 0原文

我正在使用 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>

我想将“源”参数包装在 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; }
    }

I am using JAXB to serialize my data to XML. The class code is simple as given below. I want to produce XML that contains CDATA blocks for the value of some Args. For example, current code produces this XML:

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

I want to wrap the "source" arg in CDATA such that it looks like below:

<command>
   <args>
      <arg name="test_id">1234</arg>
      <arg name="source"><[![CDATA[<html>EMAIL</html>]]></arg>
   </args>
</command>

How can I achieve this in the below code?

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

寻找一个思念的角度 2024-09-14 21:59:38

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

}

了解更多信息

Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.

If you are using MOXy as your JAXB provider then you can leverage the @XmlCDATA extension:

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

}

For More Information

后知后觉 2024-09-14 21:59:38

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

Use JAXB's Marshaller#marshal(ContentHandler) to marshal into a ContentHandler object. Simply override the characters method on the ContentHandler implementation you are using (e.g. JDOM's SAXHandler, Apache's XMLSerializer, etc):

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

This is much better than using the XMLSerializer.setCDataElements(...) method because you don't have to hardcode any list of elements. It automatically outputs CDATA blocks only when one is required.

夜未央樱花落 2024-09-14 21:59:38

解决方案回顾:

  • fred 的答案只是一种解决方法,当 Marshaller 链接到架构时验证内容时会失败,因为您仅修改字符串文字而不创建 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 );
      }
   }
}

Solution Review:

  • The answer of fred is just a workaround which will fail while validating the content when the Marshaller is linked to a Schema because you modify only the string literal and do not create CDATA sections. So if you only rewrite the String from foo to <![CDATA[foo]]> the length of the string is recognized by Xerces with 15 instead of 3.
  • The MOXy solution is implementation specific and does not work only with the classes of the JDK.
  • The solution with the getSerializer references to the deprecated XMLSerializer class.
  • The solution LSSerializer is just a pain.

I modified the solution of a2ndrade by using a XMLStreamWriter implementation. This solution works very well.

XMLOutputFactory xof = XMLOutputFactory.newInstance();
XMLStreamWriter streamWriter = xof.createXMLStreamWriter( System.out );
CDataXMLStreamWriter cdataStreamWriter = new CDataXMLStreamWriter( streamWriter );
marshaller.marshal( jaxbElement, cdataStreamWriter );
cdataStreamWriter.flush();
cdataStreamWriter.close();

Thats the CDataXMLStreamWriter implementation. The delegate class simply delegates all method calls to the given XMLStreamWriter implementation.

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 );
      }
   }
}
许一世地老天荒 2024-09-14 21:59:38

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

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

Here is the code sample referenced by the site mentioned above:

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;
    }
}
薄凉少年不暖心 2024-09-14 21:59:38

出于与迈克尔·恩斯特相同的原因,我对这里的大多数答案都不满意。我无法使用他的解决方案,因为我的要求是将 CDATA 标签放入一组定义的字段中 - 正如 raigstorfer 的 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));

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

For the same reasons as Michael Ernst I wasn't that happy with most of the answers here. I could not use his solution as my requirement was to put CDATA tags in a defined set of fields - as in raiglstorfer's OutputFormat solution.

My solution is to marshal to a DOM document, and then do a null XSL transform to do the output. Transformers allow you to set which elements are wrapped in CDATA tags.

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

Further info here: http://javacoalface.blogspot.co.uk/2012/09/outputting-cdata-sections-with-jaxb.html

剪不断理还乱 2024-09-14 21:59:38

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

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

等等,任何 CDataString 元素将在马歇尔时间封装。在解组时,将自动删除。

The following simple method adds CDATA support in JAX-B which does not support CDATA natively :

  1. declare a custom simple type CDataString extending string to identify the fields that should be handled via CDATA
  2. Create a custom CDataAdapter that parses and print content in CDataString
  3. use JAXB bindings to link CDataString and you CDataAdapter. the CdataAdapter will add/remove to/from CdataStrings at Marshall/Unmarshall time
  4. Declare a custom character escape handler that does not escape character when printing CDATA strings and set this as the Marshaller CharacterEscapeEncoder

Et voila, any CDataString element will be encapsulated with at Marshall time. At unmarshall time, the will automatically be removed.

听你说爱我 2024-09-14 21:59:38

@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>

Supplement of @a2ndrade's answer.

I find one class to extend in JDK 8. But noted that the class is in com.sun package. You can make one copy of the code in case this class may be removed in future 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();
    }
  }
}

How to use:

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

Result example:

<?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>
岁吢 2024-09-14 21:59:38

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

As of Xerxes-J 2.9, XMLSerializer has been deprecated. The suggestion is to replace it with DOM Level 3 LSSerializer or JAXP's Transformation API for XML. Has anyone tried approach?

凉墨 2024-09-14 21:59:38

只是警告一句:根据 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 中调用。

Just a word of warning: according to documentation of the javax.xml.transform.Transformer.setOutputProperty(...) you should use the syntax of qualified names, when indicating an element from another namespace. According to JavaDoc (Java 1.6 rt.jar):

"(...) For example, if a URI and local name were obtained from an element defined with , then the qualified name would be "{http://xyz.foo.com/yada/baz.html}foo. Note that no prefix is used."

Well this doesn't work - the implementing class from Java 1.6 rt.jar, meaning com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl interprets elements belonging to a different namespace only then correctly, when they are declared as "http://xyz.foo.com/yada/baz.html:foo", because in the implementation someone is parsing it looking for the last colon. So instead of invoking:

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

which should work according to JavaDoc, but ends up being parsed as "http" and "//xyz.foo.com/yada/baz.html", you must invoke

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

At least in Java 1.6.

裸钻 2024-09-14 21:59:38

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

The following code will prevent from encoding CDATA elements:

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

It will also keep UTF-8 as your encoding.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文