有没有办法转义 xml 中的 CDATA 结束标记?

我想知道是否有任何方法可以在 xml 文档的 CDATA 节中转义 CDATA 结束令牌(]]>)。或者,更一般地说,如果在 CDATA 中有一些可以使用的转义序列(但是如果它存在的话,我猜它可能只有转义 start 或 end 标记才有意义)。

基本上,是否可以在 CDATA 中嵌入一个开始或结束标记,并告诉解析器不要解释它,而只是将它当作另一个字符序列。

也许,如果您发现自己正在尝试重构 xml 结构或代码,那么您应该直接重构 xml 结构或代码,但是尽管我在过去3年左右的时间里每天都在使用 xml,而且我从未遇到过这个问题,我还是想知道这是否可行。只是出于好奇。

编辑:

除了使用 html 编码..。

76980 次浏览

你必须把你的数据分成几块来隐藏 ]]>

事情是这样的:

<![CDATA[]]]]><![CDATA[>]]>

第一个 <![CDATA[]]]]>]],第二个 <![CDATA[>]]>>

很明显,这个问题纯粹是学术性的。幸运的是,它有一个非常明确的答案。

您不能转义 CDATA 结束序列。 XML 规格的生产规则20非常清楚:

[20]    CData      ::=      (Char* - (Char* ']]>' Char*))

编辑: 这个产品规则的字面意思是“一个 CDData 部分可能包含任何你想要的东西,但是序列’]] >’。没有例外。”。

编辑2: 同一部分还说:

在 CDATA 节中,只有 CDEnd 字符串被识别为标记,因此左尖括号和与号可以以它们的文字形式出现; 它们不需要(也不能)使用“ &lt;”和“ &amp;”进行转义。CDATA 节不能嵌套。

换句话说,不可能使用实体引用、标记或任何其他形式的解释语法。CDATA 节中唯一经过解析的文本是 ]]>,它将终止该节。

因此,不可能在 CDATA 节中转义 ]]>

编辑3: 同一部分还说:

2.7 CDATA 组

[定义: CDATA 节可能出现在任何可能出现字符数据的地方; 它们用于转义包含字符的文本块,否则这些文本块将被识别为标记。CDATA 节以字符串开头”< ![ CDATA [“ and end with the string”]] > “ : ]

然后,在可能出现字符数据的任何地方都可能有一个 CDATA 节,其中包括多个相邻的 CDATA 节,而不是单个 CDATA 节。这使得分割 ]]>令牌并将其两部分放在相邻的 CDATA 节成为可能。

例如:

<![CDATA[Certain tokens like ]]> can be difficult and <invalid>]]>

应该写成

<![CDATA[Certain tokens like ]]]]><![CDATA[> can be difficult and <valid>]]>

Lott 的回答是正确的: 您不对结束标记进行编码,而是将其分解到多个 CDATA 节中。

如何在现实世界中解决这个问题: 使用 XML 编辑器创建一个 XML 文档,该文档将被输入到内容管理系统中,尝试写一篇关于 CDATA 部分的文章。在 CDATA 节中嵌入代码示例的常规技巧在这里将会失败。你可以想象我是怎么学会的。

但是在大多数情况下,您不会遇到这种情况,原因如下: 如果您想将 XML 文档的文本存储为 XML 元素的内容,那么您可能会使用 DOM 方法,例如:

XmlElement elm = doc.CreateElement("foo");
elm.InnerText = "<[CDATA[[Is this a problem?]]>";

DOM 相当合理地转义了 < 和 > ,这意味着您没有无意中在文档中嵌入 CDATA 节。

有意思的是:

XmlDocument doc = new XmlDocument();


XmlElement elm = doc.CreateElement("doc");
doc.AppendChild(elm);


string data = "<![[CDATA[This is an embedded CDATA section]]>";
XmlCDataSection cdata = doc.CreateCDataSection(data);
elm.AppendChild(cdata);

这可能是。NET DOM,但是不会抛出异常。这里会抛出异常:

Console.Write(doc.OuterXml);

我猜想,实际情况是 XmlDocument 正在使用 XmlWriter 生成其输出,而 XmlWriter 在编写时检查格式是否良好。

你不能逃避 ]]>,但是你可以通过在 >之前插入 ]]><![CDATA[来逃避 ]]之后的 >,就像 C/Java/PHP/Perl 字符串中的 \一样,只需要在 >之前和 ]]之后插入。

顺便说一句,

S.Lott 的回答和这个一样,只是措辞不同。

这是另一个需要逃脱 ]]>的例子。假设我们需要在 XML 文档的 CDATA 块中保存一个完全有效的 HTML 文档,而 HTML 源恰好有自己的 CDATA 块。例如:

<htmlSource><![CDATA[
... html ...
<script type="text/javascript">
/* <![CDATA[ */
-- some working javascript --
/* ]]> */
</script>
... html ...
]]></htmlSource>

已注释的 CDATA 后缀需要更改为:

        /* ]]]]><![CDATA[> *//

因为 XML 解析器不知道如何处理 javascript 注释块

在 PHP 中: '<![CDATA['.implode(explode(']]>', $string), ']]]]><![CDATA[>').']]>'

PHP 中一种更简洁的方式:

   function safeCData($string)
{
return '<![CDATA[' . str_replace(']]>', ']]]]><![CDATA[>', $string) . ']]>';
}

如果需要,不要忘记使用多字节安全的 str _ place (非 latin1 $string) :

   function mb_str_replace($search, $replace, $subject, &$count = 0)
{
if (!is_array($subject))
{
$searches = is_array($search) ? array_values($search) : array ($search);
$replacements = is_array($replace) ? array_values($replace) : array ($replace);
$replacements = array_pad($replacements, count($searches), '');
foreach ($searches as $key => $search)
{
$parts = mb_split(preg_quote($search), $subject);
$count += count($parts) - 1;
$subject = implode($replacements[$key], $parts);
}
}
else
{
foreach ($subject as $key => $value)
{
$subject[$key] = mb_str_replace($search, $replace, $value, $count);
}
}
return $subject;
}

简单地用 ]]]]><![CDATA[>代替 ]]>

看这个结构:

<![CDATA[
<![CDATA[
<div>Hello World</div>
]]]]><![CDATA[>
]]>

对于内部 CDATA 标记,必须使用 ]]]]><![CDATA[>而不是 ]]>关闭。