目錄
1、跨平台數據格式
2、XML 和字符編碼
3、字符集和 MSXML DOM
4、用 MSXML 創建新的 XML 文檔
5、結論
6、有關詳細信息
摘要:本文介紹了字符編碼的工作原理,特別是在 XML 和 MSXML DOM 中的工作原理。
近來很多人問我,如何使 XML 文件在不同的平台間正確地傳遞數據。他們創建了 XML 文檔,鍵入了數據,粘上了幾個標記,調整了標記的格式,甚至放入了 <?xml version="1.0"?> 聲明,作為額外增添。然後他們試著裝載它,可得到的卻是意想不到的出錯消息,Microsoft(R) XML Parser (MSXML) 報告說數據有問題。對初編 XML 者來說,這真令人沮喪。難道它竟不能正常工作嗎?
當然不是。當從 MSXML 收到意想不到的出錯消息時,很可能是因為接收數據的平台將其存儲到了與發送數據不同的平台上,結果導致了字符編碼問題。
跨平台數據格式
自從計算機
軟件和硬件從業人員設法將兩台計算機連接到一起以來,他們就一直向著創建跨平台技術並使不同的平台能夠共享數據的領域而努力奮斗。很早以來,由於不同類型的計算機的數量、連接方式、希望共享的數據種類的急劇增加,事態也就變得越來越復雜。
經過幾十年關於跨平台
編程技術的研究,當今(而且可能在未來的很長一段時期內)唯一的真正跨平台解決方案是通過簡單的標准數據格式得到的。Web 的
成功正是建立在這種格式上的。在 Web 服務器和 Web
浏覽器之間傳遞的主要內容是 HTTP 標題和 Html 頁,兩者都是標准的文本格式。
在以下幾節中,我將討論字符編碼和標准字符集、Unicode、HTML Content-Type 標題、Html Content-Type 元標記和字符實體。如果您熟悉上述概念,可跳過這些內容去讀 encoding XML data for the XML Document Object Model (DOM) programmer(針對 XML 文檔對象模型 (DOM) 編程人員的編碼 XML 數據)的提示和技巧。有關詳細信息,請參閱 XML and Character Encoding(XML 和字符編碼)。
關於字符編碼
標准文本格式是建立在標准字符集之上的。要記住,所有的計算機均將文本存儲為數字。然而,不同的系統也可以用不同的數字存儲相同的文本。下表顯示了一組字節是如何被存儲的,第一個是使用默認代碼頁 1252、運行 Microsoft Windows(R) 的典型計算機,第二個是使用 Macintosh Roman 代碼頁的典型 Apple(R) Macintosh(R) 計算機。
Byte Windows Macintosh
140 Œ å
229 å Â
231 ç Á
232 è &EUML;
233 é È
比方說,如果您的祖母從 http://www.barnesandnoble.com/(英文)訂購了一本新書,她不會想到她的 Macintosh 計算機存儲字符的方式,並不同於運行 www.barnesandnoble.com(英文)的新
Windows 2000 Web 服務器。在往 Internet 訂購單的發貨欄中輸入瑞典家中的地址時,她相信 Internet 會正確地傳遞字符 å(在其 Macintosh 上的字節值是 140),並沒想到接收和處理她發送消息的計算機會將字節值 140 轉換為字母 Œ。
Unicode
Unicode Consortium(統一碼協會)確信(用雙字節而不是單字節表示每個字符)定義一個通用的代碼頁是個好主意,該代碼頁適用於全世界所有的語言,從而不同代碼頁之間的映射問題將不復存在。
既然如此,如果 Unicode 解決了跨平台的字符編碼問題,那為何它卻未成為唯一的標准呢?第一個問題是,轉換到 Unicode 有時意味著使所有的文件大小加倍 — 這樣做在網絡世界中是不可想象的。因此有人仍樂於使用老的、單字節的字符集,如 ISO-8859-1 到 ISO-8859-15、Shift-JIS、E
UC-KR 等等。
第二個問題是,仍存在許多並非基於 Unicode 的系統,這就意味著在網絡上,某些組成 Unicode 字符的字節值可能會給那些更舊的系統造成嚴重問題。因此定義了“Unicode 轉換格式 (UTF)”;它們運用位轉換技術對 Unicode 字符進行編碼,使其成為在老系統上“透明的”(或可
安全通過)的字節值。
此類字符編碼中最普及的是 UTF-8。UTF-8 采用 Unicode 標准的前 127 個字符(它們恰好是基本的拉丁文字符:A-Z、a-z 和 0-9,以及幾個標點字符),並直接將其映射到單字節值。然後采用位轉換技術,用字節的高位來編碼 Unicode 字符的其余部分。這樣做的結果是,小瑞典字符 å (0xE5) 變成了下列雙字節亂碼:Ã¥ (0xC3 0xA5)。所以,除非您能夠在腦海裡進行位轉換,否則,在UTF-8 中編碼的數據是無法被人讀懂的。
Content-Type 標題
因為更舊的單字節字符集仍被使用,所以只有當指定了數據所在的實際字符集之後,傳輸數據的問題才能得以解決。認識到這一點後,Internet 電子
郵件和 HTTP 協議小組定義了一種標准方法,用以在消息標題 Content-Type 屬性中指定字符集。該屬性從注冊的字符集名稱列表中指定一個字符集,該字符集名稱是由 Internet Assigned Numbers Authority (IANA)定義的。典型的 HTTP 標題都可能包含下列文本:
HTTP/1.1 200 OK
Content-Length: 15327
Content-Type: text/Html; charset:ISO-8859-1;
Server: Microsoft-IIS/5.0
Content-Location: http://www.microsoft.com/Default.htm
Date: Wed, 08 Dec 1999 00:55:26 GMT
Last-ModifIEd: Mon, 06 Dec 1999 22:56:30 GMT
該標題向應用程序表明,跟在標題後面的內容位於 ISO-8859-1 字符集中。
Content-Type 元標記
Content-Type 屬性是可選項,在有些應用程序中,HTTP 標題的信息被去掉了,而只有 HTML 本身通過。為了補救這一點,HTML 標准小組定義了一種可選的元標記方法,用於指定 HTML 文檔本身的字符集,使 Html 文檔字符集是自描述的。
<META HTTP-EQUIV="Content-Type" CONTENT="text/Html; charset=ISO-8859-1">
在這種情況下,字符集 ISO-8859-1 說明在此特定的 Html 頁中,字節值 229 表示 å。現在該頁對任何系統來說,都是完全清楚的,數據不會被曲解。遺憾的是,由於此元標記是可選的,所以它給錯誤留下了空子。
字符實體
不是所有的系統支持所有的注冊字符集。例如,我並不認為很多平台實際上可支持稱為 EBCDIC 的 IBM 主機字符集。Windows NT 是支持的,但許多其他系統很可能不支持 — 這大概就是 http://www.ibm.com(英文)主頁為什麼生成 ASCII 的原因。
作為備選方案,Html 允許通過指定確切的 Unicode 字符值,對該頁中的單個字符進行編碼。然後將這些字符實體進行脫離字符集的分析,即可確切地確定其 Unicode 值。它的語法是 ?amp;#229;?or ?amp;#xE5;?。
XML 和字符編碼
XML 從 Html 那裡借鑒了這些思想,並使之更進一步,定義了一個徹底明確的算法,以確定編碼使用的字符集。在 XML 中,由 XML 聲明中的可選編碼屬性定義字符編碼。下列算法確定默認的編碼:
如果文件以 Unicode 字節次序標志 [0xFF 0xFE] 或 [0xFE 0xFF] 開頭,則認為該文檔是在 UTF-16 編碼中。否則,它在 UTF-8 中。
以下是所有正確和等效的 XML 文檔:
字符集或編碼 HTTP 標題 XML 文檔
ISO-8859-1 Content-Type: text/XML; charset:ISO-8859-1; <test>ålt;/test>
UTF-8 Content-Type: text/XML; <test>Ã¥</test>
ISO-8859-1 Content-Type: text/xml; <?XML version="1.0" encoding="ISO-8859-1"?>
<test>ålt;/test>
UTF-8(用字符實體) Content-Type: text/XML; <test>å</test>
UTF-16(帶字節次序標志的 Unicode) Content-Type: text/XML; ff fe 3c 00 74 00 65 00 73 00 74 00 3e 00 e5 00 ..<.t.e.s.t.>...
3c 00 2f 00 74 00 65 00 73 00 74 00 3e 00 0d 00 <./.t.e.s.t.>...
0a 00
字符集和 MSXML DOM
現在,討論過不同的字符編碼方法之後,讓我們來看看如何在 MSXML DOM 中加載 XML 文檔,以及當碰到模糊編碼的字符時,可能收到的出錯消息的類型。加載 XML DOM 文檔的兩個主要方法是 LoadXML 方法和 Load 方法。
LoadXML 方法總是采用只在 UCS-2 或 UTF-16 中編碼的 Unicode BSTR。如果將非有效的 Unicode BSTR 的內容傳遞給 LoadXML,則加載會失敗。
Load 方法可將以下內容當作 VARIANT:
Value Description
URL 如果 VARIANT 是 BSTR,則將其理解為 URL。
VT_ARRAY VT_UI1 VARIANT 也可以是包含原始編碼字節的 SAFEARRAY。
IUnknown 如果 VARIANT 是 IUnknown 接口,則 DOM 文檔為 IStream、IPersistStream 和 IPersistStreamInit 調用 QueryInterface。
Load 方法實現以下算法,用於確定 XML 的字符編碼或字符集。
如果 Content-Type HTTP 標題定義了字符集,該字符集則替代 XML 文檔本身的所有內容。因為沒有 HTTP 標題,所以這顯然不適用於 SAFEARRAY 和 IStream 機制。
如果有雙字節 Unicode 字節次序標志,則它假設編碼是 UTF-16。它既可處理大 endian,也可處理小 endian。
如果有四字節 Unicode 字節次序標志 (0xFF 0xFE 0xFF 0xFE),則它假設編碼是 UTF-32。它既可處理大 endian,也可處理小 endian。
否則,它假設編碼是 UTF-8,除非它用指定其他一些字符集的編碼屬性(如 ISO-8859-1、Windows-1252、Shift-JIS 等),找到 XML 聲明。
您將看到兩個從 XML DOM 返回的指出編碼問題的出錯消息。第一個通常指出文檔中的字符與 XML 文檔的編碼不匹配:
在文本內容中發現了一個無效字符。
ParseError 對象可告訴您這個搗亂字符在某一行的確切位置,以便使您解決該問題。
第二個出錯消息指出您一開始用的是 Unicode 字節次序標志(或調用了 LoadXML 方法),然後編碼屬性指定了不是雙字節編碼的編碼(如 UTF-8 或 Windows-1250):
不支持從當前編碼轉換到指定的編碼。
另外,您可能調用了 Load 方法,並在一開始使用了單字節編碼(沒有字節次序標志),但是它隨後發現了指定雙字節或四字節編碼(如 UTF-16 或 UCS-4)的編碼屬性。
基本原則是不能利用 XML 聲明的編碼屬性,在多字節字符集如 UTF-8、Shift-JIS 或 Windows-1250,與 Unicode 字符編碼如 UTF-16、UCS-2 或 UCS-4 之間進行轉換,這是因為聲明本身必須對每個字符都使用與文檔其余部分相同數量的字節。
最後,IXMLHttpRequest 接口提供如下方法,用以訪問
下載的數據:
Methods Description
ResponseXML 表示由 MSXML DOM 分析器分析的響應實體(用與 Load 方法相同的規則)。
ResponseText 表示作為字串的響應實體。本方法盲目地解碼從 UTF-8 收到的消息實體。這是一個已知問題,應在即將面市的 MSXML Web Release 中得到解決。
ResponseBody 表示作為無符號字節數組的響應實體。
ResponseStream 表示作為 IStream 接口的響應實體。
用 MSXML 創建新的 XML 文檔
一旦加載了 XML 文檔,即可用 DOM 處理 XML 文檔,而不必考慮任何編碼問題,因為文檔是作為 Unicode 存儲在內存中的。所有 XML DOM 接口都是基於 COM BSTR 的,後者是雙字節的 Unicode 字串。這就是說,您可以從新開始在包含所有 Unicode 字符的內存中建立 MSXML DOM 文檔,並且所有組建將會共享該內存中的 DOM,而不會對 Unicode 字符值的意思有任何疑惑。然而,當對其進行保存時,MSXML 將以默認方式按 UTF-8 編碼所有數據。例如,假設您進行了以下處理:
var xmldoc = new ActiveXObject("Microsoft.XMLDOM")
var e = XMLdoc.createElement("test");
e.text = "å;
XMLdoc.appendChild(e);
xmldoc.save("foo.XML");
下列 UTF-8 編碼文件的結果是:
<test>Ã¥</test>
注意 上述例子只有當在浏覽器以外的環境運行時,才有效。由於受到安全限制,在浏覽器裡調用 Save 方法將不會產生相同的結果。
盡管這看上去有點怪,但卻是正確的。下列測試裝載了用 UTF-8 編碼的文件,並測試 UTF-8 是否被重新解碼為 Unicode 字符值 229。它是:
var xmldoc = new ActiveXObject("Microsoft.XMLDOM")
xmldoc.load("foo.XML");
if (XMLdoc.documentElement.text.charCodeAt(0) == 229)
{
WScript.echo("Yippee - it worked !!");
}
要想更改 XML DOM Save 方法使用的編碼,需要用如下位於文檔頂部的編碼屬性創建 XML 聲明:
var pi = xmldoc.createProcessingInstruction("XML",
" version='1.0' encoding='ISO-8859-1'");
XMLdoc.appendChild(pi);
調用 save 方法時,您就會得到以下用 ISO-8859-1 編碼的文件:
<?XML version="1.0" encoding="ISO-8859-1"?>
<test>ålt;/test>
現在,小心不要被 XML 屬性迷惑。XML 屬性返回 Unicode 字串。如果在創建 ISO-8859-1 編碼聲明之後,調用 DOMDocument 對象上的 XML 屬性,即可取回以下 Unicode 字串:
<?XML version="1.0"?>
<test>ålt;/test>
請注意這裡沒有 ISO-8859-1 編碼聲明了。這是正常的。這樣做的原因是使您可以轉而用此字串調用 LoadXML,它會起作用。如果它不這麼做,LoadXML 會失敗並返回出錯消息:“不支持從當前編碼切換到指定的編碼。”
結論
但願本文有助於解釋字符編碼的工作原理,特別是在 XML 和 MSXML DOM 中的工作原理。一旦您理解了字符集編碼,它是相當簡單的,而且 XML 是非常出色的,因為它在這方面未留有絲毫含糊的余地。盡管 MSXML DOM 有幾處怪異需要密切留意,但它仍不失為一個能讓您讀取和寫入任何 XML 編碼的強大工具。
有關詳細信息
Microsoft MSDN Online Library: XML DOM Reference(Microsoft MSDN 聯機庫:XML DOM 引用)
Character Encoding Model(字符編碼模型),作者:Ken Whistler 和 Mark Davis
IANA Character Sets(IANA 字符集)
http://www.ietf.org(英文)的 Internet Engineering Task Force (IETF) 提供了 RFC 列表
Microsoft MSDN Online Library: Compatibility Issues with Mixed Environments(Microsoft MSDN 聯機庫:與混合環境的兼容性問題)