【问题标题】:JAXB Marshalling Unmarshalling with CDATAJAXB 编组 使用 CDATA 解组
【发布时间】:2012-12-21 01:32:02
【问题描述】:

我正在尝试使用 JAXB 进行编组。

我的输出是这样的:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
    <name>&lt;![CDATA[&lt;h1&gt;kshitij&lt;/h1&gt;]]&gt;</name>
    <surname>&lt;h1&gt;solanki&lt;/h1&gt;</surname>
    <id>&lt;h1&gt;1&lt;/h1&gt;</id>
</root>

...但我需要这样的输出:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <root>
        <name><![CDATA[<h1>kshitij</h1>]]></name>
        <surname><![CDATA[<h1>solanki</h1>]]></surname>
        <id><![CDATA[0]]></id>
    </root>

我正在使用以下代码来执行此操作。

如果我取消注释代码,我会得到 PropertyBindingException。没有它我可以编译,但我没有得到所需的确切输出。

  package com.ksh.templates;

import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import com.sun.xml.bind.marshaller.CharacterEscapeHandler;

public class MainCDATA {
    public static void main(String args[])
    {
        try
        {
            String name = "<h1>kshitij</h1>";
            String surname = "<h1>solanki</h1>";
            String id = "<h1>1</h1>";
            
            TestingCDATA cdata = new TestingCDATA();
            cdata.setId(id);
            cdata.setName(name);
            cdata.setSurname(surname);
            
            JAXBContext jaxbContext = JAXBContext.newInstance(TestingCDATA.class);
            Marshaller marshaller = jaxbContext.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
            
            marshaller.setProperty(CharacterEscapeHandler.class.getName(), new CharacterEscapeHandler() { 
                public void escape(char[] ac, int i, int j, boolean flag,
                Writer writer) throws IOException {
                writer.write( ac, i, j ); }
                });
            StringWriter stringWriter = new StringWriter(); 
            marshaller.marshal(cdata, stringWriter);
            System.out.println(stringWriter.toString());
        } catch (Exception e) {
            System.out.println(e);
        }       
    }
}

我的 bean 看起来像这样:

package com.ksh.templates;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import com.sun.xml.txw2.annotation.XmlCDATA;

@XmlRootElement(name = "root")
@XmlAccessorType(XmlAccessType.FIELD)
public class TestingCDATA {

    @XmlElement
    @XmlJavaTypeAdapter(value = AdaptorCDATA.class)
    private String name;
    @XmlElement
    @XmlJavaTypeAdapter(value = AdaptorCDATA.class)
    private String surname;
    
    @XmlCDATA
    public String getName() {
        return name;
    }
    @XmlCDATA
    public void setName(String name) {
        this.name = name;
    }
    @XmlCDATA
    public String getSurname() {
        return surname;
    }
    @XmlCDATA
    public void setSurname(String surname) {
        this.surname = surname;
    }
}

适配器类

public class AdaptorCDATA extends XmlAdapter<String, String> {

    @Override
    public String marshal(String arg0) throws Exception {
        return "<![CDATA[" + arg0 + "]]>";
    }
    @Override
    public String unmarshal(String arg0) throws Exception {
        return arg0;
    }
}

【问题讨论】:

    标签: java xml jaxb


    【解决方案1】:

    除了@bdoughan 的回答。支持 CDATA 的字符转义处理程序

    import com.sun.xml.bind.marshaller.CharacterEscapeHandler;
    
    import java.io.IOException;
    import java.io.Writer;
    
    /**
     * This class is a modern version of JAXB MinimumEscapeHandler with CDATA support
     *
     * @author me
     * @see com.sun.xml.bind.marshaller.MinimumEscapeHandler
     */
    public class CdataEscapeHandler implements CharacterEscapeHandler {
    
        private CdataEscapeHandler() {
        }  // no instanciation please
    
        public static final CharacterEscapeHandler theInstance = new CdataEscapeHandler();
    
        @Override
        public void escape(char[] ch, int start, int length, boolean isAttVal, Writer out) throws IOException {
            // avoid calling the Writerwrite method too much by assuming
            // that the escaping occurs rarely.
            // profiling revealed that this is faster than the naive code.
            int limit = start + length;
            for (int i = start; i < limit; i++) {
                if (!isAttVal
                        && i <= limit - 12
                        && ch[i] == '<'
                        && ch[i + 1] == '!'
                        && ch[i + 2] == '['
                        && ch[i + 3] == 'C'
                        && ch[i + 4] == 'D'
                        && ch[i + 5] == 'A'
                        && ch[i + 6] == 'T'
                        && ch[i + 7] == 'A'
                        && ch[i + 8] == '[') {
                    int cdataEnd = i + 8;
                    for (int k = i + 9; k < limit - 2; k++) {
                        if (ch[k] == ']'
                                && ch[k + 1] == ']'
                                && ch[k + 2] == '>') {
                            cdataEnd = k + 2;
                            break;
                        }
                    }
                    out.write(ch, start, cdataEnd + 1);
                    if (cdataEnd == limit - 1) return;
                    start = i = cdataEnd + 1;
                }
                char c = ch[i];
                if (c == '&' || c == '<' || c == '>' || c == '\r' || (c == '\"' && isAttVal)) {
                    if (i != start)
                        out.write(ch, start, i - start);
                    start = i + 1;
                    switch (ch[i]) {
                        case '&':
                            out.write("&amp;");
                            break;
                        case '<':
                            out.write("&lt;");
                            break;
                        case '>':
                            out.write("&gt;");
                            break;
                        case '\"':
                            out.write("&quot;");
                            break;
                    }
                }
            }
    
            if (start != limit)
                out.write(ch, start, limit - start);
        }
    }

    【讨论】:

      【解决方案2】:

      请注意:我是EclipseLink JAXB (MOXy) 的负责人,也是JAXB (JSR-222) 专家组的成员。

      如果您使用 MOXy 作为您的 JAXB (JSR-222) 提供程序,那么您可以将 @XmlCDATA 扩展用于您的用例。

      @XmlCDATA 注释用于指示您希望将字段/属性的内容包装在 CDATA 部分中。 @XmlCDATA注解可以和@XmlElement结合使用。

      package forum14193944;
      
      import javax.xml.bind.annotation.*;
      import org.eclipse.persistence.oxm.annotations.XmlCDATA;
      
      @XmlRootElement
      @XmlAccessorType(XmlAccessType.FIELD)
      public class Root {
      
          @XmlCDATA
          private String name;
      
          @XmlCDATA
          private String surname;
      
          @XmlCDATA
          private String id;
      
      }
      

      jaxb.properties

      要将 MOXy 用作您的 JAXB 提供程序,您需要使用以下条目添加名为 jaxb.properties 的文件。

      javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
      

      演示

      下面是一些演示代码来证明一切正常。

      package forum14193944;
      
      import java.io.File;
      import javax.xml.bind.*;
      
      public class Demo {
      
          public static void main(String[] args) throws Exception {
              JAXBContext jc = JAXBContext.newInstance(Root.class);
      
              Unmarshaller unmarshaller = jc.createUnmarshaller();
              File xml = new File("src/forum14193944/input.xml");
              Root root = (Root) unmarshaller.unmarshal(xml);
      
              Marshaller marshaller = jc.createMarshaller();
              marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
              marshaller.marshal(root, System.out);
          }
      
      }
      

      input.xml/Output

      以下是运行演示代码的输入和输出。

      <?xml version="1.0" encoding="UTF-8"?>
      <root>
         <name><![CDATA[<h1>kshitij</h1>]]></name>
         <surname><![CDATA[<h1>solanki</h1>]]></surname>
         <id><![CDATA[0]]></id>
      </root>
      

      更多信息

      【讨论】:

      • 有什么方法可以在不添加moxy的情况下做到这一点。 ?就像我正在关注下面的链接,但我没有成功。 odedpeer.blogspot.in/2010/07/…
      • @kshitij - 你能发布你当前方法看到的堆栈跟踪吗?
      • 当我取消注释代码时出现错误。 javax.xml.bind.PropertyException:名称:com.sun.xml.bind.marshaller.CharacterEscapeHandler 值:com.ksh.templates.MainCDATA$1@8b819f
      • 是在您的示例代码中使用包含internal 的属性名称,还是根据堆栈跟踪不使用internal
      • @kshitij - 您能否发布包含try 块(包含端口)的整个类?
      【解决方案3】:

      我登陆此页面试图找到类似问题的解决方案,但我找到了解决此问题的另一种方法。 解决此问题的一种方法是将 XML 作为 SAX2 事件发送到处理程序,然后在处理程序中编写逻辑以将 CDATA 标记添加到 XML。这种方法不需要添加任何注释。在从 XSD 生成要编组的类的情况下很有用。

      假设您在从 XSD 生成的类中有一个字符串字段,该字段将被编组,并且该字符串字段包含要放入 CDATA 标记内的特殊字符。

      @XmlRootElement
      public class TestingCDATA{
          public String xmlContent;
      
      }
      

      我们将从搜索一个合适的类开始,该类的方法可以在我们的内容处理程序中被覆盖。一个这样的类是 com.sun.xml.txw2.output 包中的 XMLWriter,它在 jdk 1.7 和 1.8 中可用

      import com.sun.xml.txw2.output.XMLWriter;
      import org.xml.sax.SAXException;
      
      import java.io.IOException;
      import java.io.Writer;
      import java.util.regex.Pattern;
      
      public class CDATAContentHandler extends 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();
              }
          }
      }
      

      我们正在重写 characters 方法,使用正则表达式来检查是否包含任何特殊字符。如果找到它们,那么我们会在它们周围放置 CDATA 标记。在这种情况下,XMLWriter 负责添加 CDATA 标记。

      我们将使用以下代码进行编组:

      public String addCDATAToXML(TestingCDATA request) throws FormatException {
          try {
              JAXBContext jaxbContext = JAXBContext.newInstance(TestingCDATA.class);
              Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
              StringWriter sw = new StringWriter();
              CDATAContentHandler cDataContentHandler = new CDATAContentHandler(sw, "UTF-8");
              jaxbMarshaller.marshal(request, cDataContentHandler);
              return sw.toString();
          } catch (JAXBException | IOException e) {
              throw new FormatException("Unable to add CDATA for request", e);
          }
      }
      

      如果我们按如下所述传递一个要编组的请求,这将编组对象并返回 XML。

      TestingCDATA request=new TestingCDATA();
      request.xmlContent="<?xml>";
      
      System.out.println(addCDATAToXML(request)); // Would return the following String
      
      Output- 
      
      <?xml version="1.0" encoding="UTF-8"?>
      <testingCDATA>
      <xmlContent><![CDATA[<?xml>]]></xmlContent>
      </testingCDATA>
      

      【讨论】:

        【解决方案4】:

        很抱歉挖掘了这个问题,并发布了一个新答案(我的代表还不够高,无法发表评论......)。 我遇到了同样的问题,我尝试了 Blaise Doughan 的答案,但从我的测试来看,要么它没有涵盖所有情况,要么我在某处做错了。

        marshaller.setProperty(CharacterEscapeHandler.class.getName(), new CharacterEscapeHandler() { @Override public void escape(char[] ac, int i, int j, boolean flag, Writer writer) throws IOException { writer.write(ac, i, j); } });

        根据我的测试,无论您是否在属性上使用@XmlJavaTypeAdapter(AdapterCDATA.class) 注释,此代码都会删除所有转义...

        为了解决这个问题,我实现了以下CharacterEscapeHandler

        公共类 CDataAwareUtfEncodedXmlCharacterEscapeHandler 实现 CharacterEscapeHandler { private static final char[] cDataPrefix = "".toCharArray(); 公共静态最终 CDataAwareUtfEncodedXmlCharacterEscapeHandler 实例 = new CDataAwareUtfEncodedXmlCharacterEscapeHandler(); 私人 CDataAwareUtfEncodedXmlCharacterEscapeHandler() { } @覆盖 public void escape(char[] ch, int start, int length, boolean isAttVal, Writer out) 抛出 IOException { 布尔 isCData = 长度 > cDataPrefix.length + cDataSuffix.length; 如果(isCData){ for (int i = 0, j = start; i = 0; --i, --j) { if (cDataSuffix[i] != ch[j]) { isCData = 假; 休息; } } } } 如果(isCData){ out.write(ch, start, length); } 别的 { MinimumEscapeHandler.theInstance.escape(ch, start, length, isAttVal, out); } } }

        如果您的编码不是 UTF*,您可能不想调用 MinimumEscapeHandler,而是调用 NioEscapeHandler 甚至 DumbEscapeHandler。

        【讨论】:

          【解决方案5】:

          com.sun.internal 不适用于 play2,但可以使用

          private static String marshal(YOurCLass xml){
              try{
                  StringWriter stringWritter = new StringWriter();
                  Marshaller marshaller = JAXBContext.newInstance(YourCLass.class).createMarshaller();
                  marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
                  marshaller.setProperty(Marshaller.JAXB_ENCODING, "ISO-8859-1");
                  marshaller.marshal(xml, stringWritter);
                  return stringWritter.toString().replaceAll("&lt;", "<").replaceAll("&gt;", ">");
              }
              catch(JAXBException e){
                  throw new RuntimeException(e);
              }
          }
          

          【讨论】:

          • 这就是所有的乐趣和游戏,直到你真正拥有一个包含'&gt;' 字符的String。首先它将被编组器正确转换为"&amp;gt;"。接下来,您将用 '&gt;' 替换它,从而破坏 xml。
          【解决方案6】:
              @Test
              public void t() throws Exception {
                  JAXBContext jc = JAXBContext.newInstance(Root.class);
                  Marshaller marshaller = jc.createMarshaller();
                  marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
                  Root root = new Root();
                  root.name = "<p>Jorge & Mary</p>";
                  marshaller.marshal(root, System.out);
              }
              @XmlRootElement
              @XmlAccessorType(XmlAccessType.FIELD)
              public static class Root {
                  @XmlCDATA
                  private String name;
              }
              /* WHAT I SEE IN THE CONSOLE
               * 
          <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
          <root>
              <name>&lt;p&gt;Jorge &amp; Mary&lt;/p&gt;</name>
          </root>
               */
          

          【讨论】:

          • 你在这里使用什么 JAXB 实现?您能否更详细地说明获得此结果所需的配置?
          【解决方案7】:

          您可以执行以下操作:

          适配器CDATA

          package forum14193944;
          
          import javax.xml.bind.annotation.adapters.XmlAdapter;
          
          public class AdapterCDATA extends XmlAdapter<String, String> {
          
              @Override
              public String marshal(String arg0) throws Exception {
                  return "<![CDATA[" + arg0 + "]]>";
              }
              @Override
              public String unmarshal(String arg0) throws Exception {
                  return arg0;
              }
          
          }
          

          @XmlJavaTypeAdapter 注解用于指定应使用XmlAdapter

          package forum14193944;
          
          import javax.xml.bind.annotation.*;
          import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
          
          @XmlRootElement
          @XmlAccessorType(XmlAccessType.FIELD)
          public class Root {
          
              @XmlJavaTypeAdapter(AdapterCDATA.class)
              private String name;
          
              @XmlJavaTypeAdapter(AdapterCDATA.class)
              private String surname;
          
              @XmlJavaTypeAdapter(AdapterCDATA.class)
              private String id;
          
          }
          

          演示

          我必须将 System.out 包装在 OutputStreamWriter 中才能获得所需的效果。另请注意,设置CharacterEscapeHandler 意味着它负责该Marshaller 的所有转义处理。

          package forum14193944;
          
          import java.io.*;
          import javax.xml.bind.*;
          import com.sun.xml.bind.marshaller.*;
          
          public class Demo {
          
              public static void main(String[] args) throws Exception {
                  JAXBContext jc = JAXBContext.newInstance(Root.class);
          
                  Unmarshaller unmarshaller = jc.createUnmarshaller();
                  File xml = new File("src/forum14193944/input.xml");
                  Root root = (Root) unmarshaller.unmarshal(xml);
          
                  Marshaller marshaller = jc.createMarshaller();
                  marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
                  marshaller.setProperty(CharacterEscapeHandler.class.getName(),
                          new CharacterEscapeHandler() {
                              @Override
                              public void escape(char[] ac, int i, int j, boolean flag,
                                      Writer writer) throws IOException {
                                  writer.write(ac, i, j);
                              }
                          });
                  marshaller.marshal(root, new OutputStreamWriter(System.out));
              }
          
          }
          

          input.xml/Output

          <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
          <root>
              <name><![CDATA[<h1>kshitij</h1>]]></name>
              <surname><![CDATA[<h1>solanki</h1>]]></surname>
              <id><![CDATA[0]]></id>
          </root>
          

          【讨论】:

          • 我已经试过了。它不工作。我的 jdk 版本是 1.7,所以我找不到 com.sun.xml.internal.bind.marshaller.CharacterEscapeHandler。在 jdk 1.6 中我可以找到该类,但它仍然给出错误。
          • @blaise-doughan,你的“包论坛14193944;”闻起来很香!!
          • @BlaiseDoughan,您的 AdapterCDATA 给了我以下 xml:<![CDATA[sometext]]>,所以因为 <和>这段代码被误解了。
          • 如果您已经声明了 CDATA 适配器。那么为什么要增加一个转义处理程序呢?您是否展示了 2 个单独的解决方案?还是我错过了适配器和转义处理程序之间的链接?
          • 这将禁用 all 字段上的字符转义,而不仅仅是 cdata 字段。使用起来很危险。
          猜你喜欢
          • 2011-11-24
          • 1970-01-01
          • 2013-05-17
          • 1970-01-01
          • 2014-09-29
          • 2014-10-07
          • 1970-01-01
          • 2015-03-25
          • 2012-03-05
          相关资源
          最近更新 更多