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"><html>EMAIL</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; }
}
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
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 .
Examen de la solution:
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 );
}
}
}
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;
}
}
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
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:
Et voila, tout élément CDataString sera encapsulé au moment Marshall. En cas de désaccord, le sera automatiquement supprimé.
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>
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?
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.
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.