有没有办法在XML中逃避CDATA结束标记?

145
我想知道在XML文档中的CDATA部分中是否有任何方法可以转义CDATA结束标记(]]>)。或者更一般地说,是否存在用于在CDATA内部使用的转义序列(但如果存在的话,我猜可能只有转义开始或结束标记才有意义)。
基本上,您能否将开始或结束标记嵌入到CDATA中,并告诉解析器不要解释它,而是将其视为另一个字符序列。
如果您发现自己试图这样做,那么很可能应该重新设计您的XML结构或代码,但是尽管我过去3年左右每天都在处理XML,却从未遇到过这个问题,因此我想知道是否可能。只是出于好奇。
编辑:
除了使用HTML编码之外...

4
首先,我认为答案是正确的,但需要注意:在CData中将>编码为>并不会阻止嵌入式]]>被解析为CDEnd。这只是意味着这种情况是出乎意料的,并且必须首先将&编码为&,以便数据可以被正确解码。文档的用户也必须知道如何解码该CData。这并不罕见,因为CData的部分目的是包含特定使用者知道如何处理的内容。这样的CData不能期望被任何通用的消费者正确地解释。 - nix
1
@nix,CDATA 只是提供了一种明确的方法来声明文本节点内容,使得其中的语言标记(除了]]>之外)不会被解析。出于这个原因,它特别不会扩展实体引用,比如 >,因此在 CDATA 块中,那只意味着这四个字符,而不是 '>'. 让我们理解:在 xml 规范中,所有文本内容都被称为 "cdata",而不仅仅是这些序列("字符数据")。此外,这与特定的消费代理无关。(尽管这种东西确实存在——处理指令(<?target instruction?>)。 - Semicolon
我应该补充一点,即使这种做法与节点的原始意图相反,在与XML的漫长而艰苦的战斗中,一切都是公平的。我只是觉得让读者知道<![CDATA[]]>实际上并不是为那个目的而设计的可能会有用。 - Semicolon
1
@分号 CDATA 的设计目的是允许_任何东西_:它们用于转义包含否则会被识别为标记的字符块的文本。这也意味着CDATA,因为它也是标记。但实际上,您不需要我暗示的双重编码。]]&gt;是在CDATA中编码CDEnd的可接受方法。 - nix
是的,您不需要双重编码——但仍需要代理具有特殊知识,因为解析器不会将>解析为>。我想这就是您的意思吧?也就是说,在解析后,您可以根据需要替换它们。 - Semicolon
10个回答

180
你需要将数据分成多个部分,以隐藏 ]]>
整个内容如下: <![CDATA[]]]]><![CDATA[>]]> 第一个标记 <![CDATA[]]]]> 包含了 ]]。第二个标记 <![CDATA[>]]> 包含了 >

32
这应该是被采纳的答案。“Escaping”是一个有点含糊不清的术语,但这个答案明确地涉及了“escaping” 的精神内涵。很遗憾它不符合提问者对于“escaping”的狭隘理解,这种理解不合理地要求反斜杠字符必须涉及其中。 - G-Wiz
6
总的来说,将]]>转义为]]]]><![CDATA[>,长度会增加5倍...哇。但这是一个不常见的序列。 - Brilliand
5
不仅长度为 5x 很有趣,而且这并不是 CDATA 的主要用例——代码中也经常出现这种序列!假设压缩了空格的 JavaScript,您可以通过索引从名称数组中访问名称字段,例如 "if(fields[fieldnames[0]]>3)"。现在您必须将其更改为 "if(fields[fieldnames[0]]]]><![CDATA[>3)",这违背了使用 CDATA 使其更易读的初衷,真是好笑。我想当面扇一巴掌那个想出 CDATA 语法的人。 - Triynko
1
转义,或更准确地说是引用,意味着在上下文中插入一些文本,使得原始文本在不离开上下文的情况下具有意义。它与反斜杠无关。而这个答案并不是转义或引用,因为它产生了两个CDATA部分而不是一个。 - ddaa
2
争论“转义”的含义的人是卖弄学问。这就像说你不能把a='<scr'+'ipt>'foo.com/bar%20gaz称为转义,仅仅因为虽然语言上准确,但不是确切的技术术语。是的,有多个CDATA部分,在极少数情况下这很重要。但根据牛津的定义,计算机领域中的广义定义是“导致后续字符被解释为不同的意思”。在这种情况和上述情况下,确实发生了这种情况。 - Beejor
显示剩余4条评论

153
你无法逃避CDATA结束序列。XML specification 的第20条生产规则非常明确:
[20]    CData      ::=      (Char* - (Char* ']]>' Char*))

编辑:这个产品规则的意思是“CData部分可以包含任何你想要的内容,但不能包含序列' ]]>'。没有例外。”
编辑2:同一部分也写道:

在CData部分中,只有CDEnd字符串被识别为标记,所以左尖括号和和符号可以以它们的字面形式出现;它们不需要(也不能)使用“<”和“&”进行转义。CDATA部分不能嵌套。

换句话说,在CData部分中不可能使用实体引用、标记或任何其他形式的解释语法。在CData部分内唯一解析的文本是]]>,并且它终止了该部分。
因此,在CData部分内无法转义]]>
编辑3:同一部分也写道:
2.7 CDATA Sections
[Definition: CDATA sections may occur anywhere character data may occur; they are used to escape blocks of text containing characters which would otherwise be recognized as markup. CDATA sections begin with the string "":]
Then there may be a CDATA section anywhere character data may occur, including multiple adjacent CDATA sections in place of a single CDATA section. That allows it to be possible to split the "]]>" token and put the two parts of it in adjacent CDATA sections.
ex:
<![CDATA[Certain tokens like ]]> can be difficult and <invalid>]]> 

应该写成
<![CDATA[Certain tokens like ]]]]><![CDATA[> can be difficult and <valid>]]> 

2
确实。嗯,我不是学术型的人,但正如我在问题中所说的,我只是对此感到好奇。老实说,我会相信你的话,因为我几乎无法理解规则中使用的语法。谢谢你的回答。 - Juan Pablo Califano
52
这不是一个学术性的问题。考虑一篇博客文章的RSS订阅,其中包含关于CDATA的讨论。 - usr
4
在这里,“academic”的意思是“有趣可讨论,但没有实际用途”。通常,CDATA并不实用,它只是一种将XML文本序列化的方式,并且在语义上等同于使用字符实体< >和"转义特殊字符。字符实体是最简单、最健壮和最通用的解决方案,因此请使用它来代替CDATA部分。如果您使用正确的XML库(而不是使用字符串构建XML),甚至不必考虑它。 - ddaa
5
我刚被这个问题困扰了,因为我试图将一些压缩过的Javascript代码编码到像这样的<script>标签中:<script>/*<![CDATA[*/javascript goes here/*]]>*/</script>,而我的Javascript代码刚好包含这个序列!我认为将其拆分成多个CDATA部分是个好主意... - NickZoic
6
我在现实世界中遇到了这个问题。当我阅读维基百科转储并编写另一个 XML 文件时,我在国家运输安全委员会页面上遇到了这个问题。信息框中的预算为“美元>100亿(2013年)”。源 XML 包含“[[美元|US$]]>100 million (2013)” ,但读者将其翻译为“[[美元 | US $]]>100 million(2013)”,而写入者选择使用 CDATA 转义文本,但失败了。 - Paul Jackson
显示剩余14条评论

24

只需将]]>替换为]]]]><![CDATA[>


1
谢谢。这应该是最佳答案。 - GC_

17

在IT技术中,当你需要处理XML数据时,如果遇到字符串包含]]>,就无法直接使用CDATA标签来表示该字符串。但是,在]]之后的>可以通过插入]]><![CDATA[来进行转义,类似于C/Java/PHP/Perl中字符串中的\,只不过这种方式只需要在]]之后的>前面插入。

顺便说一下,S.Lott的回答与此相同,只是措辞不同。


4
这种说法会让人产生误解。这并不是逃避。]]]]><![CDATA[>不是用来表示]]>的神奇序列。]]]]>]]字符作为数据,而]]>则结束当前CD​ATA部分。<![CDATA[>开始一个新的CDATA部分,并将>放入其中。它们实际上是两个不同的元素,在使用DOM解析器时会有不同的处理方式。你应该意识到这一点。这种做法类似于 ]]]><![CDATA[]> ,只是把]放在第一个CDATA中,而把> ]放在第二个CDATA中。差别还是存在的。 - Aidiakapi
1
区别被夸大了,因为CDATA内容被视为转义文本的字面量。只有在处理DOM时才真正重要,而在那个级别上,你正在处理其他不可见的边界,比如文本、注释和处理指令节点。 - Beejor

7
S. 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在写入时检查格式是否规范。

嗯,我有一个几乎是“真实世界”的例子。我通常从Flash中加载包含CDATA部分内的HTML标记的Xml。有一种方法可以对其进行转义可能会很有用。但无论如何,在这种情况下,CDATA内容通常是有效的XHTML,因此可以完全避免“外部”CDATA。 - Juan Pablo Califano
2
几乎总是可以完全避免使用CDATA。我发现那些在使用CDATA时遇到困难的人往往不理解他们真正想做什么,或者他们正在使用的技术如何工作。 - Robert Rossney
哦,我还应该补充一点,在我的答案中提到的CMS使用CDATA的唯一原因是我自己编写的,我并不理解我真正想要做什么以及技术如何运作。我并不需要使用CDATA。 - Robert Rossney
如果您正在使用 .net,有关 CDATA 可以避免的先前评论是正确的 - 只需将内容编写为字符串,框架将为您执行所有转义(并在读取时取消转义)。来自现实世界...... xmlStream.WriteStartElement("UnprocessedHtml"); xmlStream.WriteString(UnprocessedHtml); xmlStream.WriteEndElement(); - Mark Mullin

3

以下是另一个需要转义]]>的情况。假设我们需要在XML文档的CDATA块中保存一个完全有效的HTML文档,而HTML源代码恰好有它自己的CDATA块。例如:

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

注释的CDATA后缀需要更改为:
        /* ]]]]><![CDATA[> *//

由于XML解析器无法处理JavaScript注释块,因此需要注意。


这不是一个特殊情况。只需将 ]]> 替换为 ]]]]><![CDATA[> 仍然适用于此处。它是 JavaScript 或被注释掉的事实并不重要。 - Thomas Grainger

0
我想补充一下,如果你在 ]] 之间打破 CDATA 结束标记 ]]>,它也可以正常工作,就像这样:] ]]><![CDATA[ ]> 例如:
<![CDATA[Certain tokens like ]]]><![CDATA[]> can be difficult and <valid> but <unconventional>]]> 

然而全球通用的惯例是在此处断开 ]]> 并在 > 之前进行,就像其他答案中所示。

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

0
一种更简洁的 PHP 方式:

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

如果需要使用多字节安全的 str_replace(非 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;
   }

你能解释一下你为什么要给我点踩吗?仅仅说我犯了一个错误并不如解释错误出现的地方那样有用。 - Alain Tiemblo
1
如果您使用UTF-8,则无需进行多字节安全替换。虽然我没有投反对票 :) - frodeborli

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

-2

看看这个结构:

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

对于内部CDATA标签,您必须使用]]]]><![CDATA[>来关闭标签,而不是]]>。就这么简单。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接