Come generare un blocco CDATA usando JAXB?

Sto usando JAXB per serializzare i miei dati in XML. Il codice class è semplice come indicato di seguito. Voglio produrre XML che contenga blocchi CDATA per il valore di alcuni Arg. Ad esempio, il codice corrente produce questo XML:

  1234 <html>EMAIL</html>   

Voglio racchiudere l’argomento “source” in CDATA in modo che sembri di seguito:

   1234 <[![CDATA[EMAIL]]>   

Come posso ottenere questo nel codice qui sotto?

 @XmlRootElement(name="command") public class Command { @XmlElementWrapper(name="args") protected List 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; } } 

Nota: sono il capo di EclipseLink JAXB (MOXy) e un membro del gruppo di esperti JAXB (JSR-222) .

Se si utilizza MOXy come provider JAXB, è ansible sfruttare l’estensione @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; } } 

Per maggiori informazioni

Usa Marshaller#marshal(ContentHandler) di JAXB Marshaller#marshal(ContentHandler) per effettuare il marshalling in un object ContentHandler . Basta sovrascrivere il metodo dei characters sull’implementazione di SAXHandler si sta utilizzando (ad es. SAXHandler di SAXHandler , SAXHandler Apache, ecc.):

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

Questo è molto meglio dell’utilizzo del metodo XMLSerializer.setCDataElements(...) perché non è necessario codificare manualmente alcun elenco di elementi. Esegue automaticamente l’output dei blocchi CDATA solo quando ne è richiesto uno .

Recensione della soluzione:

  • La risposta di fred è solo una soluzione alternativa che fallirà durante la convalida del contenuto quando Marshaller è collegato a uno schema perché si modifica solo la stringa letterale e non si creano sezioni CDATA. Quindi, se riscrivi solo la stringa da foo a la lunghezza della stringa viene riconosciuta da Xerces con 15 anziché 3.
  • La soluzione MOXy è specifica per l’implementazione e non funziona solo con le classi del JDK.
  • La soluzione con i riferimenti getSerializer alla class XMLSerializer deprecata.
  • La soluzione LSSerializer è solo un dolore.

Ho modificato la soluzione di a2ndrade utilizzando un’implementazione XMLStreamWriter . Questa soluzione funziona molto bene.

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

Questa è l’implementazione di CDataXMLStreamWriter. La class delegato semplicemente delega tutte le chiamate di metodo alla specifica implementazione di 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 ); } } } 

Ecco l’esempio di codice a cui fa riferimento il sito menzionato sopra:

 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", //  "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; } } 

Per gli stessi motivi di Michael Ernst, non ero così felice con la maggior parte delle risposte qui. Non potevo usare la sua soluzione poiché il mio requisito era quello di inserire i tag CDATA in un insieme definito di campi, come nella soluzione OutputFormat di raiglstorfer.

La mia soluzione è effettuare il marshalling su un documento DOM, quindi eseguire una trasformazione XSL null per eseguire l’output. I trasformatori ti consentono di impostare quali elementi sono inclusi nei tag CDATA.

 Document document = ... jaxbMarshaller.marshal(jaxbObject, document); Transformsr nullTransformsr = TransformsrFactory.newInstance().newTransformsr(); nullTransformsr.setOutputProperty(OutputKeys.INDENT, "yes"); nullTransformsr.setOutputProperty(OutputKeys.CDATA_SECTION_ELEMENTS, "myElement {myNamespace}myOtherElement"); nullTransformsr.transform(new DOMSource(document), new StreamResult(writer/stream)); 

Ulteriori informazioni qui: http://javacoalface.blogspot.co.uk/2012/09/outputting-cdata-sections-with-jaxb.html

Il seguente semplice metodo aggiunge il supporto CDATA in JAX-B che non supporta CDATA in modo nativo:

  1. dichiarare un tipo semplice personalizzato CDataString che estende la stringa per identificare i campi che devono essere gestiti tramite CDATA
  2. Creare un CDataAdapter personalizzato che analizzi e stampi il contenuto in CDataString
  3. usa i collegamenti JAXB per colbind CDataString e te CDataAdapter . il CdataAdapter aggiungerà / rimuoverà a / da CdataStrings a Marshall / Unmarshall
  4. Dichiarare un gestore escape personalizzato che non sfugga al carattere durante la stampa di stringhe CDATA e impostarlo come Marshaller CharacterEscapeEncoder

Et voilà, qualsiasi elemento di CDataString sarà incapsulato al tempo di Marshall. Al tempo di unmarshall, verrà automaticamente rimosso.

Supplemento della risposta di @a2ndrade .

Trovo una class da estendere in JDK 8. Ma ho notato che la class è nel pacchetto com.sun . Puoi fare una copia del codice nel caso in cui questa class possa essere rimossa in futuro 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(); } } } 

Come usare:

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

Esempio di risultato:

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

A partire da Xerxes-J 2.9, XMLSerializer è stato deprecato. Il suggerimento è di sostituirlo con DOM Level 3 LSSerializer o JAXP’s Transformation API for XML. Qualcuno ha provato ad avvicinarsi?

Il codice seguente impedirà la codifica degli elementi 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()); 

Mantiene anche UTF-8 come codifica.

Solo una parola di avvertimento: secondo la documentazione di javax.xml.transform.Transformsr.setOutputProperty (…) si dovrebbe usare la syntax dei nomi qualificati, quando si indica un elemento da un altro spazio dei nomi. Secondo JavaDoc (Java 1.6 rt.jar):

“(…) Ad esempio, se un URI e un nome locale sono stati ottenuti da un elemento definito con, il nome qualificato sarebbe” { http://xyz.foo.com/yada/baz.html } foo. Si noti che non viene utilizzato alcun prefisso. ”

Beh, questo non funziona – la class di implementazione da Java 1.6 rt.jar, che significa com.sun.org.apache.xalan.internal.xsltc.trax.TransformsrImpl interpreta gli elementi che appartengono a uno spazio dei nomi diverso solo allora correttamente, quando vengono dichiarati come ” http://xyz.foo.com/yada/baz.html:foo “, perché nell’implementazione qualcuno lo sta analizzando cercando gli ultimi due punti. Quindi, invece di invocare:

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

che dovrebbe funzionare secondo JavaDoc, ma finisce per essere analizzato come “http” e “//xyz.foo.com/yada/baz.html”, devi invocare

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

Almeno in Java 1.6.