如何用Java生成含指定字段CDATA的XML(替代已弃用XmlSerializer)
问题描述
我需要更新一段旧代码,该代码使用了Xerces 2.9.0中已弃用的XmlSerializer类,官方建议新应用使用DOM Level 3 LSSerializer或JAXP的XML转换API(TrAX)来序列化XML。需求是从Java类生成XML,并为XML中的特定字段outerHtml添加<![CDATA[前缀和]]>后缀。
以下是旧代码实现:
import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.Serializable; import java.io.StringWriter; import java.io.Writer; import java.nio.charset.StandardCharsets; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.xml.serialize.OutputFormat; import org.apache.xml.serialize.XMLSerializer; private static File generateXmlWithCDATA(MyObjectJava myObjectJava, File fileXmlOutput) throws JAXBException, IOException { JAXBContext requestJaxbContext = JAXBContext.newInstance(MyObjectJava.class); Marshaller marshaller = requestJaxbContext.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8"); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); // get an Apache XMLSerializer configured to generate CDATA try(OutputStream out = new FileOutputStream(fileXmlOutput)) { XMLSerializer serializer = getXMLSerializer(out); marshaller.marshal(myObjectJava, serializer.asContentHandler()); } return fileXmlOutput; } private static XMLSerializer getXMLSerializer(OutputStream os) { // 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[] { "^outerHtml" // <outerHtml> }); // set any other options you'd like of.setPreserveSpace(true); of.setIndenting(true); // create the serializer final XMLSerializer serializer = new XMLSerializer(of); serializer.setOutputByteStream(os); return serializer; }
期望生成的XML输出示例:
<MyObjectJava> <name>Leo</name> <surname>DaVinci</surname> <outerHtml><![CDATA[<a href="https://www.isasite.com">Is a site</a>]]></outerHtml> </MyObjectJava>
解决方案
方案一:使用DOM Level 3 LSSerializer
该方案先将JAXB对象序列化为DOM Document,再遍历DOM树找到目标元素,将其文本内容替换为CDATA节点,最后用LSSerializer输出XML。
import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import org.w3c.dom.CDATASection; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.w3c.dom.ls.DOMImplementationLS; import org.w3c.dom.ls.LSOutput; import org.w3c.dom.ls.LSSerializer; private static File generateXmlWithCDATA(MyObjectJava myObjectJava, File fileXmlOutput) throws JAXBException, IOException { // 初始化JAXB上下文和Marshaller JAXBContext jaxbContext = JAXBContext.newInstance(MyObjectJava.class); Marshaller marshaller = jaxbContext.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8"); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); // 将JAXB对象序列化为DOM Document Document document = marshaller.getNode(myObjectJava).getOwnerDocument(); // 遍历DOM树,为outerHtml元素添加CDATA addCDATAToElement(document, "outerHtml"); // 使用LSSerializer输出XML到文件 DOMImplementationLS domImpl = (DOMImplementationLS) document.getImplementation(); LSSerializer serializer = domImpl.createLSSerializer(); serializer.getDomConfig().setParameter("format-pretty-print", true); serializer.getDomConfig().setParameter("encoding", "UTF-8"); try (FileOutputStream outputStream = new FileOutputStream(fileXmlOutput)) { LSOutput output = domImpl.createLSOutput(); output.setByteStream(outputStream); serializer.write(document, output); } return fileXmlOutput; } private static void addCDATAToElement(Document document, String elementName) { NodeList nodeList = document.getElementsByTagName(elementName); for (int i = 0; i < nodeList.getLength(); i++) { Element element = (Element) nodeList.item(i); String textContent = element.getTextContent().trim(); // 清空元素现有子节点 while (element.hasChildNodes()) { element.removeChild(element.getFirstChild()); } // 创建并添加CDATA节点 CDATASection cdataSection = document.createCDATASection(textContent); element.appendChild(cdataSection); } }
方案二:使用JAXP TrAX API(Transformer)
该方案先将JAXB对象转为DOM Document,处理CDATA后,用Transformer完成XML序列化输出。
import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.w3c.dom.CDATASection; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; private static File generateXmlWithCDATA(MyObjectJava myObjectJava, File fileXmlOutput) throws JAXBException, IOException, TransformerException { // JAXB初始化并序列化为DOM Document JAXBContext jaxbContext = JAXBContext.newInstance(MyObjectJava.class); Marshaller marshaller = jaxbContext.createMarshaller(); Document document = marshaller.getNode(myObjectJava).getOwnerDocument(); // 为outerHtml添加CDATA addCDATAToElement(document, "outerHtml"); // 使用Transformer输出XML TransformerFactory transformerFactory = TransformerFactory.newInstance(); Transformer transformer = transformerFactory.newTransformer(); // 设置输出属性:格式化、编码、缩进量 transformer.setOutputProperty(javax.xml.transform.OutputKeys.INDENT, "yes"); transformer.setOutputProperty(javax.xml.transform.OutputKeys.ENCODING, "UTF-8"); transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); try (FileOutputStream outputStream = new FileOutputStream(fileXmlOutput)) { DOMSource source = new DOMSource(document); StreamResult result = new StreamResult(outputStream); transformer.transform(source, result); } return fileXmlOutput; } private static void addCDATAToElement(Document document, String elementName) { NodeList nodeList = document.getElementsByTagName(elementName); for (int i = 0; i < nodeList.getLength(); i++) { Element element = (Element) nodeList.item(i); String textContent = element.getTextContent().trim(); while (element.hasChildNodes()) { element.removeChild(element.getFirstChild()); } CDATASection cdataSection = document.createCDATASection(textContent); element.appendChild(cdataSection); } }
说明
- 两个方案均通过JAXB将Java对象转为DOM结构,手动处理CDATA节点,完全脱离了已弃用的Xerces API。
addCDATAToElement方法可复用,支持为任意指定名称的元素添加CDATA包裹。- 两种方案都保留了原代码的格式化输出、UTF-8编码特性,生成的XML与旧代码输出完全一致。
内容的提问来源于stack exchange,提问作者4535992
相关产品推荐
相关产品推荐

