在存储、传输或处理文本时,有必要了解字符编码方式。这种规则也适用于 XML,因为 XML 是基于文本的。
尽管可以使用多种方法对 XML 进行编码,却并没有关于使用哪种编码方式以及如何进行识别的准则。因此,本技术说明阐述了将字符编码方式应用到 XML 的最佳应用。
选择 XML 实体的字符编码方式
在选择所使用的字符编码方式时,必须首先了解可以选择哪些方式。在涉及到标准规范和执行环境的那些特定 XML 应用程序的情况下,可能将编码方式限定在某个范围内。实际上,在一种特定情况下,可能只有一种选择。
术语“规定编码方式”是指在这种情况下必须使用的编码方式。例如,编程环境的字符串数据类型可能必须包含在预定的编码方式中,如 Java 字符中的 UTF-16 或 SQL 字符数据类型中的数据库字符集。唯一的编码方式经常以不同的方法进行规定。表 1 显示了这类情况的一些其他示例。
表 1. 规定唯一编码方式的示例
注意:Oracle XDK for C/C++ 提供一种特殊的模式,允许以任意的单字节字符编码方式来创建 DOM 树,而 API 以指定编码方式工作。此特性用于优化目的,被看作是例外情况,因为其目的是用于那些已了解数据包含指定字符集中字符的情况。强烈建议您始终使用 xmlinitenc 初始化函数或将 data_encoding 属性指定给 XML 上下文。
使用规定编码方式是一种最佳应用,因为您不必关注文档内或文档本身的编码信息 ― 用户始终了解编码方式。因此减少了出错机率并可以提高效率。
但是即使规定了编码方式,XML 处理器也可能不“了解”它 ― 在这种情况下,应用程序必须确保使用规定的编码方式。例如,如果 Java 应用程序有一个 DOM 树必须串行化为 UTF-8 [RFC-3629] 格式的输出流,则在将输出从 Writer 转换到 OutputStream 时,通过明确指定 UTF-8 可以确保该过程的执行。以下伪代码是在 Java Servlet 中指定输出编码方式的一个示例:
/* response is an http servlet response object */
response.setCharacterEncoding("UTF-8"); // set the output encoding to UTF-8
PrintWriter
w = response.getWriter(); // get the output stream mandated to UTF-8
:
/* doc is an instance of an XML */
doc.print(w);
// the document printed in the specified encoding
同样,如果您的输入必须是 UTF-8 格式,则您的应用程序应该编写为只接受 UTF-8 格式的输入。例如,在 Java 语言中,您可能需要使用构造符创建 InputSource 对象,该构造符使用一个参数来指定输入编码方式。此外,您可以从输入流中创建 InputStreamReader,指定 UTF-8 作为输入编码方式。以下伪代码说明如何在 Java 语言中指定输入编码方式。
InputSource
is = new InputSource();
// create an input source
is.setByteStream(request.getInputStream()); // set the input stream mandated to UTF-8
is.setEncoding("UTF-8");
// set the mandate encoding to the input source
parser.parse(is);
// the parser will parse in the specified encoding
常用编程环境的大部分字符串数据类型都将字符编码方式限定在特定的范围中。即使提供多种选择,通常也会有某些约束。例如,标准库和 Oracle 库必须支持用于 C/C++ 中 char 类型的字符编码方式。使用 FORCE_INCODING 标志或 input_encoding 属性来指定规定的或外部指定的输入编码方式。以下伪代码演示了如何为 C 的 XDK 指定规定编码方式。
// parse an input stream in UTF-8 with DOM
XmlLoadDom(ctx, &err, "stream", in, "input_encoding", "UTF-8", NULL);
// parse an input stream in UTF-8 with SAX
XmlLoadSax(ctx, &err, "stream", in, "input_encoding", "UTF-8", NULL);
// print the document in UTF-8
XmlSaveDom(ctx, &err, doc, "stream", out, "output_encoding", "UTF-8", NULL);
选择规定字符编码方式
如果应用程序不需要支持多种编码方式,它可以为其本身规定唯一的编码方式。如果规定了一种编码方式,则这种方式应该是 UTF-8 或 UTF-16 [RFC-2781] ― 否则交互操作性将受到严重影响,因为使用文档的 XML 处理器可以不支持其他编码方式。如果需要与 US-ASCII [RFC-20] 兼容,或者需要以串行化格式进行传输或存储,则建议使用 UTF-8。在其他情况下,可能适于使用 UTF-16。
支持多种编码方式
需要支持多种编码方式的应用程序能够支持 XML 处理器所支持的任何编码方式。所有的 XML 处理器都支持 UTF-8 和 UTF-16。它们通常也支持一些常用的本地编码方式。
虽然 Oracle XML 处理器支持所有常用编码方式以及许多其他编码方式,但建议仅在必要时允许多种编码方式。应用程序不应该包含那些不使用 UTF-8 或 UTF-16 编码方式的 XML 文档,除非知道用户支持该编码方式并且其内容可以使用编码方式表达。例如,如果数据库字符集不是 Unicode,则不赞成在数据库中包含 XML 文档并在数据库字符集中利用它为未知用户提供服务。
为接收各种编码方式的输入实体,应该由 XML 处理器以字节流的形式不加更改地读取输入流。确保将外部提供的编码信息(例如内容类型的 HTTP 标题中的 charset 参数)传递到 XML 处理器,以便在 XML 处理器分析输入时强制为指定的编码方式。这种情况就象指定的编码方式就是规定编码方式一样。
为了以任意编码方式生成输出,需要确保实体带有字符编码信息,其方法是通过外部标记实现,如 HTTP 标题的 charset 参数以及 Oracle Files 或 Oracle XML DB 等信息库中的字符集属性,或者是通过嵌入标记实现(即编码声明)。
外部标记优先于内部标记,因为外部标记更可靠并更易于处理;尽可能不使用内部标记是明智的选择。实际上,先前曾讨论过,因为通常不了解编码方式,所以一般不需要内部标记。
经常由于所需的字符集转换而使声明的编码方式与实际编码方式不一致。例如,如果您将带有编码声明信息的文档插入 CLOB 类型的数据库列中,或通过 Java 字符流来读取它时,其声明不会神奇地转变为实际的值。通过使用 NLS_LANG 设置和 Java 字符数据类型等更高级协议来维护正确的编码方式时,很容易避免这种情况。(Oracle 的 XMLType 数据类型可满足处理多种字符编码方式的预期情况。)
外部与内部编码信息的对比
字符编码信息的来源可以分为两类:外部或内部。本章讨论二者之间的重要差别。
外部编码信息
图 1 描述了分析器如何根据一种外部编码信息源 ― HTTP 的内容类型标题 ― 确定字符编码方式。注意,这里没有使用内部编码信息。
当输入是以 HTTP 或者任何可以从外部识别其编码方式的其他形式传入时,应用程序应该完成以下工作中的一项:
将 charset 参数的值传递给分析器
根据 charset 参数将输入流转换为 Unicode
指示分析器来分析 URI
使用复合编码方式需要的数据类型
内部编码信息
图 2 显示了分析器如何通过自动检测来确定字符编码方式。注意,文档必须具有正确的字节顺序标记 (BOM) 和/或编码声明。
在没有提供外部编码信息的情况下,可以使用自动检测。例如:
XML 作为文件存储在文件系统中。
发送方没有提供外部编码信息,并且没有规定编码方式。
内部字符编码信息来源的详细信息
我们已经讨论了将会使用何种编码方式以及如何在运行时指定、传输和确定这种方式,就让我们来探讨字节顺序标记和编码声明,这些是自动检测字符编码方式的工具。
字节顺序标记 (BOM)
BOM(Unicode 字符 U+FEFF、ZERO WIDTH NO-BREAK SPACE)可以出现在 XML 实体的开始部分。在 XML 中,BOM 不仅用于显示输入文本流的字节顺序,而且可以帮助检测字符编码方式。XML 处理器通常检查输入流的前几个字节,以判断编码方式是否为 UTF-16 或 ASCII,因此 XML 处理器能够读取可能在 XML 标题中提供的编码声明。UTF-16 格式的实体需要具有供自动检测使用的 BOM,因为 UTF-16 编码方式具有两种形式:UTF-16BE(big endian:fe ff)和 UTF-16LE(little endian:ff fe)。虽然没有字节顺序问题,但 UTF-8 格式的实体可以具有 BOM (ef bb bf)。BOM 不是文档的一部分,因此不能从用户代码中读取。通常 XML 处理器在必要时添加或删除 BOM。
编码声明
编码声明是 XML 标题的参数之一(特别称为 XML 或文本声明)。例如
引入编码声明是为了在缺少外部字符编码信息的情况下进行分析时,为那些不使用 UTF-8 或 UTF-16 编码方式的实体提供字符编码信息。一种常见的误解认为,如果 XML 实体正在使用的不是 UTF-8 或 UTF-16 的编码方式,则它必须具有编码声明。实际上,如果知道编码方式,则编码声明没有用处,这经常是问题之所在。XML 分析器提供了一种指定每个输入实体字符编码方式的方法。如果分析器知道实体的编码方式,则声明中的值并不重要。如果以传递给分析器的参数形式从外部提供了编码信息,则通常忽略该值。在其他情况