有没有办法在 xml 中转义 CDATA 结束令牌?

Posted

技术标签:

【中文标题】有没有办法在 xml 中转义 CDATA 结束令牌?【英文标题】:Is there a way to escape a CDATA end token in xml? 【发布时间】:2010-09-18 10:17:13 【问题描述】:

我想知道是否有任何方法可以在 xml 文档的 CDATA 部分中转义 CDATA 结束令牌 (]]>)。或者,更一般地说,如果在 CDATA 中使用了一些转义序列(但如果它存在,我想它可能只对转义开始或结束标记有意义,无论如何)。

基本上,您能否在 CDATA 中嵌入开始或结束标记,并告诉解析器不要对其进行解释,而是将其视为另一个字符序列。

也许,如果您发现自己尝试这样做,您应该重构您的 xml 结构或代码,但即使过去 3 年左右我每天都在使用 xml,而且我从来没有这个问题,我想知道是否可能。只是出于好奇。

编辑:

除了使用 html 编码...

【问题讨论】:

首先,我接受正确的答案,但请注意:没有什么可以阻止某人在 CData 中将 > 编码为 >,以确保嵌入的 ]]> 不会被解析为 CDEnd。它只是意味着它是意外的,& 也必须首先编码为&,以便可以正确解码数据。文档的用户也必须知道解码这个 CData。这并非闻所未闻,因为 CData 的部分目的是包含特定消费者了解如何处理的内容。这样的 CData 不能被任何通用消费者正确解释。 @nix,CDATA 只是提供了一种明确的方式来声明文本节点内容,这样(除了 ]]>)中的语言标记不会被解析。它特别不扩展像 > 这样的实体引用。因此,在 CDATA 块中,这仅表示这四个字符,而不是“>”。换个角度来看:在 xml 规范中,所有文本内容都称为“cdata”,而不仅仅是这些序列(“字符数据”)。这也与特定的消费代理无关。 (这样的事情确实存在——处理指令()。 (我应该补充一点,即使这种事情与节点的初衷背道而驰,但在与 XML 的漫长而痛苦的战斗中一切都是公平的。我只是觉得它可能对读者有用要知道 实际上并不是为此目的而设计的。) @Semicolon CDATA 旨在允许任何东西它们用于转义包含字符的文本块,否则这些字符会被识别为标记这也意味着CDATA,因为它也是标记。但是,事实上,你不需要我暗示的双重编码。 ]]> 是在CDATA 中编码CDEnd 的可接受方式。 没错,你不需要双重编码——但你仍然需要代理具有特殊知识,因为解析器不会解析 >作为 >。这就是你的意思,我想?解析后可以根据需要替换它们吗? 【参考方案1】:

你必须将你的数据分成几部分来隐藏]]>

这就是全部内容:

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

第一个<![CDATA[]]]]> 具有]]。第二个<![CDATA[>]]>>

【讨论】:

感谢您的回答。我宁愿寻找类似反斜杠的东西(在 C、php、Java 等的字符串中)。按照ddaa引用的规则,好像没有这种事。 这应该是公认的答案。 Escaping 是一个有点含糊的术语,但这个答案肯定体现了 escaping 的精神。太糟糕了,它不符合 OP 的 转义 的狭隘概念,由于某种原因,它任意要求反斜杠字符参与其中。 总而言之,将]]> 转义为]]]]><![CDATA[>。 5倍的长度……哇。但是,这是一个不常见的序列。 5x 的长度不仅有趣,而且在代码中也不是不常见的序列,这是 CDATA 的主要用例!假设压缩的 javascript 删除了空格,您可以通过索引从名称数组中按名称访问字段,例如“if(fields[fieldnames[0]]>3)”,现在您必须将其更改为“if( fields[fieldnames[0]]]]>3)",这违背了使用 CDATA 使其更具可读性的目的,哈哈。我想口头上给想出 CDATA 语法的人一巴掌。 那些争论“逃脱”含义的人是迂腐的。这就像说你不能调用 a='<scr'+'ipt>'foo.com/bar%20gaz 转义,只是因为虽然语言上准确,但它不是确切的技术术语。是的,有多个 CDATA 部分,是的,在极少数情况下这很重要。但根据牛津的说法,计算中的广义定义是“导致后续字符被不同地解释”。在这种情况下和提到的情况下,都会发生这种情况。【参考方案2】:

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

您不能转义 CDATA 结束序列。 XMLspecification的生产规则20很清楚:

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

编辑:此产品规则的字面意思是“CData 部分可能包含您想要的任何内容,但序列 ']]>'。没有例外。”。

EDIT2:same section 还写着:

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

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

因此,无法在 CDATA 部分中转义 ]]>

EDIT3:same section 还写着:

2.7 CDATA 部分

[定义:CDATA 段可能出现在字符数据可能出现的任何地方;它们用于转义包含字符的文本块,否则这些字符会被识别为标记。 CDATA 部分以字符串 "":]

结尾

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

例如:

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

应该写成

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

【讨论】:

确实如此。好吧,我不是学术类型,但正如我在问题中所说,我只是对此感到好奇。老实说,我只是相信你的话,因为我几乎无法理解用于规则的语法。谢谢你的回答。 这不是一个学术问题。考虑一篇博客文章的 RSS 提要,其中包含有关 CDATA 的讨论。 我的意思是“学术”的意思:“讨论很有趣,但没有实际用途”。一般情况下,CDATA 没什么用,它只是一种序列化 XML 文本的方式,在语义上相当于使用字符实体 < 转义特殊字符。 >和“。字符实体是最简单、最健壮和最通用的解决方案,因此请使用它来代替 CDATA 部分。如果您使用适当的 XML 库(而不是使用字符串构建 XML),您甚至不必考虑它。 我刚刚被这个咬了,因为我试图将一些压缩的 Javascript 编码到一个 我在现实世界中经历过。在阅读***转储并编写另一个 xml 文件时,我在National Transportation Safety Board 的页面上遇到了这个问题。它在信息框中包含 US$>1 亿(2013 年)的预算。源 xml 包含 [[United States dollar|US$]]&amp;gt;100 million (2013),读者将其翻译为 [[United States dollar|US$]]&gt;100 million (2013),作者选择使用 CDATA 转义文本并失败。【参考方案3】:

您不会转义]]&gt;,而是通过在&gt; 之前插入]]&gt;&lt;![CDATA[]] 之后转义&gt;,就像C/Java/PHP/Perl 中的\ 一样字符串,但只需要在 &gt; 之前和 ]] 之后。

顺便说一句,

S.Lott 的回答与此相同,只是措辞不同。

【讨论】:

这种说法让人误解。这是转义。 ]]]]&gt;&lt;![CDATA[&gt; 不是 ]]&gt; 的神奇序列。 ]]]]&gt;]] 字符作为数据,]]&gt; 结束当前 CDATA 部分。 &lt;![CDATA[&gt; 开始一个新的 CDATA 部分并将 &gt; 放入其中。它们实际上是两个不同的元素,在使用 DOM 解析器时会被区别对待。你应该意识到这一点。这种执行方式类似于]]]&gt;&lt;![CDATA[]&gt;,不同之处在于它将] 放在第一个CDATA 中,]&gt; 放在第二个CDATA 中。区别依然存在。 差异被夸大了,因为 CDATA 内容被视为转义文本的文字范围。只有在搞乱 DOM 时才真正重要,并且在那个级别上,您正在处理其他不可见的边界,例如文本、注释和处理指令节点。【参考方案4】:

只需将]]&gt; 替换为]]]]&gt;&lt;![CDATA[&gt;

【讨论】:

谢谢。这应该是最佳答案。【参考方案5】:

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 在写入时检查格式是否正确。

【讨论】:

嗯,我有一个几乎“真实世界”的例子。我通常从包含 CDATA 部分中的 html 标记的 Flash 加载 Xml。我想,有办法逃脱它可能会很有用。但无论如何,在这种情况下,CDATA 内容通常是有效的 XHTML,因此可以完全避免“外部”CDATA。 CDATA 几乎总是可以完全避免。我发现经常与 CDATA 斗争的人不了解他们真正想要做什么和/或他们使用的技术如何真正发挥作用。 哦,我还应该补充一点,我在回答中提到的 CMS 使用 CDATA 的唯一原因是我写了它,我不明白我真正想要做什么和/或者技术是如何工作的。我不需要使用 CDATA。 如果您使用的是 .net,前面关于 CDATA 可避免的评论是正确的 - 只需将内容写为字符串,框架将为您完成所有转义(以及读取时的转义)来自现实世界....... xmlStream.WriteStartElement("UnprocessedHtml"); xmlStream.WriteString(UnprocessedHtml); xmlStream.WriteEndElement();【参考方案6】:

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

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

注释的CDATA后缀需要改成:

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

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

【讨论】:

这不是特例。只需将]]&gt; 替换为]]]]&gt;&lt;![CDATA[&gt; 仍然适用于此。它是 JavaScript 或已注释的事实并不重要。【参考方案7】:

在 PHP 中:'&lt;![CDATA['.implode(explode(']]&gt;', $string), ']]]]&gt;&lt;![CDATA[&gt;').']]&gt;'

【讨论】:

【参考方案8】:

一种更简洁的 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;
   

【讨论】:

你能解释一下你的反对意见吗?说我做错了不如解释它在哪里有用。 如果您使用 UTF-8,则无需进行多字节安全替换。不过我没有投反对票:)【参考方案9】:

查看这个结构:

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

对于内部 CDATA 标记,您必须使用 ]]]]&gt;&lt;![CDATA[&gt; 而不是 ]]&gt; 关闭。就这么简单。

【讨论】:

以上是关于有没有办法在 xml 中转义 CDATA 结束令牌?的主要内容,如果未能解决你的问题,请参考以下文章

在终端中转义(结束)

为啥必须在 XML 属性中转义 <?

在速度模板中转义引号

在 JavaScript 中转义 HTML 实体?

xml CDATA区、特殊字符:<= 小于等于、>= 大于等于

在正则表达式中转义特殊字符