Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JAXB Marshalling Unmarshalling with CDATA

Tags:

java

xml

jaxb

People also ask

What is JAXB marshalling and unmarshalling?

JAXB definitionsMarshalling is the process of transforming Java objects into XML documents. Unmarshalling is the process of reading XML documents into Java objects.

What is unmarshalling marshalling?

Marshalling is converting the data present in an object into a an xml format and viewing it in an xml format and unmarshalling is reverse of it converting an xml file into an object.

How does JAXB marshalling work?

In JAXB, marshalling involves parsing an XML content object tree and writing out an XML document that is an accurate representation of the original XML document, and is valid with respect the source schema. JAXB can marshal XML data to XML documents, SAX content handlers, and DOM nodes.

How do you Unmarshal XML string to Java object using JAXB?

To unmarshal an xml string into a JAXB object, you will need to create an Unmarshaller from the JAXBContext, then call the unmarshal() method with a source/reader and the expected root object.


You could do the following:

AdapterCDATA

package forum14193944;

import javax.xml.bind.annotation.adapters.XmlAdapter;

public class AdapterCDATA extends XmlAdapter<String, String> {

    @Override
    public String marshal(String arg0) throws Exception {
        return "<![CDATA[" + arg0 + "]]>";
    }
    @Override
    public String unmarshal(String arg0) throws Exception {
        return arg0;
    }

}

Root

The @XmlJavaTypeAdapter annotation is used to specify that the XmlAdapter should be used.

package forum14193944;

import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Root {

    @XmlJavaTypeAdapter(AdapterCDATA.class)
    private String name;

    @XmlJavaTypeAdapter(AdapterCDATA.class)
    private String surname;

    @XmlJavaTypeAdapter(AdapterCDATA.class)
    private String id;

}

Demo

I had to wrap System.out in an OutputStreamWriter to get the desired effect. Also note that setting a CharacterEscapeHandler means that it is responsible for all escape handling for that Marshaller.

package forum14193944;

import java.io.*;
import javax.xml.bind.*;
import com.sun.xml.bind.marshaller.*;

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Root.class);

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        File xml = new File("src/forum14193944/input.xml");
        Root root = (Root) unmarshaller.unmarshal(xml);

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.setProperty(CharacterEscapeHandler.class.getName(),
                new CharacterEscapeHandler() {
                    @Override
                    public void escape(char[] ac, int i, int j, boolean flag,
                            Writer writer) throws IOException {
                        writer.write(ac, i, j);
                    }
                });
        marshaller.marshal(root, new OutputStreamWriter(System.out));
    }

}

input.xml/Output

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
    <name><![CDATA[<h1>kshitij</h1>]]></name>
    <surname><![CDATA[<h1>solanki</h1>]]></surname>
    <id><![CDATA[0]]></id>
</root>

Please Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.

If you use MOXy as your JAXB (JSR-222) provider then you can leverage the @XmlCDATA extension for your use case.

Root

The @XmlCDATA annotation is used to indicate that you want the contents of a field/property wrapped in a CDATA section. The @XmlCDATA annotation can be used in combination with @XmlElement.

package forum14193944;

import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.XmlCDATA;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Root {

    @XmlCDATA
    private String name;

    @XmlCDATA
    private String surname;

    @XmlCDATA
    private String id;

}

jaxb.properties

To use MOXy as your JAXB provider you need to add file named jaxb.properties with the following entry.

javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory

Demo

Below is some demo code to prove that everything works.

package forum14193944;

import java.io.File;
import javax.xml.bind.*;

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Root.class);

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        File xml = new File("src/forum14193944/input.xml");
        Root root = (Root) unmarshaller.unmarshal(xml);

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(root, System.out);
    }

}

input.xml/Output

Below is the input to and output from running the demo code.

<?xml version="1.0" encoding="UTF-8"?>
<root>
   <name><![CDATA[<h1>kshitij</h1>]]></name>
   <surname><![CDATA[<h1>solanki</h1>]]></surname>
   <id><![CDATA[0]]></id>
</root>

For More Information

  • http://blog.bdoughan.com/2010/07/cdata-cdata-run-run-data-run.html
  • http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html

Sorry for digging out this question, and posting a new answer (my rep isn't high enough yet to comment...). I ran into the same issue, I tried Blaise Doughan's answer, but from my tests, either it doesn't cover all cases, either I'm doing something wrong somewhere.



    marshaller.setProperty(CharacterEscapeHandler.class.getName(),
                    new CharacterEscapeHandler() {
                        @Override
                        public void escape(char[] ac, int i, int j, boolean flag,
                                Writer writer) throws IOException {
                            writer.write(ac, i, j);
                        }
                    });


From my tests, this code removes all escaping, no matter if you are using the @XmlJavaTypeAdapter(AdapterCDATA.class) annotation on your attribute...

To fix that issue, I implemented the following CharacterEscapeHandler :


    public class CDataAwareUtfEncodedXmlCharacterEscapeHandler implements CharacterEscapeHandler {

        private static final char[] cDataPrefix = "<![CDATA[".toCharArray();
        private static final char[] cDataSuffix = "]]>".toCharArray();

        public static final CDataAwareUtfEncodedXmlCharacterEscapeHandler instance = new CDataAwareUtfEncodedXmlCharacterEscapeHandler();

        private CDataAwareUtfEncodedXmlCharacterEscapeHandler() {
        }

        @Override
        public void escape(char[] ch, int start, int length, boolean isAttVal, Writer out) throws IOException {
            boolean isCData = length > cDataPrefix.length + cDataSuffix.length;
            if (isCData) {
                for (int i = 0, j = start; i < cDataPrefix.length; ++i, ++j) {
                    if (cDataPrefix[i] != ch[j]) {
                        isCData = false;
                        break;
                    }
                }
                if (isCData) {
                    for (int i = cDataSuffix.length - 1, j = start + length - 1; i >= 0; --i, --j) {
                        if (cDataSuffix[i] != ch[j]) {
                            isCData = false;
                            break;
                        }
                    }
                }
            }
            if (isCData) {
                out.write(ch, start, length);
            } else {
                MinimumEscapeHandler.theInstance.escape(ch, start, length, isAttVal, out);
            }
        }
    }

If your encoding is not UTF*, you may not want to call MinimumEscapeHandler but rather NioEscapeHandler or even DumbEscapeHandler.