XML解析器的核心工作之一是识别标签和实体。当一段文本中出现小于号时,解析器会默认其后是一个元素名称的开始;出现与符号时,会尝试读取一个实体引用。这种机制保证了XML的结构化,但也给包含大量特殊符号的文本内容带来麻烦。CDATA区段就是用来告诉解析器:这一段内容请按原样处理,不要尝试解析其中的任何标记。接下来从它解决的问题、具体语法、与实体转义的差异以及常见注意事项几个方面展开。

一、CDATA区段解决什么问题
XML中有五类预定义实体经常被误解析,分别是小于号<、大于号>、与符号&、双引号"和单引号'。如果在上层文本节点中直接写a < b && b > c,解析器会在第一个小于号处尝试读取标签名,发现后面跟着空格时就会报错;与符号后如果不是合法实体名,同样会导致解析失败。
传统做法是使用字符实体引用逐个替换,例如把小于号写成<,把与符号写成&。这种方式可以解决问题,但当嵌入的内容是整段JavaScript、SQL或HTML片段时,可读性会变得很差,维护时也容易漏掉某个符号。CDATA区段提供了一种整体豁免机制,让一整块内容都不参与XML标记解析。
CDATA全称为Character Data,也就是字符数据。它并不是一种元素,而是XML规范中定义的文本区域。解析器遇到这个区域后,会跳过内部的标签识别和实体替换过程,只把内容当作纯文本处理。因此,它非常适合承载那些本身含有大量XML保留字符的内容。
<message> <text>a < b && b > c</text> </message>
上面的代码在真实XML中是非法写法,因为<text>元素内部的<会被解析器当作新的标签起点。解决它要么进行完整转义,要么使用接下来的CDATA区段。
二、CDATA区段的基本语法
CDATA区段以<![CDATA[开始,以]]>结束。开始标记和结束标记之间就是被保护的内容,这些内容可以包含小于号、大于号、与符号、引号等字符,且无需转义。需要注意,标记本身不能被拆开,也不能插入空格。
<![CDATA[ 这里的内容不会被 XML 解析器当作标签 ]]>
下面是一个完整的消息示例,<text>元素内部包含了一段比较表达式。由于该表达式位于CDATA区段中,解析器不会把<和&&识别为XML语法。
<message>
<text><![CDATA[
a < b && b > c
]]></text>
</message>
解析上述XML后,<text>元素的内容就是字符串a < b && b > c,而不是一个带有子元素的复杂结构。CDATA区段一旦开始,内部的所有内容都按字面处理,例如写&也不会被还原成&,而是保持原样输出。
CDATA区段只能出现在元素字符数据可以出现的位置,不能用在属性值中。如果把<![CDATA[放进属性值里,它只会被当作普通字符串,不会开启任何特殊解析模式。
三、CDATA与字符实体转义的区别
字符实体转义和CDATA区段都能解决特殊字符问题,但工作方式完全不同。字符实体是在字符层面做替换,例如<表示一个小于号,>表示一个大于号。它的优点是精确,任何位置都可以使用,包括属性值;缺点是当文本中符号密集时,替换后几乎不可读,而且容易漏掉某个字符。
CDATA区段则是整段处理,一次性告诉解析器不要解析内部内容。它更适合大段代码、脚本或格式化文本,可读性更好,维护成本更低。但它也有两个主要限制:一是内部不能出现结束标记]]>,二是不能嵌套使用,也不能用在属性值中。
属性值中的特殊字符仍然只能通过实体来表达。例如下面示例中,title属性需要表示Tom & Jerry,就必须写成Tom & Jerry。
<note title="Tom & Jerry"> <body>Tom & Jerry</body> </note>
从实际使用角度看,短文本字段优先用字符实体,大段内容优先用CDATA。两者也可以混合使用,例如大部分内容放在CDATA区段中,少量动态部分通过实体或文本拼接完成。
| 对比项 | CDATA区段 | 字符实体转义 |
|---|---|---|
| 可读性 | 大段内容更直观 | 符号多时较差 |
| 使用位置 | 元素字符数据中 | 文本和属性值均可 |
| 限制 | 不能出现]]>,不能嵌套 | 需要逐个替换 |
四、在真实场景中的用法
嵌入脚本是CDATA区段最典型的应用场景之一。例如在SVG或XHTML文档中,JavaScript代码经常包含小于号、与符号和大于号。如果不用CDATA,就需要把每一个运算符都转义,代码会变得非常难以维护。
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
<script type="text/javascript"><![CDATA[
function isSmall(a, b) {
return a < b;
}
var valid = a < b && b > c;
]]></script>
</svg>
配置文件中也是常见场景。许多框架使用XML保存SQL语句或正则表达式,这些内容里会出现>、<等符号。通过CDATA区段可以避免在XML配置里写出大量>、<,让配置文件更接近原始语句。
<query><![CDATA[ SELECT * FROM users WHERE age > 18 AND name LIKE 'A%' ]]></query>
此外,在RSS或Atom订阅源中嵌入HTML摘要时,也经常使用CDATA。不过要明确一点:CDATA只是让解析器不把内容当作XML标签,它不负责数据内容的合法性校验。接收方是否能够正确处理这些文本,取决于后续的业务逻辑,而不是XML解析层。
五、常见误区与注意事项
第一个常见误区是认为CDATA内部可以继续书写XML元素。实际上,解析器对CDATA内部完全不做结构解析,即使写入<strong>这是文本,不是元素</strong>,它也不会成为子元素,而只是一个字符串。
<content><![CDATA[ <strong>这是文本,不是元素</strong> ]]></content>
第二个误区是在CDATA内部使用实体引用。由于CDATA区段会关闭实体解析,写<只会得到字面量<,不会转换成小于号。如果内容中确实需要表达实体引用本身,恰恰适合放在CDATA中;如果希望实体被解析,则应放在普通文本节点中。
第三个注意事项是结束标记的冲突。CDATA内容里不能出现]]>,否则解析器会提前结束区段。如果真实数据中必须包含这个字符串,可以将内容拆成多个CDATA区段进行拼接。例如要表示abc]]>def,可以写成下面这样。
<text><![CDATA[abc]]]]><![CDATA[>def]]></text>
上述XML解析后,<text>元素的内容是abc]]>def。这个技巧虽然不常用,但在处理某些特殊数据格式时非常有用。理解了CDATA区段的工作原理后,就可以根据内容形态灵活选择转义策略,写出更清晰、更稳定的XML文档。

