31

I am trying to do marshaling with JAXB.

My output is like this:

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

...but I need output like this:

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

I am using following code to do this.

If I uncomment code I get PropertyBindingException. Without it I can compile but I am not getting the exact required output.

  package com.ksh.templates;

import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import com.sun.xml.bind.marshaller.CharacterEscapeHandler;

public class MainCDATA {
    public static void main(String args[])
    {
        try
        {
            String name = "<h1>kshitij</h1>";
            String surname = "<h1>solanki</h1>";
            String id = "<h1>1</h1>";
            
            TestingCDATA cdata = new TestingCDATA();
            cdata.setId(id);
            cdata.setName(name);
            cdata.setSurname(surname);
            
            JAXBContext jaxbContext = JAXBContext.newInstance(TestingCDATA.class);
            Marshaller marshaller = jaxbContext.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
            
            marshaller.setProperty(CharacterEscapeHandler.class.getName(), new CharacterEscapeHandler() { 
                public void escape(char[] ac, int i, int j, boolean flag,
                Writer writer) throws IOException {
                writer.write( ac, i, j ); }
                });
            StringWriter stringWriter = new StringWriter(); 
            marshaller.marshal(cdata, stringWriter);
            System.out.println(stringWriter.toString());
        } catch (Exception e) {
            System.out.println(e);
        }       
    }
}

My bean looks like this:

package com.ksh.templates;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import com.sun.xml.txw2.annotation.XmlCDATA;

@XmlRootElement(name = "root")
@XmlAccessorType(XmlAccessType.FIELD)
public class TestingCDATA {

    @XmlElement
    @XmlJavaTypeAdapter(value = AdaptorCDATA.class)
    private String name;
    @XmlElement
    @XmlJavaTypeAdapter(value = AdaptorCDATA.class)
    private String surname;
    
    @XmlCDATA
    public String getName() {
        return name;
    }
    @XmlCDATA
    public void setName(String name) {
        this.name = name;
    }
    @XmlCDATA
    public String getSurname() {
        return surname;
    }
    @XmlCDATA
    public void setSurname(String surname) {
        this.surname = surname;
    }
}

Adaptor Class

public class AdaptorCDATA 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;
    }
}
Lii
  • 11,553
  • 8
  • 64
  • 88
MRX
  • 1,611
  • 8
  • 33
  • 55

7 Answers7

45

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>
bdoughan
  • 147,609
  • 23
  • 300
  • 400
  • I just tried to run this code and i am getting same error that i was getting yesterday. means still there is some problem. and also i made new project and try to run this code. and getting same error. there are no external jars. still not working. – MRX Jan 08 '13 at 05:35
  • i am getting following error. javax.xml.bind.PropertyException: name: CharacterEscapeHandler value: MainClass$1@66e815 javax.xml.bind.PropertyException: name: CharacterEscapeHandler value: MainClass$1@66e815 at javax.xml.bind.helpers.AbstractMarshallerImpl.setProperty(Unknown Source) at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.setProperty(Unknown Source) at MainClass.main(MainClass.java:35) – MRX Jan 08 '13 at 05:35
  • i can do unmarshal from file but i can not do marshalling. – MRX Jan 08 '13 at 05:38
  • @kshitij - The code as is requires that you download the JAXB reference implementation from http://jaxb.java.net, or EclipseLink JAXB (MOXy) from http://www.eclipse.org/eclipselink/moxy.php. You could also try changing the `CharacterEscapeHandler` import statement to be `import com.sun.xml.internal.bind.marshaller.*` – bdoughan Jan 08 '13 at 10:08
  • can i do it without this moxy.? because i can not change jar files right now. because i have to test all things with this moxy. is this thing is in jersey framework. ? – MRX Jan 08 '13 at 10:28
  • @kshitij - As I said you could try changing the `CharacterEscapeHandler` import to `com.sun.xml.internal.bind.marshaller.*` which may or may not be allowed in your environment. – bdoughan Jan 08 '13 at 10:31
  • 1
    i already tried it. it is not working. my jdk version is 1.7 so i can not find com.sun.xml.internal.bind.marshaller.CharacterEscapeHandler. and in jdk 1.6 i can find that class but it is still giving error. – MRX Jan 08 '13 at 10:35
  • @kshitij - Then for this approach you will need to use the JAXB reference implementation which can be downloaded from http://jaxb.java.net/ or EclipseLink JAXB (MOXy) http://www.eclipse.org/eclipselink/moxy.php – bdoughan Jan 08 '13 at 10:44
  • ok. that means i have to download jaxb-api.jar and jaxb-impl.jar. then what to do extra in above code? – MRX Jan 08 '13 at 13:15
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/22395/discussion-between-blaise-doughan-and-kshitij) – bdoughan Jan 08 '13 at 13:22
  • @kshitij - All you would need to do is add the `jaxb-impl.jar` to your class path. If you are using JDK 1.6 download a 2.1 implementation and if you are using JDK 1.7 get the 2.2 impl. – bdoughan Jan 08 '13 at 13:24
  • is there any configuration like in moxy we have to provide one jaxb.properties. like that? – MRX Jan 08 '13 at 13:31
  • 1
    @blaise-doughan , your "package forum14193944;" smells so sweet!! – David W Jan 23 '14 at 17:03
  • 9
    @BlaiseDoughan, your AdapterCDATA gives me following xml: <![CDATA[sometext]]>, so because of < and > this code is missinterpreted. – Stepan Yakovenko Feb 09 '14 at 13:01
  • 1
    If you already declared your CDATA adapter. Then why an additional escape handler ? Are you showing 2 separate solutions ? Or am I missing the link between the adapter and the escape handler? – bvdb Sep 07 '15 at 12:08
  • 11
    This will disable character escaping on _all_ fields, not just cdata fields. Quite dangerous to use. – Tobb Jan 05 '17 at 09:25
  • How to achieve these annotations by using binding.xml or modifying XSD ? – Kaushik Lele Apr 17 '17 at 13:29
  • This solution uses internal Java code (not part of the API), which is subject for removal in future versions or in other JVM implementations, and isn't guaranteed consistent behavior. – user64141 Jun 15 '17 at 23:04
  • There are 2 problems with this code - it uses internal package and ... what seems to be "bit" more serious - it does not escape the strings.. Imagine you have <>@ in another field, it will not produce the valid XML!! – Mejmo Jul 24 '17 at 06:39
  • Excellent this works for me and saved a lot of time. – Venkat Jul 26 '17 at 13:53
  • It worked. thanks !! Can someone explain in simple terms what this is doing ? "CharacterEscapeHandler" – HookUp Aug 10 '17 at 17:44
  • Dirty method that disable escaping, to me it's a bad practice. – pdem Sep 25 '19 at 14:11
  • 1
    Will disable escaping for the entire marshaller. Not good - the same thing as new NoEscapeHandler() – Vladimir Nov 12 '19 at 16:22
15

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

rainer198
  • 3,195
  • 2
  • 27
  • 42
bdoughan
  • 147,609
  • 23
  • 300
  • 400
  • is there any way to do it without adding moxy. ? like i am following below link but i am not getting Success. http://odedpeer.blogspot.in/2010/07/jaxb-sun-and-how-to-marshal-cdata.html – MRX Jan 07 '13 at 11:22
  • @kshitij - Can you post the stack trace that you are seeing with your current approach? – bdoughan Jan 07 '13 at 11:37
  • when i am un-commenting code i am getting error. javax.xml.bind.PropertyException: name: com.sun.xml.bind.marshaller.CharacterEscapeHandler value: com.ksh.templates.MainCDATA$1@8b819f – MRX Jan 07 '13 at 11:45
  • Are using the property name that contains `internal` as in your sample code or without `internal` as per the stack trace? – bdoughan Jan 07 '13 at 11:50
  • @kshitij - instead could you post the entire class that contains the `try` block (complete with ports)? – bdoughan Jan 07 '13 at 12:00
  • @kshitij - Are you using the JAXB RI or the version of JAXB in the JDK? This would be easy for me to tell if you updated your question to contain the stack trace. – bdoughan Jan 07 '13 at 12:09
  • in my library i have added jaxb library. how to know it is using from JDK or from library.? – MRX Jan 07 '13 at 12:14
  • @kshitij - Do you see `internal` as part of the package name? – bdoughan Jan 07 '13 at 12:18
  • I updated Bean class. would you like to tell me that what effect can occur if i am using from internal package? – MRX Jan 07 '13 at 12:29
  • @kshitij - No I meant do you see `internal` as part of `com.sun.xml.bind` in the stack trace? – bdoughan Jan 07 '13 at 12:41
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/22319/discussion-between-kshitij-and-blaise-doughan) – MRX Jan 07 '13 at 13:15
  • In a spring-boot app I had to use the java services locator approach, by adding /scr/main/resoures/META-INF/services/javax.xml.bind.JAXBContext with the content of org.eclipse.persistence.jaxb.JAXBContextFactory - afterwards the @XmlCDATA annotation was working nicely. – Vladimir Nov 12 '19 at 18:32
  • If someone is having issues following this approach, check the location of the jabx.properties file. It has to be on the same package as the entity definition ( package com.foo.bar.model for example). I have tried in src/main/resources but it just doesn't work. It has to be located in the same package of the class that needs the CDATA. – Nico Gallegos Oct 09 '21 at 07:08
9

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.

Marc P.
  • 91
  • 1
  • 3
  • 1
    This should be the accepted answer, because it only removes escaping from the CDATA portion of the XML – racc Jun 30 '22 at 01:31
0
    @Test
    public void t() throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Root.class);
        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        Root root = new Root();
        root.name = "<p>Jorge & Mary</p>";
        marshaller.marshal(root, System.out);
    }
    @XmlRootElement
    @XmlAccessorType(XmlAccessType.FIELD)
    public static class Root {
        @XmlCDATA
        private String name;
    }
    /* WHAT I SEE IN THE CONSOLE
     * 
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
    <name>&lt;p&gt;Jorge &amp; Mary&lt;/p&gt;</name>
</root>
     */
Stephan
  • 41,764
  • 65
  • 238
  • 329
user1346730
  • 165
  • 4
  • 13
  • 2
    What implementation of JAXB are you using here ? Can you explicit more the configuration required for obtaining this result ? – Stephan Jul 17 '14 at 20:01
0

I landed on this page trying to find solution to a similar issue, I found another approach to solve this. One way to solve this problem is to send XML as SAX2 events to a handler, then write the logic in the handler to add the CDATA tags to the XML. This approach doesn't require any annotation to be added. Useful in scenarios where classes to be marshaled are generated from XSD's.

Suppose you have a String field in a class generated from XSD which is to be marshaled and the String field contains special characters which are to be put inside a CDATA tag.

@XmlRootElement
public class TestingCDATA{
    public String xmlContent;

}

We'll start by searching a suitable class whose method can be overridden in our content handler. One such class is XMLWriter found in package com.sun.xml.txw2.output, It's available in jdk 1.7 and 1.8

import com.sun.xml.txw2.output.XMLWriter;
import org.xml.sax.SAXException;

import java.io.IOException;
import java.io.Writer;
import java.util.regex.Pattern;

public class CDATAContentHandler extends 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();
        }
    }
}

We are overriding the characters method, using regex to check if any special characters are contained. If they are found then we put CDATA tags around them. In this case XMLWriter takes care of adding CDATA tag.

We'll use the following code for marshaling:

public String addCDATAToXML(TestingCDATA request) throws FormatException {
    try {
        JAXBContext jaxbContext = JAXBContext.newInstance(TestingCDATA.class);
        Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
        StringWriter sw = new StringWriter();
        CDATAContentHandler cDataContentHandler = new CDATAContentHandler(sw, "UTF-8");
        jaxbMarshaller.marshal(request, cDataContentHandler);
        return sw.toString();
    } catch (JAXBException | IOException e) {
        throw new FormatException("Unable to add CDATA for request", e);
    }
}

This would marshal the object and return XML, if we pass a request to be marshaled as mentioned below.

TestingCDATA request=new TestingCDATA();
request.xmlContent="<?xml>";

System.out.println(addCDATAToXML(request)); // Would return the following String

Output- 

<?xml version="1.0" encoding="UTF-8"?>
<testingCDATA>
<xmlContent><![CDATA[<?xml>]]></xmlContent>
</testingCDATA>
pkoli
  • 646
  • 8
  • 21
0

In addition to @bdoughan answer. Character escape handler with CDATA support

import com.sun.xml.bind.marshaller.CharacterEscapeHandler;

import java.io.IOException;
import java.io.Writer;

/**
 * This class is a modern version of JAXB MinimumEscapeHandler with CDATA support
 *
 * @author me
 * @see com.sun.xml.bind.marshaller.MinimumEscapeHandler
 */
public class CdataEscapeHandler implements CharacterEscapeHandler {

    private CdataEscapeHandler() {
    }  // no instanciation please

    public static final CharacterEscapeHandler theInstance = new CdataEscapeHandler();

    @Override
    public void escape(char[] ch, int start, int length, boolean isAttVal, Writer out) throws IOException {
        // avoid calling the Writerwrite method too much by assuming
        // that the escaping occurs rarely.
        // profiling revealed that this is faster than the naive code.
        int limit = start + length;
        for (int i = start; i < limit; i++) {
            if (!isAttVal
                    && i <= limit - 12
                    && ch[i] == '<'
                    && ch[i + 1] == '!'
                    && ch[i + 2] == '['
                    && ch[i + 3] == 'C'
                    && ch[i + 4] == 'D'
                    && ch[i + 5] == 'A'
                    && ch[i + 6] == 'T'
                    && ch[i + 7] == 'A'
                    && ch[i + 8] == '[') {
                int cdataEnd = i + 8;
                for (int k = i + 9; k < limit - 2; k++) {
                    if (ch[k] == ']'
                            && ch[k + 1] == ']'
                            && ch[k + 2] == '>') {
                        cdataEnd = k + 2;
                        break;
                    }
                }
                out.write(ch, start, cdataEnd + 1);
                if (cdataEnd == limit - 1) return;
                start = i = cdataEnd + 1;
            }
            char c = ch[i];
            if (c == '&' || c == '<' || c == '>' || c == '\r' || (c == '\"' && isAttVal)) {
                if (i != start)
                    out.write(ch, start, i - start);
                start = i + 1;
                switch (ch[i]) {
                    case '&':
                        out.write("&amp;");
                        break;
                    case '<':
                        out.write("&lt;");
                        break;
                    case '>':
                        out.write("&gt;");
                        break;
                    case '\"':
                        out.write("&quot;");
                        break;
                }
            }
        }

        if (start != limit)
            out.write(ch, start, limit - start);
    }
}
Bogdan Samondros
  • 138
  • 1
  • 10
-1

com.sun.internal dont works with play2,but this works

private static String marshal(YOurCLass xml){
    try{
        StringWriter stringWritter = new StringWriter();
        Marshaller marshaller = JAXBContext.newInstance(YourCLass.class).createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.setProperty(Marshaller.JAXB_ENCODING, "ISO-8859-1");
        marshaller.marshal(xml, stringWritter);
        return stringWritter.toString().replaceAll("&lt;", "<").replaceAll("&gt;", ">");
    }
    catch(JAXBException e){
        throw new RuntimeException(e);
    }
}
Bruno Lee
  • 1,867
  • 16
  • 17
  • 6
    That's all fun and games, until you really have a `String` containing a `'>'` character. First it will be converted correctly to `">"` by the marshaller. Next you will replaced it by a `'>'`, breaking the xml. – bvdb Sep 07 '15 at 12:00