Comment générer un bloc CDATA en utilisant 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 contenant des blocs CDATA pour la valeur de certains args. Par exemple, le code actuel produit ce XML:

  1234 <html>EMAIL</html>   

Je veux envelopper l’argument “source” dans CDATA pour qu’il ressemble à celui-ci:

   1234 <[![CDATA[EMAIL]]>   

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

 @XmlRootElement(name="command") public class Command { @XmlElementWrapper(name="args") protected List arg; } @XmlRootElement(name="arg") public class Arg { @XmlAtsortingbute public Ssortingng name; @XmlValue public Ssortingng value; public Arg() {}; static Arg make(final Ssortingng name, final Ssortingng value) { Arg a = new Arg(); a.name=name; a.value=value; return a; } } 

Remarque: je suis l’ interlocuteur EclipseLink JAXB (MOXy) et membre du groupe d’experts JAXB (JSR-222) .

Si vous utilisez MOXy comme fournisseur JAXB, vous pouvez @XmlCDATA extension @XmlCDATA :

 package blog.cdata; import javax.xml.bind.annotation.XmlRootElement; import org.eclipse.persistence.oxm.annotations.XmlCDATA; @XmlRootElement(name="c") public class Customer { private Ssortingng bio; @XmlCDATA public void setBio(Ssortingng bio) { this.bio = bio; } public Ssortingng getBio() { return bio; } } 

Pour plus d’informations

Utilisez le Marshaller#marshal(ContentHandler) de JAXB pour Marshaller#marshal(ContentHandler) dans un object ContentHandler . Remplacez simplement la méthode des characters de l’implémentation ContentHandler que vous utilisez (par exemple, SAXHandler de JDOM, SAXHandler Apache, etc.):

 public class CDataContentHandler extends (SAXHandler|XMLSerializer|Other...) { // see http://www.w3.org/TR/xml/#syntax private static final Pattern XML_CHARS = Pattern.comstack("[<>&]"); 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 bien mieux que d’utiliser la XMLSerializer.setCDataElements(...) car vous n’avez pas besoin de coder en dur une liste d’éléments. Il envoie automatiquement les blocs CDATA uniquement lorsque cela est nécessaire .

Examen de la solution:

  • La réponse de fred est simplement une solution de contournement qui échouera lors de la validation du contenu lorsque le Marshaller est lié à un schéma car vous ne modifiez que le littéral de chaîne et ne créez pas de sections CDATA. Donc, si vous ne réécrivez que la chaîne de 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 obsolète.
  • La solution LSSerializer n’est qu’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 de CDataXMLStreamWriter. La classe delegate 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 ssortingng. */ public class CDataXMLStreamWriter extends DelegatingXMLStreamWriter { private static final Pattern XML_CHARS = Pattern.comstack( "[&<>]" ); 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 ); } } } 

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

 import java.io.File; import java.io.SsortingngWriter; 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(Ssortingng[] 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 Ssortingng[] { "ns1^foo", //  "ns2^bar", //  "^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; } } 

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

Ma solution consiste à faire appel à un document DOM, puis à effectuer une transformation XSL nulle pour effectuer la sortie. Les transformateurs vous permettent de définir quels éléments sont inclus dans les 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’infos ici: http://javacoalface.blogspot.co.uk/2012/09/outputting-cdata-sections-with-jaxb.html

La méthode simple suivante ajoute la prise en charge CDATA dans JAX-B qui ne prend pas en charge nativement CDATA:

  1. déclarer une chaîne d’extension simple de type CDataSsortingng personnalisée pour identifier les champs à traiter via CDATA
  2. Créer un CDataAdapter personnalisé qui parsing et imprime le contenu dans CDataSsortingng
  3. utilisez les liaisons JAXB pour lier CDataSsortingng et vous CDataAdapter . le CdataAdapter va append / supprimer à / de CdataSsortingngs au temps de Marshall / Unmarshall
  4. Déclarez un gestionnaire d’évasion de caractères personnalisé qui n’échappe pas au caractère lors de l’impression de chaînes CDATA et définissez-le comme étant le composant CharacterEscapeEncoder de Marshaller

Et voila, tout élément CDataSsortingng sera encapsulé à l’heure de Marshall. Au moment de la suppression, la volonté sera automatiquement supprimée.

Supplément de la réponse de @a2ndrade .

Je trouve une classe à étendre dans JDK 8. Mais com.sun que la classe est dans le package com.sun . Vous pouvez faire une copie du code au cas où cette classe pourrait être supprimée dans un futur JDK.

 public class CDataContentHandler extends com.sun.xml.internal.txw2.output.XMLWriter { public CDataContentHandler(Writer writer, Ssortingng encoding) throws IOException { super(writer, encoding); } // see http://www.w3.org/TR/xml/#syntax private static final Pattern XML_CHARS = Pattern.comstack("[<>&]"); 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(); SsortingngWriter sw = new SsortingngWriter(); CDataContentHandler cdataHandler = new CDataContentHandler(sw,"utf-8"); marshaller.marshal(gu, cdataHandler); System.out.println(sw.toSsortingng()); 

Exemple de résultat:

   ><<]]> UNKNOWN::UNKNOWN  v2 <]]>   cb8cbc487ee542ec83e934e7702b9d26  

À partir de Xerxes-J 2.9, XMLSerializer est devenu obsolète. La suggestion est de le remplacer par DOM Level 3 LSSerializer ou l’API de transformation JAXP pour XML. Quelqu’un a-t-il essayé l’approche?

Le code suivant empêchera le codage des éléments CDATA:

 Marshaller marshaller = context.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8"); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); SsortingngWriter ssortingngWriter = new SsortingngWriter(); PrintWriter printWriter = new PrintWriter(ssortingngWriter); 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(ssortingngWriter.toSsortingng()); 

Il gardera également UTF-8 comme encodage.

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 nom d’URI et un nom local ont été obtenus à partir d’un élément défini avec, le nom qualifié serait alors” { 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 que com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl interprète correctement les éléments appartenant à un espace de noms différent, lorsqu’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’parsingr à 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é comme “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.