Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
I'm trying to come up with a decent workaround for you, but below is an enhancement request you can follow where we are going to enhance the @XmlCDATA
annotation to make this use case much easier to support.
UPDATE #1
Below is an approach I'm working on for your use case. It requires a small change to MOXy which I have figured out but still need to fully test. You can track our progress on this issue using the following link:
DomHandler (CdataHandler)
JAXB has the concept of a DomHandler
that allows you some extra control of what the XML looks like. We will leverage a DomHandler
to add in a CDATA
block when required.
package forum14145131;
import javax.xml.bind.ValidationEventHandler;
import javax.xml.bind.annotation.DomHandler;
import javax.xml.parsers.*;
import javax.xml.transform.Source;
import javax.xml.transform.dom.*;
import org.w3c.dom.*;
public class CdataHandler implements DomHandler<String, DOMResult> {
private static DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();;
@Override
public DOMResult createUnmarshaller(ValidationEventHandler veh) {
return new DOMResult();
}
@Override
public String getElement(DOMResult domResult) {
Document document = (Document) domResult.getNode();
return document.getDocumentElement().getTextContent();
}
@Override
public Source marshal(String string, ValidationEventHandler veh) {
try {
DocumentBuilder db = dbf.newDocumentBuilder();
Document document = db.newDocument();
Node node;
if(string.contains("<") || string.contains("&") || string.contains("&")) {
node = document.createCDATASection(string);
} else {
node = document.createTextNode(string);
}
return new DOMSource(node);
} catch(Exception e) {
throw new RuntimeException(e);
}
}
}
Embed
The @XmlAnyElement
annotation is used to specify the DomHandler
.
package forum14145131;
import javax.xml.bind.annotation.*;
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
class Embed{
@XmlAnyElement(CdataHandler.class)
private String value; //THIS CAN BE CDATA OR NOT
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
For More Information
UPDATE #2
Below is an approach that you can use today using the existing EclipseLink library leveraging an XmlAdapter
and a CharacterEscapeHandler
:
XmlAdapter (CdataAdapter)
package forum14145131;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class CdataAdapter extends XmlAdapter<String, String> {
@Override
public String marshal(String string) throws Exception {
if(string.contains("&") || string.contains("<") || string.contains("\"")) {
return "<![CDATA[" + string + "]]>";
} else {
return string;
}
}
@Override
public String unmarshal(String string) throws Exception {
return string;
}
}
Embed
The @XmlJavaTypeAdapter
annotation is used to specify the XmlAdapter
.
package forum14145131;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
class Embed{
@XmlValue
@XmlJavaTypeAdapter(CdataAdapter.class)
private String value; //THIS CAN BE CDATA OR NOT
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
Demo
Below is how you specify the CharacterEscapeHandler
. Note this overrides all the character escaping for this Marshaller
. We do this so the CDATA
portion doesn't get escaped. For production code your implementation of this method would need to be beefed up over what is presented in this example.
package forum14145131;
import java.io.*;
import javax.xml.bind.*;
import org.eclipse.persistence.jaxb.MarshallerProperties;
import org.eclipse.persistence.oxm.CharacterEscapeHandler;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Embed.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum14145131/input.xml");
Embed embed = (Embed) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setProperty(MarshallerProperties.CHARACTER_ESCAPE_HANDLER,
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(embed, System.out);
}
}
input.xml/Output
<?xml version="1.0" encoding="UTF-8"?>
<embed><![CDATA[Hello & World]]></embed>