2010-06-28 36 views
38

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 de corriente produce este XML:¿Cómo se genera el bloque CDATA con JAXB?

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

quiero envolver el arg "fuente" en CDATA tal que parece que a continuación:

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

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

@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; } 
    } 
+0

¿Se puede encontrar ninguna solución a este problema? Si es así, por favor comparte, gracias. – Javatar

Respuesta

24

Nota: Soy el EclipseLink JAXB (MOXy) de plomo y un miembro del grupo de expertos JAXB (JSR-222).

Si está utilizando moxy como su proveedor de JAXB entonces se puede aprovechar la @XmlCDATA extensión:

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

+3

No estoy seguro de por qué esta respuesta recibió un voto negativo. Es una respuesta directa a las preguntas, con un enlace a una descripción detallada de cómo se puede aplicar la solución. JAXB es una especificación y las implementaciones compatibles, como MOXy, contienen extensiones para manejar cosas tales como CDATA. –

+0

Veo la solución en su blog. Esperaba encontrar algo que no requiriera el uso de Jar de terceros. Pero me di cuenta de que no es compatible con la implementación de JAXB que viene con Sun JDK. – Shreerang

+4

Este enlace ha aparecido más a menudo cuando intento resolver este problema. Confío en que funciona muy bien, pero una cosa que me desconcierta es que no puedo entender cómo conectar la solución a mi cliente. Cada ejemplo usa el método '' main'' para probar que funciona el marshalling, pero les falta una parte de cómo usarlo en un cliente real. Por ejemplo, dónde debería '' JAXBContext jc = JAXBContext.newInstance (classes, props); '' en el cliente generado wsdl2java go, ya que este JAXBContext es invocado por jax-ws automáticamente, si lo he entendido correctamente. –

10

Aquí es el ejemplo de código 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", // <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; 
    } 
} 
2

Como de Jerjes-J 2,9, XMLSerializer está en desuso. La sugerencia es reemplazarlo con DOM Level 3 LSSerializer o JAXP's Transformation API for XML. ¿Alguien ha intentado acercarse?

+0

Estoy tratando de hacer y encontré un enlace: http://www.mirthcorp.com/community/fisheye/rdiff/Mirth/trunk/server/src/com/webreach/mirth/model/converters/DocumentSerializer.java ? r1 = 1809 & r2 = 1881 & u & N –

18

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

Esta es mucho mejor de utilizar el método XMLSerializer.setCDataElements(...) porque usted no tiene que codificar cualquier lista de elementos Automáticamente emite bloques CDATA solo cuando se requiere uno.

+0

Limpio y fácil. Voy a pasar algunas pruebas. ¡Gracias! – ericp

+0

¿No puedo extender una clase DataWriter y utilizar este procedimiento? Soy el contentHandler predeterminado, así que no puedo extenderlo y usarlo para resolver mis problemas. –

+0

@Apoorvasahay Finalmente, encuentro una clase en la clase para extender en JDK 8. 'com.sun.xml.internal.txw2.output.XMLWriter'. Consulte mi respuesta para más detalles. – bluearrow

5

El siguiente método sencillo añade soporte CDATA en JAX-B que no soporta CDATA forma nativa:

  1. declaran una cadena personalizado simple tipo CDataString se extiende para identificar los campos que deben ser manejados a través de CDATA
  2. Crear una encargo CDataAdapter que analiza e imprimir contenido en CDataString
  3. uso fijaciones JAXB para enlazar CDataString y CDataAdapter.la CdataAdapter será añadir/quitar a/desde CdataStrings de Marshall/Resolver referencia de tiempo
  4. declarar una encargo manejador carácter de escape que no escapa el carácter cuando se imprime cadenas CDATA y establecerlo como el Marshaller CharacterEscapeEncoder

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

+1

Vea aquí un ejemplo de código: http://stackoverflow.com/a/14197860/809536 – ZiglioUK

14

Revisión Solución:

  • La respuesta de Fred es sólo una solución temporal, que se producirá un error al validar el contenido cuando el Marshaller está vinculado a un esquema porque modifica únicamente la cadena literal y no crea las secciones CDATA. Así que si sólo reescribir la cadena de foo a <! [CDATA [foo]]> la longitud de la cadena es reconocida por Xerces con 15 en lugar de 3.
  • La solución es moxy aplicación específica y no lo hace trabajar solo 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 problema.

he modificado la solución de a2ndrade mediante el uso de una aplicació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 de 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); 
     } 
    } 
} 
+2

Delegando XMLStreamWriter: https://github.com/apache/cxf/blob/master/core/src/main/java/org/ Apache/cxf/staxutils/DelegatingXMLStreamWriter.java –

8

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 etiquetas CDATA en un conjunto definido de campos, como en la solución OutputFormat de raiglstorfer.

Mi solución es reunir a 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 etiquetas 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

+0

Esto funciona muy bien. Esta es una solución fácil, aunque los elementos CDATA deben definirse en el momento de la asignación. – migu

0

El siguiente código impedirá que codifican 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 codificación.

+0

Y luego, cuando viene otro campo que no es CDATA, con <> en él, se estropea. Chicos, este es un enfoque realmente malo para hacer esto. Lo vi un par de veces aquí, pero realmente no lo recomendaría. El método de escape está ahí, porque escapas de algo, no porque NO quieras escapar del todo. – Mejmo

0

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

"(...) Por ejemplo, si un nombre de URI y locales se obtuvieron a partir de un elemento definido con, a continuación, el nombre completo sería" {} http://xyz.foo.com/yada/baz.html foo. Tenga en cuenta que no se usa prefijo."

bien esto no funciona - la clase que implementa desde Java 1.6 rt.jar, lo que significa com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl interpreta elementos que pertenecen a un espacio de nombres diferentes sólo a continuación, correctamente, que hayan sido declarados como "http://xyz.foo.com/yada/baz.html:foo", porque en la implementación alguien está analizarlo en busca de la última de colon Así que en lugar de invocar:.

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

que debe trabajar de acuerdo a JavaDoc, pero termina siendo analizada 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.

4

Suplemento de la respuesta @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(); 
    } 
    } 
} 

Modo de empleo:

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 Resultado:

<?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> 
+0

Gracias por su respuesta @bluearrow. Seguí los pasos pero recibí un error con respecto a com.sun.xml.internal.txw2.output.XMLWriter que pude resolver usando http://stackoverflow.com/a/33917172/3161688. ¡Gracias! –

Cuestiones relacionadas