¿Cómo generar bloque CDATA usando JAXB?

Estoy usando JAXB para serializar mis datos a XML. El código de clase es simple como se indica a continuación. Quiero producir XML que contenga bloques CDATA por el valor de algunos Args. Por ejemplo, el código actual produce este XML:

  1234 <html>EMAIL</html>   

Quiero envolver el argumento “fuente” en CDATA de modo que se vea a continuación:

   1234 <[![CDATA[EMAIL]]>   

¿Cómo puedo lograr esto en el siguiente código?

 @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: Soy el líder de EclipseLink JAXB (MOXy) y miembro del grupo de expertos JAXB (JSR-222) .

Si está utilizando MOXy como su proveedor JAXB, puede aprovechar la extensión @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; } } 

Para más información

Utilice el Marshaller#marshal(ContentHandler) de JAXB Marshaller#marshal(ContentHandler) para formar un objeto ContentHandler . Simplemente anule el método de characters en la implementación de ContentHandler que está utilizando (por ejemplo, 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.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(); } } 

Esto es mucho mejor que utilizar el XMLSerializer.setCDataElements(...) porque no es necesario codificar ninguna lista de elementos. Emite automáticamente bloques CDATA solo cuando se requiere uno .

Revisión de la solución:

  • La respuesta de fred es solo una solución que fallará al validar el contenido cuando Marshaller está vinculado a un esquema, ya que modifica solo el literal de cadena y no crea secciones CDATA. Entonces, si solo reescribe la cadena desde foo a Xerces reconoce la longitud de la cadena con 15 en lugar de 3.
  • La solución MOXy es específica de la implementación y no solo funciona con las clases del JDK.
  • La solución con el getSerializer hace referencia a la clase XMLSerializer en desuso.
  • La solución LSSerializer es simplemente un dolor.

Modifiqué la solución de a2ndrade utilizando una implementación XMLStreamWriter . Esta solución funciona muy bien.

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

Esa es la implementación CDataXMLStreamWriter. La clase de delegado simplemente delega todas las llamadas de método a la implementación dada de 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 ); } } } 

Aquí está la muestra del código al que hace referencia el sitio mencionado anteriormente:

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

Por las mismas razones que Michael Ernst, no estaba contento con la mayoría de las respuestas aquí. No pude usar su solución ya que mi requisito era poner tags CDATA en un conjunto definido de campos, como en la solución OutputFormat de raiglstorfer.

Mi solución es reunirme con un documento DOM, y luego hacer una transformación XSL nula para hacer la salida. Los transformadores le permiten establecer qué elementos están incluidos en las tags 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)); 

Más información aquí: http://javacoalface.blogspot.co.uk/2012/09/outputting-cdata-sections-with-jaxb.html

El siguiente método simple agrega soporte CDATA en JAX-B que no es compatible con CDATA de forma nativa:

  1. declarar una cadena de extensión CDataString de tipo simple personalizado para identificar los campos que se deben manejar a través de CDATA
  2. Cree un CDataAdapter personalizado que analice e imprima contenido en CDataString
  3. use enlaces JAXB para vincular CDataString y usted CDataAdapter . el CdataAdapter agregará / eliminará a / de CdataStrings en el horario de Marshall / Unmarshall
  4. Declare un controlador de escape de caracteres personalizado que no escape al carácter al imprimir cadenas de CDATA y configúrelo como Marshaller CharacterEscapeEncoder

Et voila, cualquier elemento CDataString se encapsulará en tiempo de Marshall. En el tiempo unmarshall, se eliminará automáticamente.

Suplemento de la respuesta de @a2ndrade .

Encuentro una clase para extender en JDK 8. Pero noté que la clase está en el paquete com.sun . Puede hacer una copia del código en caso de que esta clase se elimine en el 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(); } } } 

Cómo utilizar:

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

Ejemplo de resultado:

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

A partir de Xerxes-J 2.9, XMLSerializer ha quedado en desuso. La sugerencia es reemplazarlo con DOM Level 3 LSSerializer o JAXP’s Transformation API for XML. ¿Alguien ha intentado acercarse?

El siguiente código evitará que se codifiquen elementos 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()); 

También mantendrá UTF-8 como su encoding.

Solo una palabra de advertencia: de acuerdo con la documentación de javax.xml.transform.Transformer.setOutputProperty (…) debe usar la syntax de nombres calificados, cuando se indica un elemento de otro espacio de nombres. De acuerdo con JavaDoc (Java 1.6 rt.jar):

“(…) Por ejemplo, si se obtuvo un URI y un nombre local de un elemento definido con, entonces el nombre calificado sería” { http://xyz.foo.com/yada/baz.html } foo. Tenga en cuenta que no se usa prefijo “.

Bueno, esto no funciona: la clase de implementación de Java 1.6 rt.jar, es decir, com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl interpreta los elementos que pertenecen a un espacio de nombre diferente solo entonces correctamente, cuando se declaran como ” http://xyz.foo.com/yada/baz.html:foo “, porque en la implementación alguien lo está analizando en busca de los dos últimos puntos. Entonces, en lugar de invocar:

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

que debería funcionar de acuerdo con JavaDoc, pero termina siendo analizado como “http” y “//xyz.foo.com/yada/baz.html”, debe invocar

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

Al menos en Java 1.6.

    Intereting Posts