Как сгенерировать блок CDATA с помощью JAXB? - PullRequest
39 голосов
/ 29 июня 2010

Я использую JAXB для сериализации моих данных в XML.Код класса прост, как указано ниже.Я хочу создать XML, который содержит блоки CDATA для значения некоторых аргументов.Например, текущий код создает этот 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; }
    }

Ответы [ 10 ]

28 голосов
/ 15 июля 2010

Примечание: Я 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;
   }

}

Для получения дополнительной информации

20 голосов
/ 26 июля 2011

Используйте JAXB's Marshaller#marshal(ContentHandler), чтобы маршалировать в ContentHandler объект. Просто переопределите метод characters в используемой реализации ContentHandler (например, SAXHandler JDOM, XMLSerializer Apache и т. Д.):

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 только тогда, когда требуется один .

16 голосов
/ 05 мая 2012

Обзор решения:

  • Ответ fred - это просто обходной путь, который не удастся при проверке содержимого, когда маршаллер связан со схемой, потому что вы изменяете только строковый литерал и не создаете CDATAразделы.Так что если вы переписываете строку только с foo до длина строки распознается Xerces с 15 вместо 3.
  • Решение MOXy зависит от реализации и не работает только с классами JDK.
  • Решениесо ссылками getSerializer на устаревший класс XMLSerializer.
  • Решение LSSerializer - просто боль.

Я изменил решение a2ndrade с помощью реализации XMLStreamWriter ,Это решение работает очень хорошо.

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 );
      }
   }
}
10 голосов
/ 18 июля 2010

Вот пример кода, на который ссылается сайт, упомянутый выше:

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;
    }
}
9 голосов
/ 28 сентября 2012

По тем же причинам, что и Майкл Эрнст, я не был так рад большинству ответов здесь. Я не мог использовать его решение, так как мое требование состояло в том, чтобы поместить теги CDATA в определенный набор полей - как в решении OutputFormat от raiglstorfer.

Мое решение - выполнить маршализацию документа DOM, а затем выполнить нулевое 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

5 голосов
/ 05 августа 2011

Следующий простой метод добавляет поддержку CDATA в JAX-B, которая изначально не поддерживает CDATA:

  1. объявляет пользовательский простой тип CDataString , расширяющий строку, чтобы идентифицировать поля, которые должныобрабатываться через CDATA
  2. Создать пользовательский CDataAdapter , который анализирует и печатает содержимое в CDataString
  3. , использует JAXB-привязки для связи CDataString и вашего CDataAdapter .CdataAdapter будет добавлять / удалять CdataStrings во время Marshall / Unmarshall
  4. Объявить пользовательский обработчик escape-символов , который не экранирует символ при печати строк CDATA, и установить его как Marshaller CharacterEscapeEncoder

И так далее, любой элемент CDataString будет инкапсулирован во время Маршалла.В нерабочее время оно будет автоматически удалено.

4 голосов
/ 18 сентября 2016

Дополнение ответа @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>
2 голосов
/ 05 мая 2011

Начиная с Xerxes-J 2.9, XMLSerializer устарел. Предлагаем заменить его на DOM Level 3 LSSerializer или JAXP Transformation API для XML. Кто-нибудь пробовал подход?

0 голосов
/ 12 мая 2016

Просто предупреждающее слово: в соответствии с документацией 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.

0 голосов
/ 22 августа 2014

Следующий код не позволит кодировать элементы 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 в качестве вашей кодировки.

...