XML 数据的编码方式

  •   2009-07-27/22:10
  • Chris Lovett
    Microsoft Corporation
    2000 年 3 月

    目录

    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 &#338; &aring;
    229 &aring; &Acirc;
    231 &ccedil; &Aacute;
    232 è &Euml;
    233 é &Egrave;


    比方说,如果您的祖母从 http://www.barnesandnoble.com/(英文)订购了一本新书,她不会想到她的 Macintosh 计算机存储字符的方式,并不同于运行 www.barnesandnoble.com(英文)的新 Windows 2000 Web 服务器。在往 Internet 订购单的发货栏中输入瑞典家中的地址时,她相信 Internet 会正确地传递字符 &aring;(在其 Macintosh 上的字节值是 140),并没想到接收和处理她发送消息的计算机会将字节值 140 转换为字母 &#338;。

    Unicode
    Unicode Consortium(统一码协会)确信(用双字节而不是单字节表示每个字符)定义一个通用的代码页是个好主意,该代码页适用于全世界所有的语言,从而不同代码页之间的映射问题将不复存在。

    既然如此,如果 Unicode 解决了跨平台的字符编码问题,那为何它却未成为唯一的标准呢?第一个问题是,转换到 Unicode 有时意味着使所有的文件大小加倍 — 这样做在网络世界中是不可想象的。因此有人仍乐于使用老的、单字节的字符集,如 ISO-8859-1 到 ISO-8859-15、Shift-JIS、EUC-KR 等等。

    第二个问题是,仍存在许多并非基于 Unicode 的系统,这就意味着在网络上,某些组成 Unicode 字符的字节值可能会给那些更旧的系统造成严重问题。因此定义了“Unicode 转换格式 (UTF)”;它们运用位转换技术对 Unicode 字符进行编码,使其成为在老系统上“透明的”(或可安全通过)的字节值。

    此类字符编码中最普及的是 UTF-8。UTF-8 采用 Unicode 标准的前 127 个字符(它们恰好是基本的拉丁文字符:A-Z、a-z 和 0-9,以及几个标点字符),并直接将其映射到单字节值。然后采用位转换技术,用字节的高位来编码 Unicode 字符的其余部分。这样做的结果是,小瑞典字符 &aring; (0xE5) 变成了下列双字节乱码:&Atilde;&yen; (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 表示 &aring;。现在该页对任何系统来说,都是完全清楚的,数据不会被曲解。遗憾的是,由于此元标记是可选的,所以它给错误留下了空子。

    字符实体
    不是所有的系统支持所有的注册字符集。例如,我并不认为很多平台实际上可支持称为 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>&aring;lt;/test>
    UTF-8 Content-Type: text/xml; <test>&Atilde;&yen;</test>
    ISO-8859-1 Content-Type: text/xml; <?xml version="1.0" encoding="ISO-8859-1"?>
    <test>&aring;lt;/test>
    UTF-8(用字符实体) Content-Type: text/xml; <test>&#229;</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 = "&aring;;
    xmldoc.appendChild(e);
    xmldoc.save("foo.xml");

    下列 UTF-8 编码文件的结果是:

    <test>&Atilde;&yen;</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>&aring;lt;/test>

    现在,小心不要被 XML 属性迷惑。XML 属性返回 Unicode 字串。如果在创建 ISO-8859-1 编码声明之后,调用 DOMDocument 对象上的 XML 属性,即可取回以下 Unicode 字串:

    <?xml version="1.0"?>
    <test>&aring;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 联机库:与混合环境的兼容性问题)



    评论 {{userinfo.comments}}

    {{money}}

    {{question.question}}

    A {{question.A}}
    B {{question.B}}
    C {{question.C}}
    D {{question.D}}
    提交

    驱动号 更多