web-dev-qa-db-fra.com

Comment générer un bloc CDATA à l'aide de JAXB?

J'utilise JAXB pour sérialiser mes données en XML. Le code de classe est simple comme indiqué ci-dessous. Je veux produire du XML qui contient des blocs CDATA pour la valeur de certains Args. Par exemple, le code actuel produit ce XML:

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

Je veux envelopper l'argument "source" dans CDATA de sorte qu'il ressemble à ci-dessous:

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

Comment puis-je y parvenir dans le code ci-dessous?

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

Remarque: Je suis le EclipseLink JAXB (MOXy) chef de file et membre du groupe d'experts JAXB (JSR-222) .

Si vous utilisez MOXy comme fournisseur JAXB, vous pouvez tirer parti de @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;
   }

}

Pour plus d'informations

28
bdoughan

Utilisez Marshaller#marshal(ContentHandler) de JAXB pour le marshaler dans un objet ContentHandler . Remplacez simplement la méthode characters sur l'implémentation ContentHandler que vous utilisez (par exemple SAXHandler de JDOM, XMLSerializer d'Apache, 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();
    }
}

C'est beaucoup mieux que d'utiliser la méthode XMLSerializer.setCDataElements(...) car vous n'avez pas à coder en dur une liste d'éléments. Il ne sort automatiquement les blocs CDATA que lorsqu'un seul est requis .

20
a2ndrade

Examen de la solution:

  • La réponse de fred n'est qu'une solution de contournement qui échouera lors de la validation du contenu lorsque le Marshaller est lié à un schéma car vous modifiez uniquement le littéral de chaîne et ne créez pas de sections CDATA. Donc, si vous réécrivez uniquement la chaîne de foo à <! [CDATA [foo]]> la longueur de la chaîne est reconnue par Xerces avec 15 au lieu de 3.
  • La solution MOXy est spécifique à l'implémentation et ne fonctionne pas uniquement avec les classes du JDK.
  • La solution avec les références getSerializer à la classe XMLSerializer déconseillée.
  • La solution LSSerializer est juste une douleur.

J'ai modifié la solution de a2ndrade en utilisant une implémentation XMLStreamWriter. Cette solution fonctionne très bien.

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

C'est l'implémentation CDataXMLStreamWriter. La classe déléguée délègue simplement tous les appels de méthode à l'implémentation XMLStreamWriter donnée.

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 );
      }
   }
}
16
Michael Ernst

Voici l'exemple de code référencé par le site mentionné ci-dessus:

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

Pour les mêmes raisons que Michael Ernst, je n'étais pas très satisfait de la plupart des réponses ici. Je ne pouvais pas utiliser sa solution car mon exigence était de mettre des balises CDATA dans un ensemble défini de champs - comme dans la solution OutputFormat de raiglstorfer.

Ma solution consiste à marshaler vers un document DOM, puis à effectuer une transformation XSL nulle pour effectuer la sortie. Les transformateurs vous permettent de définir quels éléments sont enveloppés dans des balises 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));

Plus d'informations ici: http://javacoalface.blogspot.co.uk/2012/09/outputting-cdata-sections-with-jaxb.html

9
Reg Whitton

La méthode simple suivante ajoute la prise en charge CDATA dans JAX-B qui ne prend pas en charge CDATA de manière native:

  1. déclarer une chaîne d'extension type simple personnalisé CDataString pour identifier les champs qui doivent être traités via CDATA
  2. Créer un CDataAdapter personnalisé qui analyse et imprime le contenu dans CDataString
  3. utilisez liaisons JAXB pour lier CDataString et vous CDataAdapter. le CdataAdapter ajoutera/supprimera à/de CdataStrings au moment Marshall/Unmarshall
  4. Déclarez un gestionnaire d'échappement de caractères personnalisé qui n'échappe pas au caractère lors de l'impression de chaînes CDATA et définissez-le comme Marshaller CharacterEscapeEncoder

Et voila, tout élément CDataString sera encapsulé au moment Marshall. En cas de désaccord, le sera automatiquement supprimé.

5
fred

Supplément de @a2ndrade réponse.

Je trouve une classe à étendre dans JDK 8. Mais j'ai noté que la classe est en com.Sun paquet. Vous pouvez faire une copie du code au cas où cette classe pourrait être supprimée dans le futur 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();
    }
  }
}

Comment utiliser:

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

Exemple de résultat:

<?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>
4
bluearrow

Depuis Xerxes-J 2.9, XMLSerializer est obsolète. La suggestion est de le remplacer par DOM Level 3 LSSerializer ou l'API de transformation de JAXP pour XML. Quelqu'un at-il essayé l'approche?

2
NBW

Juste un mot d'avertissement: selon la documentation de javax.xml.transform.Transformer.setOutputProperty (...), vous devez utiliser la syntaxe des noms qualifiés, lorsque vous indiquez un élément d'un autre espace de noms. Selon JavaDoc (Java 1.6 rt.jar):

"(...) Par exemple, si un URI et un nom local ont été obtenus à partir d'un élément défini avec, alors le nom qualifié serait" { http://xyz.foo.com/yada/baz.html } foo. Notez qu'aucun préfixe n'est utilisé. "

Eh bien, cela ne fonctionne pas - la classe d'implémentation de Java 1.6 rt.jar, ce qui signifie com.Sun.org.Apache.xalan.internal.xsltc.trax.TransformerImpl interprète les éléments appartenant à un autre l'espace de noms seulement alors correctement, quand ils sont déclarés comme " http://xyz.foo.com/yada/baz.html:foo ", car dans l'implémentation, quelqu'un est en train de l'analyser à la recherche du dernier deux-points Donc, au lieu d'invoquer:

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

qui devrait fonctionner selon JavaDoc, mais finit par être analysé en tant que "http" et "//xyz.foo.com/yada/baz.html", vous devez invoquer

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

Au moins en Java 1.6.

0
zetzer

Le code suivant empêchera de coder les éléments 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());

Il conservera également UTF-8 comme encodage.

0
Paulius Matulionis