處理 XML 數據:有效地使用 CDATA 區域

  • 當您在XML文件中存儲數據時,您通常需要仔細以安全且不易使XML解析器混淆的方式進行編碼。特殊的XML標記字符需要被轉換爲一些實體,如果您在文本編輯器中親自編寫XML,這種需求可能會很麻煩。爲避免這種麻煩,您可以使用CDATA區域來直接存儲數據,而不必擔心編碼問題。本文將向您介紹CDATA區域,以及如何使用這些CDATA區域,以便在提供XML文件的同時提供標記好的數據。

簡介

常用縮略詞

Ajax:異步 JavaScript + XML

API:應用程序編程接口

CSS:層疊樣式表

DOM:文檔對象模型

DTD:文檔類型定義

HTML:超文本標記語言

HTTP:超文本傳輸協議

IIS:Internet 信息服務

LAN:局域網

MIME:多用途 Internet 郵件擴展

UTF:統一碼轉換格式

VPN:虛擬專用網

XHTML:可擴展超文本標記語言

XML:可擴展標記語言

XSD:XML 模式定義

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>

Firefox3 “吃掉了” CDATA 區域的內容,如 圖 1 所示。

圖 1. Firefox 忽略 CDATA 區域
處理 XML 數據:有效地使用 CDATA 區域

查看原圖(大圖)

基於 WebKit 的瀏覽器(比如 Safari 和Chrome)使用僞造的標記字符來顯示這個頁面(見 圖 2)。

圖 2. Safari 和 Chrome 呈現 CDATA 區域
處理 XML 數據:有效地使用 CDATA 區域

查看原圖(大圖)

Internet Explorer? 也使用一些類似的僞造標記字符來顯示這個頁面(參見 圖 3)。

圖 3. Internet Explorer 8 也呈現 CDATA 區域
處理 XML 數據:有效地使用 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 這樣的文本編碼,儘管引用一個外部文件可能是個更好的主意。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章