在XML数据处理中,CDATA段用于包裹不需要解析器做实体转义的文本内容。当我们将外部系统返回的报文、富文本或含有大量符号的脚本映射到内部对象时,如果忽略CDATA的存在,就容易出现内容被截断、符号被误转义或者映射后格式错乱的问题。理解解析器如何识别CDATA,并采用正确的读取与转换方式,是稳定处理XML映射的前提。

什么是CDATA以及为什么需要它
CDATA全称是Character Data,它在XML中以 <![CDATA[ 开头,以 ]]> 结尾。被包裹在其中的所有内容都会被解析器视为纯字符,不会对其中的 <、>、& 等符号做标记语言处理。比如一段SQL语句或JavaScript代码,如果直接写在XML元素里,其中的小于号会被当成标签开始,从而导致解析失败。
使用CDATA之后,内容可以原样保留。但要注意,CDATA内部不能嵌套 ]]>,否则会提前结束区段。在映射场景中,我们通常不是为了“显示”CDATA,而是为了在跨系统传输时保护内容完整性。很多旧版接口约定用CDATA承载报文主体,如果映射层错误地对其再做一层转义,接收方就会拿到双重编码的字符串。
不同语言中读取CDATA的方式
以Java的DOM解析为例,当节点包含CDATA时,getTextContent() 通常会把CDATA和普通文本合并返回,而通过 getChildNodes() 遍历则能看到 CDATA_SECTION_NODE 类型的节点。下面示例展示如何判断并提取CDATA内容:
import org.w3c.dom.*;
import javax.xml.parsers.*;
import java.io.ByteArrayInputStream;
public class CdataRead {
public static void main(String[] args) throws Exception {
String xml = "<root><desc><![CDATA[if (a < b) { return true; }]]></desc></root>";
DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document doc = db.parse(new ByteArrayInputStream(xml.getBytes("UTF-8")));
Node desc = doc.getElementsByTagName("desc").item(0);
// 遍历子节点,识别CDATA类型
NodeList children = desc.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node n = children.item(i);
if (n.getNodeType() == Node.CDATA_SECTION_NODE) {
System.out.println("CDATA内容: " + n.getNodeValue());
} else if (n.getNodeType() == Node.TEXT_NODE) {
System.out.println("普通文本: " + n.getNodeValue());
}
}
}
}
Python的ElementTree对CDATA的支持相对隐式:默认解析时CDATA会被合并进元素的 text 属性,你无法直接从API区分它原来是CDATA还是普通文本。如果业务必须区分,需要使用自定义树构建器或者改用lxml库。以下代码展示用lxml保留CDATA标记读取:
from lxml import etree
xml_str = "<root><desc><![CDATA[line1 & line2]]></desc></root>"
parser = etree.XMLParser(strip_cdata=False)
root = etree.fromstring(xml_str.encode("utf-8"), parser)
desc = root.find("desc")
# 查看内部是否含有CDATA片段
for child in desc.iter():
if isinstance(child, etree._CDATA):
print("找到CDATA:", child.text)
else:
print("文本:", desc.text)
映射为对象时的常见陷阱
在把XML映射成JSON或Java Bean时,很多人会用通用框架如Jackson XML或JAXB。这类框架默认把元素文本映射成字符串字段,不会单独标注是否来自CDATA。如果上游系统在CDATA里放了HTML片段,映射后字段值是正确的HTML,但一旦你再把这个字符串当作XML属性值输出,就可能因未转义而破坏结构。
另一个陷阱是“重复转义”。假设接口约定CDATA里放的是已经转义好的XML报文,某些开发者在映射层又调用了 escapeXml(),结果接收方从CDATA取出后还要再解一次,导致内容多出 < 这类符号。正确做法是:在映射配置中明确该字段是原始流,禁止二次编码,只在最终序列化到XML并且确实不在CDATA中时才做转义。
写入时如何生成CDATA
如果我们的程序需要产出含有CDATA的XML,应当使用支持CDATA写入的API,而不是手动拼接字符串,以免忘记闭合 ]]>。下面以Java的DOM写为例:
import org.w3c.dom.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
public class CdataWrite {
public static void main(String[] args) throws Exception {
Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
Element root = doc.createElement("root");
Element script = doc.createElement("script");
// 创建CDATA节点并追加
CDATASection cdata = doc.createCDATASection("if (x < 10) { alert('ok'); }");
script.appendChild(cdata);
root.appendChild(script);
doc.appendChild(root);
Transformer tf = TransformerFactory.newInstance().newTransformer();
tf.transform(new DOMSource(doc), new StreamResult(System.out));
}
}
使用框架如JAXB时,可以通过 @XmlCData 之类的扩展注解(某些实现提供)或者自定义适配器,在序列化指定字段时包成CDATA。重点是保持“读”和“写”的约定一致:如果读取方依赖CDATA保护内容,写入方就必须真实输出CDATA区段,而不是转义后塞进普通文本。
总结性处理建议
处理XML中的CDATA内容映射,核心在于厘清解析器行为、明确接口契约以及避免重复编码。建议在项目里封装统一的XML工具类,对CDATA节点做显式标记与日志输出,便于排查映射异常。同时在接口文档中写清哪些字段使用CDATA承载,这样前后端和第三方对接时就不会因理解偏差引发解析错误。
当系统升级解析库时,也要回归测试CDATA读写路径,因为不同版本对空白符和区段合并的策略可能有细微差别。把CDATA当成“内容容器”而非“显示语法”,才能在复杂数据映射中少踩坑。