内容


处理 XML 数据

有效地使用 CDATA 区域

Comments

简介

XML 是一种受到广泛支持的 Internet 标准,用于以一种特殊的方式编码结构化数据。实际上,以 XML 编码的数据可以通过任何编程语言解码,人们甚至可以使用标准的文本编辑器来阅读或编写 XML 数据。许多应用程序,尤其是兼容现代标准的 Web 浏览器,可以直接处理 XML 数据。

作为一个基于文本的标准,XML 非常适于在客户机和服务器系统之间交换数据。大部分数据(文件路径、描述、地址、名称等)已经是基于文本的数据,而整数、浮点数字和日期等数据可以在这些数据格式和字符串格式之间来回轻松转换。

遗憾的是,要将某些数据(比如 XHTML 或 XML 标记)包含在 XML 文档内则比较麻烦。将标记放进 XML 元素的一种方法是将某些标记字符(小于 [<]、大于 [>] 和与字符 [&])替换为它们的对等实体(分别为 <、> 和 &)。这种方法扩展了数据并使数据极其不适合人们阅读,更别提在文本编辑器里手动编写 XML 时必须转换标记给您带来的愤怒了。

更好的解决方案可能是将数据直接放到您的 XML 文档中,那就是 XML 的 CDATA 区域发挥作用的时候了。

CDATA 是何物?

XML 文档中的文本通常解析为字符数据,或者(按照文档类型定义术语)称为 PCDATA。XML 的特殊字符(&、< 和 >)在 PCDATA 中可以识别,并用于解析元素名称和实体。CDATA(字符数据)区域被解析器视为数据块,从而允许您在数据流中包含任意字符。

如果您曾经尝试过将一些 HTML 或 XML 放到一个 XML 文档(也许作为文档资料)中,那么只要您试图包含一个示例,就会遇到这个问题。清单 1 展示了一个简单的段落示例,包含一些强调文本。

清单 1. 一个 sample 元素中的一些样例 XHTML
<?xml version="1.0" encoding="UTF-8"?>
<sample>
    <description>
    Paragraphs can include emphasized text.
    </description>

    <example>
    <p>The pug snoring on the couch next to me is 
    <em>extremely</em> cute.</p>
    </example>
</sample>

当您想显示标记时,您将遇到极大的麻烦(参见 清单 2)。

清单 2. 显示标记的样例 XHTML
<?xml version="1.0" encoding="UTF-8"?>
<sample>
    <description>
    Paragraphs can include emphasized text.
    </description>

    <example>
    &lt;p&gt;The pug snoring on the couch next to me is 
    &lt;em&gt;extremely&lt;em&gt; cute.&lt;/p&gt;
    </example>
</sample>

将这个样例标记包含在一个 CDATA 区域中允许您原样编写标记,无需 XML 解析器试图将其解释为一个包含 <em> 元素的 <p> 元素。如果您的 XML 正在针对一个 DTD 或 XML 模式进行验证,则这是必须的(除非这些元素实际存在于 DTD 或 XSD 中且可以包含在文档中该位置)。参见 清单 3

清单 3. 使用 CDATA 来保护样例
<?xml version="1.0" encoding="UTF-8"?>
<sample>
    <description>
    Paragraphs can include emphasized text.
    </description>

    <example>
    <![CDATA[<p>The pug snoring on the couch next to me is 
    <em>extremely</em> cute</p>]]>
    </example>
</sample>

使用 CDATA

清单 3 中的简单示例所示,CDATA 区域的起始标记是一个特殊的序列 <![CDATA[,结束标记是 ]]> 序列。这些标记之间的任何内容都将原封不动地通过 XML 解析器。有些开发平台拥有特殊的 CDATA 对象(比如 XML DOM 中的 CDATASection)来表示 CDATA 区域中的内容,但其他平台将其作为更通用的组件提供,通常是 XML 文本节点。不管是哪种情况,CDATA 区域的内容都将不经修改就可用。

即使 XML 通常允许有空白区域,但 ]]> 区域结束标记不能包含任何空格和换行符。

XHTML 中的 CDATA

如果您已经看到过许多包含嵌入式 JavaScript 的 Web 页面,那么您也就看到了 CDATA 的实际应用。您将经常看到类似于 清单 4 的内容。

清单 4. XHTML 的 <script> 元素中的 CDATA
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
         "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" 
content="application/xhtml+xml;charset=utf-8"/>
<title>CDATA Section in Action</title>
<script type="text/javascript">
// <![CDATA[
function nowWeAreSafe( x, y, z ) {
    // Without the CDATA section, these would cause 
    // parsing errors:
    if( x < y && y > z ) {
        return y--;
    }
    return 0;
}
// ]]>
</script>
</head>
<body>
...
</body>
</html>

<script> 元素中的 JavaScript 开始于一个包含 CDATA 区域的开始标记的注释,结束于一个关闭这个 CDATA 区域的注释。这种方法看起来没有意义,只会使您的 XHTML 和 JavaScript 更杂乱,除非您意识到:没有这个 CDATA 区域,您的脚本将通过 Web 浏览器的 XHTML 解析器运行。

这通常不会导致问题出现,除非您非常 “幸运”,但这肯定会导致解析器错误,这种解析器错误会导致令人迷惑、难以调试的呈现错误。这是什么原因呢?

如您所料,<、> 和 & 字符可以标记为元素或实体(或者游离的标记字符)。而且,双短横线(--)序列可以视为一个 XHTML 块的意外开始(或结束)。事实上,那就是为什么应该将嵌入式脚本封装到 CDATA 区域而不是 XML 注释中的原因 — 注释太脆弱了。

CDATA 有时也出现在内联 <style> 元素中,尽管这不太常见(参见 清单 5)。

清单 5. CDATA 阻止 <style> 元素中的解析错误
<style type="text/css">
/* <![CDATA[ */
body {
    background-image: 
        url("marble.png?width=300&height=300")
    }
/* ]]> */
</style>

请再次注意 CDATA 标记是如何隐藏在特定于语言的注释中的,这样它们就不会迷惑客户机 Web 浏览器中的 CSS 解析器。

CDATA 的局限

显然,CDATA 区域很有用;但与其他所有好东西一样,它也有几个应该牢记的限制:

浏览器通常不是 XML 解析器

浏览器不会可靠地处理 HTML 或 XHTML 中的 CDATA(如果有的话)。CDATA 区域可以出现在 XHTML 中的任意位置(就像在任何 XML 应用程序中一样),但在实践中,这些 CDATA 区域通常会被忽略。它们的内容要么丢失(CDATA 区域从正常的 DOM 中消失),要么显示为文本,并带有一些游离的标记字符。

为了查看这种效果,请检查一个页面,该页面显示样例段落,带有可见标记(使用实体)的样例段落,并尝试使用 CDATA 显示带有可见标记的样例段落。这个 XHTML 页面源代码如 清单 6 所示。

清单 6. 尝试在 XHTML 中使用 CDATA
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="application/xhtml+xml;charset=utf-8"/>
<title>CDATA Section in Action</title>
</head>
<body>
<h1>CDATA Section in Action</h1>

<p>
A sample paragraph:
</p>

<p>The pug snoring on the couch next to me is <em>extremely</em> 
cute.</p>

<p>
Markup version:
</p>

<p id="no1">
<p>The pug snoring on the couch next to me is <em>extremely</em> cute.</p>
</p>

<p>
CDATA version:
</p>

<p id="no2">
Uh,
<![CDATA[<p>The pug snoring on the couch next to me is <em>extremely</em> cute.</p>]]>
where?
</p>

<p>
Wait, what?
</p>

</body>
</html>

Firefox 3 “吃掉了” CDATA 区域的内容,如 图 1 所示。(查看 图 1 的文本版本。)

图 1. Firefox 忽略 CDATA 区域
Firefox 忽略 CDATA 区域,没有显示这些区域
Firefox 忽略 CDATA 区域,没有显示这些区域

基于 WebKit 的浏览器(比如 Safari 和 Chrome)使用伪造的标记字符来显示这个页面(见 图 2)。(查看 图 2 的文本版本。)

图 2. Safari 和 Chrome 呈现 CDATA 区域
Safari 和 Chrome 呈现 CDATA 区域,但是有一些错误字符
Safari 和 Chrome 呈现 CDATA 区域,但是有一些错误字符

Internet Explorer® 也使用一些类似的伪造标记字符来显示这个页面(参见 图 3)。(查看 图 3 的文本版本。)

图 3. Internet Explorer 8 也呈现 CDATA 区域
Internet Explore 8 也呈现 CDATA 区域,但是有一些错误字符
Internet Explore 8 也呈现 CDATA 区域,但是有一些错误字符

尽管当 XHTML 文档包含 CDATA 区域时浏览器的表现不正常,但浏览器的确能够正确处理通过 Ajax 加载的 XML 文档中的 CDATA 区域。如果某个浏览器不能正确处理,那么人们将认为该浏览器的 XML 解析器 “不规范”,无情地嘲笑它,然后给它贴上 “肆意破坏 Ajax 规范” 的标签。

区域结束标记仍然特殊

即使您能够将任何内容放到一个 CDATA 区域中,这个区域结束标记的序列(]]>)仍然被认为是特殊的。CDATA 区域绝对不能嵌套。如果 XML 解析器读到这个序列,就认为您的 CDATA 区域结束,当解析器遇到真正的区域结尾时,可能会发出一条解析错误消息。

换句话说,XML(或 XHTML)解析器不允许在一个 CDATA 区域之内使用 <![CDATA[,因为解析器将忽略除区域结束标记 ]]> 之外的其他标记字符(参见 清单 7)。

清单 7. 这是无效 XML,您不能嵌套 CDATA 区域
<?xml version="1.0" encoding="UTF-8"?>
<sample>
    <description>
    You can't nest CDATA sections.
    </description>

    <example>
    <![CDATA[You want a <![CDATA[ ]]> inside your
    example? No, this is wrong.]]>
    </example>
</sample>

如果您需要将区域结束标记放到 CDATA 区域中,那么该怎么办呢?您需要将它分割为两个 CDATA 区域(见 清单 8)。

清单 8. 将区域结束标记放到 CDATA 区域中的正确方法
<?xml version="1.0" encoding="UTF-8"?>
<sample>
    <description>
    Split up the section end.
    </description>

    <example>
    <![CDATA[You want a ]]]]><![CDATA[>
    inside your example? Do it this way.]]>
    </example>
</sample>

也就是说,将您的数据中的任何 ]]> 替换为 ]]]]><![CDATA[>,以便序列中最后的 > 远离这些中括号。解析器正在特意寻找作为三个字符序列的 ]]>,通过分割它,您打破了这个序列。

不错,]]]]><![CDATA[> 是一大块令人畏惧的标记。幸运的是,这种情况并不经常出现。

区域结束标记仍然是文本

即使 CDATA 区域的内容完好无损地通过您的解析器,它们仍需是有效的 XML 数据字符,正如文档的字符编码所规定的一样。使用类似于 UTF-8 之类的编码方式,您可以对数据使用广泛的字符,但它并非完全是 8 位的。

所有所谓的控制字符(那些 16 进制值低于 0x20 的字符,空格字符)可能会导致您的解析器因为一个无效的标记错误而停止工作。您不能将任何数据随意地塞到一个 CDATA 区域中同时还拥有一个有效的文档。

大小问题

当您使用 CDATA 区域在 XML 中添加数据块时,最后需要考虑的问题是大小。如果您通过 Web 服务来提供 XML 文件,确保您的客户机应用程序能够处理潜在的大型数据传输,当数据通过 3G 连接缓慢传输时不会导致超时或堵塞它们的用户界面。

反过来也一样;确保您的服务器能够接受来自发送 XML 数据的客户机的大型上游传输。Web 服务器(特别是 Windows® 平台上的 IIS)通常具有相当小的上传限制,不能帮助阻止拒绝服务攻击。像这样从浏览器发送大量数据容易出错(例如,用户可能会认为传输已经崩溃而取消传输,出现这种情况该怎么办呢?),这样很可能会锁定服务器和客户机上的有用资源。

同样,根据您正在做的工作,您需要记住:许多人正在使用移动平台,其他人也可能被堵塞在拨号连接上(仍然如此!),假定您的应用程序在您的 LAN 外部工作。

即使您的应用程序不是那样设计的,有的人还是会试图通过他们 iPhone 手机上的拨号 VPN 连接使用它,他们会抱怨您的应用程序速度太慢,而不是他们愚蠢的生活选择!

在 XML 中存储二进制数据

当您的确需要在 XML 文档中包含二进制数据时,您需要确保这些数据不会给 XML 解析器带来麻烦。如果这些数据碰巧是文本,您只需将它们放到 CDATA 区域中,但真正的二进制数据需要以一种安全且可恢复的方式编码。

幸运的是,MIME 标准定义了一种受到广泛支持的、安全的编码模式:base64。经过 base64 编码的二进制数据大约是原来大小的 137%,因此您需要在额外的存储空间(和少许处理吞吐量)与在 XML 文档中嵌入二进制数据的能力之间找到平衡。

通常,您需要在 XML 文档中标明原始文件和已编码文件的文件名,如 清单 9 所示。

清单 9. XML 文档内的 base64 编码文件示例
<?xml version="1.0" encoding="UTF-8"?>
<sample>
    <description>
    An embedded image file.
    </description>
    
    <image name="stop.png" encoding="base64"
        source="FamFamFam"
        href="http://www.famfamfam.com/lab/icons/silk/">
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQ
CAYAAAAf8/9hAAAABGdBTUEAAK/INwWK
6QAAABl0RVh0U29mdHdhcmUAQWRvYmUg
SW1hZ2VSZWFkeXHJZTwAAAJOSURBVDjL
pZI9T1RBFIaf3buAoBgJ8rl6QVBJVNDC
ShMLOhBj6T+wNUaDjY0WmpBIgYpAjL/A
ShJ+gVYYYRPIony5IETkQxZ2770zc2fG
YpflQy2MJzk5J5M5z/vO5ESstfxPxA4e
rL4Zuh4pLnoaiUZdq7XAGKzRJVbIBZ3J
PLJaD9c/eCj/CFgZfNl5qK5q8EhTXdxx
LKgQjAFr0NK0ppOpt9n51D2gd2cmsvOE
lVcvOoprKvuPtriNzsY8rH+H0ECoQEg4
WklY1czP8akZby51p6G3b6QAWBl43llS
VTlUfuZE3NmYh9Vl0HkHSuVq4ENFNWFd
C+uJ5JI/9/V2Y//rkShA1HF6yk/VxJ0f
07CcgkCB7+fSC8Dzcy7mp4l9/khlUzwe
caI9hT+wRrsOISylcsphCFLl1RXIvBMp
YDZJrKYRjHELACNEgC/KCQQofWBQ5nuV
64UAP8AEfrDrQEiLlJD18+p7BguwfAoB
UmKEsLsAGZSiFWxtgWWP4gGAkuB5YDRW
ylKAKIDJZBa1H8Kx47C1Cdls7qLnQTZf
fQ+20lB7EiU1ent7sQBQ6+vdq2PJ5dC9
ABW1sJnOQbL5Qc/HpNOYehf/4lW+jY4v
h2tr3fsWafrWzRtlDW5f9aVzjUVj72Fm
CqzBypBQCKzbjLp8jZUPo7OZyYm7bYkv
w/sAAFMd7V3lp5sGqs+fjRcZhVYKY0xu
pwysfpogk0jcb5ucffbbKu9Esv1Kl1N2
+Ekk5rg2DIXRmog1Jdr3F/Tm5mO0edc6
MSP/CvjX+AV0DoH1Z+D54gAAAABJRU5E
rkJggg==
    </image>
</sample>

在机器生成的 XML 文档中,您可以省略空白,并且无需换行字符就可以运行整个 base64 编码的文件。

避免问题

处理 XML 中的二进制数据的最好方法是完全避免这种数据。正如您在 HTML 中所看到的,以一种标准方式引用一个外部文件效果很好。当您有某种途径来使客户机应用程序获取外部文件时,这是一个极好的选择。对于 HTML,浏览器只是发出另一个 HTTP 请求,获取通过 <img> 这样的元素包含的数据。

通过不在 XML 中直接包含二进制数据,您避免了潜在的文本编码浪费,并有可能实现其他增强功能,比如多数人喜爱的 Web 浏览器中的图像缓存。

结束语

您可以使用 XML 的 CDATA 区域(开始标记为 <![CDATA,结束标记为 ]]>)来使您文档中的一部分远离解析器。CDATA 区域中的数据将原样通过解析器,进去时是什么文本,出来时还是什么文本;尽管您需要通过停止并重新开始 CDATA 区域来保护任何 ]]> 序列。

即使您不能在 XHTML 文档中利用 CDATA 的优势,XML 仍然在浏览器和常规编程平台中受到很好的支持。使用 CDATA 来将标记数据直接嵌入 XML 文档免除了编码数据的烦恼,但是您要小心翼翼,并考虑客户机和服务器上(潜在)大型数据传输的影响。

当您需要在 XML 文档中存储二进制数据时,可以使用标准的 MIME base64 这样的文本编码,尽管引用一个外部文件可能是个更好的主意。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=XML
ArticleID=470198
ArticleTitle=处理 XML 数据
publish-date=03012010