2010-06-28 27 views
38

Używam JAXB do serializowania moich danych do XML. Kod klasy jest prosty, jak podano poniżej. Chcę utworzyć plik XML zawierający bloki CDATA dla wartości niektórych argumentów Arg. Na przykład, obecny kod produkuje ten XML:Jak wygenerować blok CDATA za pomocą JAXB?

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

chcę owijać „źródło” arg w CDATA taki sposób, że wygląda jak poniżej:

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

Jak mogę to osiągnąć w poniższym kodzie ?

@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

można znaleźć jakieś rozwiązanie tego problemu? Jeśli tak, podziel się, dzięki. – Javatar

Odpowiedz

24

Uwaga: Jestem EclipseLink JAXB (MOXy) ołowiu i członkiem grupy JAXB (JSR-222) ekspertów.

Jeśli używasz Moxy jako dostawcę JAXB następnie można wykorzystać rozszerzenie @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; 
    } 

} 

Aby uzyskać więcej informacji

+3

Nie jestem pewien, dlaczego ta odpowiedź została odrzucona. Jest to bezpośrednia odpowiedź na pytania, wraz z linkiem do szczegółowego opisu, w jaki sposób można zastosować to rozwiązanie. JAXB jest specyfikacją i zgodnymi z nią implementacjami, takimi jak MOXy, zawiera rozszerzenia obsługujące takie elementy, jak CDATA. –

+0

Widzę rozwiązanie na twoim blogu, miałem nadzieję, że znajdę coś, co nie wymagałoby użycia jakiegokolwiek soku trzeciej strony. Ale zrozumiałem, że nie jest to wspierane przez implementację JAXB, która pochodzi z Sun JDK. – Shreerang

+4

Ten link pojawiał się częściej, gdy próbuję rozwiązać ten problem. Ufam, że działa bardzo dobrze, ale jedną rzeczą, która mnie zastanawia, jest to, że nie mogę zrozumieć, jak podłączyć to rozwiązanie do mojego klienta. Każdy przykład wykorzystuje metodę '' main'' do udowodnienia, że ​​działa zestawienie, ale brakuje mu części, jak go używać w prawdziwym kliencie. Na przykład gdzie powinien być '' JAXBContext jc = JAXBContext.newInstance (klasy, rekwizyty); '' w wsdl2java wygenerowany klient idź, ponieważ ten JAXBContext jest automatycznie wywoływany przez jax-ws, jeśli dobrze zrozumiałem. –

10

Oto przykład kodu odwołuje stronie wspomnianej powyżej:

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

jako Kserksesa-J 2.9, XMLSerializer została wycofana. Sugerowana jest zamiana na DOM Level 3 LSSerializer lub JAXP's Transformation API dla XML. Czy ktoś próbował podejść?

+0

Próbuję zrobić i znalazłem link: 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

Skorzystaj z JAXB Marshaller#marshal(ContentHandler), aby przeprowadzić marszałek do obiektu ContentHandler. Wystarczy zastąpić metodę characters sprawie realizacji ContentHandler używasz (np JDOM na SAXHandler, Apache 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(); 
    } 
} 

To jest znacznie lepiej niż przy zastosowaniu metody XMLSerializer.setCDataElements(...) bo nie trzeba zakodować dowolny lista elementów. Automatycznie wyprowadza bloki CDATA tylko wtedy, gdy jest wymagany.

+0

Czyste i łatwe. Mam zamiar przeprowadzić pewne testy. Dzięki! – ericp

+0

Nie mogę rozszerzyć klasy DataWriter i użyć tej procedury? Jestem domyślnym contentHandler, więc nie mogę go rozszerzyć i użyć go do rozwiązania moich problemów. –

+0

@Apoorvasahay Na koniec znajduję jedną klasę w klasie, która ma być rozszerzona w JDK 8. 'com.sun.xml.internal.txw2.output.XMLWriter'. Zobacz szczegóły na mój temat. – bluearrow

5

Poniższy prosty sposób dodaje wsparcie CDATA w JAX-B, która nie obsługuje CDATA natywnie:

  1. zadeklarować niestandardowy typ prosty CDataString rozciągający ciąg zidentyfikować obszary, które powinny być obsługiwane przez CDATA
  2. Tworzenie zwyczaj CDataAdapter który analizuje i drukowania zawartości w CDataString
  3. użycie JAXB powiązania połączyć CDataString i CDataAdapter.CdataAdapter będzie dodawanie/usuwanie do/z CdataStrings na Marshalla/wycofać czasie
  4. Zadeklaruj niestandardowej obsługi charakter ucieczki że nie ucieka charakter podczas drukowania ciągów CDATA i ustawić go jako naziemnego CharacterEscapeEncoder

Et voila , każdy element CDataString zostanie zamknięty w czasie Marshalla. W unmarshall time tester zostanie automatycznie usunięty.

+1

Zobacz tutaj przykład kodu: http://stackoverflow.com/a/14197860/809536 – ZiglioUK

14

Rozwiązanie Review:

  • Odpowiedź Fred jest tylko obejście który zawiedzie podczas sprawdzania zawartości gdy naziemnego jest połączony z Schema ponieważ można modyfikować tylko ciąg dosłowny i nie tworzą sekcje CDATA. Więc jeśli tylko przepisać ciąg od foo do <! [CDATA [foo]]> długość łańcucha jest rozpoznawany przez Xerces z 15 zamiast 3.
  • roztworze Moxy jest specyficzny i nie realizacja działa tylko z klasami JDK.
  • Rozwiązanie z odwołaniami getSerializer do wycofanej klasy XMLSerializer.
  • Rozwiązanie LSSerializer to tylko ból.

Zmodyfikowałem rozwiązanie a2ndrade za pomocą implementacji XMLStreamWriter. To rozwiązanie działa bardzo dobrze.

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

To jest implementacja CDataXMLStreamWriter. Klasa delegatów po prostu przekazuje wszystkie wywołania metod do danej implementacji 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

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

8

Z tych samych powodów co Michael Ernst nie byłem tak zadowolony z większości odpowiedzi tutaj. Nie mogłem użyć jego rozwiązania, ponieważ moim wymaganiem było umieszczenie znaczników CDATA w zdefiniowanym zestawie pól - tak jak w rozwiązaniu OutputFormat autorstwa raiglstorfera.

Moje rozwiązanie polega na przeniesieniu do dokumentu DOM, a następnie wykonaniu zerowej transformacji XSL w celu uzyskania danych wyjściowych. Transformatory pozwalają ustawić, które elementy są zawijane w znaczniki 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)); 

Więcej informacji tutaj: http://javacoalface.blogspot.co.uk/2012/09/outputting-cdata-sections-with-jaxb.html

+0

To działa naprawdę ładnie. Jest to łatwe rozwiązanie, chociaż elementy CDATA muszą być zdefiniowane w czasie marszałka. – migu

0

Poniższy kod spowoduje z kodującego elementy 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()); 

Będzie również zachować UTF-8 jako kodowania.

+0

A potem, gdy pojawi się jakieś inne pole inne niż CDATA, z <> w nim, to się zepsuje. Chłopaki, to naprawdę złe podejście. Widziałem to kilka razy tutaj, ale nie polecam go. Znajduje się tam metoda Escape, ponieważ uciekasz z czegoś, a nie dlatego, że wcale NIE chcesz uciec. – Mejmo

0

Tylko słowo ostrzeżenia: zgodnie z dokumentacją javax.xml.transform.Transformer.setOutputProperty (...) należy użyć składni kwalifikowanych nazw, wskazując element z innej przestrzeni nazw. Zgodnie z JavaDoc (Java 1.6 rt.jar):

"(...) Na przykład, jeśli nazwa URI i lokalne zostały uzyskane z elementu z określonym, a następnie kwalifikowaną nazwą byłoby" {} http://xyz.foo.com/yada/baz.html bla. Zauważ, że nie jest używany żaden prefiks."

No to nie zadziała - klasę wykonawczą z Java 1.6 rt.jar, czyli com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl interpretuje elementy należące do innej przestrzeni nazw tylko następnie poprawnie, gdy są one zadeklarowane jako „http://xyz.foo.com/yada/baz.html:foo”, ponieważ w realizacji ktoś analizowania go patrząc na ostatni okrężnicy Więc zamiast wywoływać.

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

który powinien działać zgodnie z JavaDoc, ale kończy się analizowany jako "http" i "//xyz.foo.com/yada/baz.html", musisz wywołać

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

Przynajmniej w Javie 1.6.

4

Dodatek do odpowiedzi @a2ndrade.

Znajduję jedną klasę do rozszerzenia w JDK 8. Ale zauważyłem, że klasa jest w pakiecie com.sun. Możesz wykonać jedną kopię kodu na wypadek, gdyby ta klasa mogła zostać usunięta w przyszłym 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(); 
    } 
    } 
} 

Jak używać:

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

Przykładowy wynik:

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

Dzięki za odpowiedź @bluearrow. Postępowałem zgodnie z instrukcjami, ale dostałem błąd dotyczący pliku com.sun.xml.internal.txw2.output.XMLWriter, który udało mi się rozwiązać za pomocą http://stackoverflow.com/a/33917172/3161688. Dzięki! –

Powiązane problemy