在 XML 中使用 CDATA 时关于 结束标签 ]]> 的疑问与处理

陪她去流浪 桃子 2016年06月13日 编辑 阅读次数:2301

今天在处理一个转码/转义相关的问题时,突然想到我之前遗留了一个关于“在 XML 中使用 CDATA 时,如果 CDATA 内容中包含了结束标签 ]]> 该怎么办?”的问题。

于是搜索了一番,escaping - Is there a way to escape a CDATA end token in xml? - Stack Overflow

一开始,我一直以为 XML 的 CDATA 中可以保存任意格式的内容而不需要任何转义,格式是:<![CDATA[任何内容]]>,但实际上这样是不行的,因为词法(语法)解析器终究会查找 CDATA 的结束标签 ]]> 来作为 CDATA 的结束,如果在 CDATA 内容中出现了 ]]>,很明显就会出错。同样,也是不可能使用转义字符或实体标签的,因为那样的话,CDATA 的意义就不大了。

上面问题中,针对这个问题的回答,有人说这太学术了(purely academic),而另外有人却不这样认为:“This is not an academic question. Think about an RSS feed of a blog post that contains a discussion about CDATA. – usr”(这并不学术,考虑一下你正在写一篇关于在 XML 中使用 CDATA 的文章的时候)。

从现在开始,我得严格对待这个问题了,因为我就是“另外人”中的一个,我现在在代码中犯的错跟上面完全一样,详情:movsb/taoblog: using CDATA in feed description section。我就是把整个文章的 content 直接丢入了 CDATA 中,完全没有考虑文章内容是否本身就有 ]]> 的问题。代码严谨的我是不能纵容这种错误的 :-)。

解决办法

答案其实很明显了,在使用 XML 的 CDATA 时,除了 ]]> 外的任何内容都可以 CDATA 中。于是 ]]> 这个序列就必须不能同时出现(不同时出现的意思是指在解析当前 CDATA 时的同一上下文中),必须拆开写。

于是,将要被放进 CDATA 中的内容必须]]> 作为分隔串将原内容划分成多个部分,并将这些被分隔出来的多个部分放在多个 CDATA 中,重新组合并拼出原有的 ]]>

举个例子

要把 <script><![CDATA[template]]></script> 放入 CDATA 中时,如果直接放入:

<![CDATA[<script><![CDATA[template]]></script>]]>

,很明显可以看出,出现了两个 ]]>,这将会导致解析错误。正确的做法应该是拆开写:

<![CDATA[<script><![CDATA[template]]]]><![CDATA[></script>]]>

,即分开两个 CDATA,]] 放前面,> 放后面。注意标签之间不要有多余的其它字符,否则将与原文不符。

实际运用

若是在PHP中,则可以像这样写:

$content = '<![CDATA[' . str_replace(']]>', ']]]]><![CDATA[>', $content) . ']]>';

若是在 GO 语言中,则可以像这样写:

1
2
3
4
func cdata(s string) string {
	s = strings.Replace(s, "]]>", "]]]]><!CDATA[>", -1)
	return "<![CDATA[" + s + "]]>"
}

标签:xml · HTML