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 XML qui contient des blocs CDATA pour la valeur de quelques Args. Par exemple, le code courant produit ce XML:

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

je veux enrouler la "source" arg dans CDATA tel qu'il se présente comme ci-dessous:

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

Comment puis-je obtenir ceci 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; }
    }
39
demandé sur skaffman 2010-06-29 01:45:39

10 réponses

Remarque: je suis EclipseLink JAXB (MOXy) lead et un membre du JAXB (JSR-222) groupe d'experts.

si vous utilisez MOXy comme fournisseur JAXB, alors vous pouvez utiliser le @XmlCDATA poste:

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 En Savoir Plus Informations

25
répondu Blaise Doughan 2017-08-22 20:28:21

utilisez JAXB's Marshaller#marshal(ContentHandler) pour le maréchal dans un ContentHandler objet. Simplement remplacer le characters méthode sur L'implémentation ContentHandler que vous utilisez (par ex. SAXHandler, Apache est 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();
    }
}

C'est beaucoup mieuxXMLSerializer.setCDataElements(...) méthode parce que vous n'avez pas besoin de hardcode n'importe quelle liste d'éléments. Il affiche automatiquement les blocs CDATA seulement quand on est requis.

20
répondu a2ndrade 2015-10-14 17:34:52

Examen De La Solution:

  • La réponse de fred est juste une solution de contournement qui va échouer lors de la validation du contenu lorsque le Marshaller est lié à un Schéma parce que vous modifiez uniquement la chaîne de caractères littérale et ne pas créer des sections CDATA. Donc si vous réécrivez seulement 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 à la mise en oeuvre et ne ne travaillez qu'avec les classes de la JDK.
  • la solution avec les références getSerializer à la classe XmlSerializer dépréciée.
  • la solution LSSerializer est juste une douleur.

j'ai modifié la solution d'a2ndrade en utilisant un XMLStreamWriter mise en œuvre. 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();
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 );
      }
   }
}
14
répondu Michael Ernst 2012-05-05 09:59:59

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;
    }
}
10
répondu ra9r 2010-07-17 20:11:44

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 étiquettes CDATA dans un ensemble défini de champs - comme dans la solution OutputFormat de raiglstorfer.

ma solution est de marshal à un document DOM, puis faire une transformation XSL nulle pour faire la sortie. Les transformateurs vous permettent de définir quels éléments sont enveloppés dans des étiquettes 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

8
répondu Reg Whitton 2012-09-28 09:29:47

la méthode simple suivante ajoute le support CDATA dans JAX-B qui ne supporte pas CDATA nativement :

  1. déclarer un personnaliser le type simple CDataString extension de la chaîne de caractères pour identifier les champs qui doivent être traités via CDATA
  2. Créer un custom CDataAdapter qui analyse et d'imprimer le contenu dans CDataString
  3. utiliser JAXB pour les liaisons de lien CDataString et vous CDataAdapter. le CdataAdapter ajoutera / supprimera vers/à partir de CdataStrings à Marshall/Unmarshall temps
  4. Déclarer un personnaliser le caractère d'échappement handler qui n'a pas de caractère d'échappement lors de l'impression CDATA de cordes et de définir ce que le Marshaller CharacterEscapeEncoder

et voila, n'importe quel élément de CDataString sera encapsulé avec au temps Marshall. À l'Heure de non-Marshall, le sera automatiquement supprimé.

5
répondu fred 2011-11-17 09:32:51

Complément