PHP Manual
后退 |  | 前进

# CXXXVI. XML 语法解析函数库

# 简介

XML（eXtensible Markup Language，可扩展标记语言）是一种在 WEB 上进行文档交换的数据格式。该语言是由 W3C（World Wide Web Concortium，世界万维网组织）定义的一种标准。您可以访问 http://www.w3.org/XML/ 以获取关于 XML 及其相关技术的更多信息。

本扩展模块可为 James Clark 的 expat 提供支持。该工具包帮助您解析 XML 文档（而非 XML 文档的有效化）。它支持三种源代码的编码方式，这三种编码方式也被 PHP 本身所支持，它们分别是：US-ASCII、ISO-8859-1 和 UTF-8。 该系统尚不支持 UTF-16。

该扩展模块使您能够建立 XML 语法解析器，并对不同的 XML 事件定义对应的处理器。每个 XML 语法解析器都有若干个您可以根据需要调整的参数。

# 需求

该扩展模块默认使用 expat compat layer。它也可以使用 expat，您可以访问 http://www.jclark.com/xml/expat.html 来获取。expat 自带的 Makefile 文件不会生成默认的扩展库，您可以使用以下的生成规则来实现：

    libexpat.a: $(OBJS)
        ar -rc $@ $(OBJS)
        ranlib $@

请访问 http://sourceforge.net/projects/expat/ 以获取 expat 源文件的 RPM 包。

# 安装

这些函数默认为有效的，它们使用了捆绑的 expat 库。您可以通过参数 --disable-xml 来屏蔽 XML 的支持。如果您将 PHP 编译为 Apache 1.3.9 或更高版本的一个模块， PHP 将自动使用 Apache 捆绑的 expat 库。如果您不希望使用该捆绑的 expat 库，请在运行 PHP 的 configure 配置脚本时使用参数 --with-expat-dir=DIR，其中 DIR 应该指向 expat 安装的根目录。

PHP 的 Windows 版本已经内置该扩展模块的支持。无需加载任何附加扩展库即可使用这些函数。

# 运行时配置

本扩展模块在 `php.ini` 中未定义任何设置指令。

# 资源类型

## xml

资源 xml 和由函数 xml_parser_create() 及 xml_parser_create_ns() 返回的资源类似，它们指向一个可被本扩展中的函数使用的 xml 解析对象。 extension.

# 预定义常量

以下常量由本扩展模块定义，因此只有在本扩展模块被编译到 PHP 中，或者在运行时被动态加载后才有效。

`XML_ERROR_NONE` (integer)

`XML_ERROR_NO_MEMORY` (integer)

`XML_ERROR_SYNTAX` (integer)

`XML_ERROR_NO_ELEMENTS` (integer)

`XML_ERROR_INVALID_TOKEN` (integer)

`XML_ERROR_UNCLOSED_TOKEN` (integer)

`XML_ERROR_PARTIAL_CHAR` (integer)

`XML_ERROR_TAG_MISMATCH` (integer)

`XML_ERROR_DUPLICATE_ATTRIBUTE` (integer)

`XML_ERROR_JUNK_AFTER_DOC_ELEMENT` (integer)

`XML_ERROR_PARAM_ENTITY_REF` (integer)

`XML_ERROR_UNDEFINED_ENTITY` (integer)

`XML_ERROR_RECURSIVE_ENTITY_REF` (integer)

`XML_ERROR_ASYNC_ENTITY` (integer)

`XML_ERROR_BAD_CHAR_REF` (integer)

`XML_ERROR_BINARY_ENTITY_REF` (integer)

`XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF` (integer)

`XML_ERROR_MISPLACED_XML_PI` (integer)

`XML_ERROR_UNKNOWN_ENCODING` (integer)

`XML_ERROR_INCORRECT_ENCODING` (integer)

`XML_ERROR_UNCLOSED_CDATA_SECTION` (integer)

`XML_ERROR_EXTERNAL_ENTITY_HANDLING` (integer)

`XML_OPTION_CASE_FOLDING` (integer)

`XML_OPTION_TARGET_ENCODING` (integer)

`XML_OPTION_SKIP_TAGSTART` (integer)

`XML_OPTION_SKIP_WHITE` (integer)

# 事件处理器

XML 事件处理器定义如下：

表格 1. 已支持的 XML 事件处理器

用来设置处理器的 PHP 函数 | 事件描述
--- | ---
xml_set_element_handler() | 元素事件（Element events）将在 XML 解析器遇到标记符的起始符或者终止符时发生。另外，对于起始符和终止符也有独立的处理器。
xml_set_character_data_handler() | 粗略的说，字符数据（Character data）是指 XML 文档中所有标记符以外的内容，包括标记符之间的空格。需要注意的是 XML 语法解析器不会加上或者去掉任何空格。空格的取舍将由应用程序（也就是您自己）来决定。
xml_set_processing_instruction_handler() | PHP 程序员对“处理指令”（Processing Instructions，PIs）应该已经很熟悉了。<?php ?> 就是一个处理指令，其中 php 被称为“PI target”。除了以“XML”开头的 PI target 已被保留以外，对这些 PIs 的处理将由应用程序来完成。
xml_set_default_handler() | 所有无法被其它处理器处理的事件将由默认处理器来处理。这些事件包括诸如 XML 和文档类型声明等内容。
xml_set_unparsed_entity_decl_handler() | 该处理器将在遇到无法解析的实体名称（NDATA）声明时被调用。
xml_set_notation_decl_handler() | 该处理器将在声明一个注释时被调用。
xml_set_external_entity_ref_handler() | 当 XML 解析器遇到指向外部解析的一般实体名时，该处理器将被调用。该指向的目标可以是一个文件，也可以是 URL。请参阅“外部实体名范例”。

# 大小写折叠（Case Folding）

元素处理函数可能会导致元素名称“大小写折叠”（case-folded）。“大小写折叠”被 XML 标准定义为“一个应用于一系列字符的过程，在该过程中，这些字符中的所有的非大写字符将被替换成它们对应大写等价字符”。换句话说，对于 XML，“大小写折叠”就是指将字符串转换成大写字符。

所有被传递给处理器函数的元素名称将默认的发生“大小写折叠”。该过程可以分别被 xml_parser_get_option() 和 xml_parser_set_option() 函数查询和控制。

# 错误代码

以下常量被定义为 XML 的错误代码，将由 xml_parse() 返回：

XML_ERROR_NONE
XML_ERROR_NO_MEMORY
XML_ERROR_SYNTAX
XML_ERROR_NO_ELEMENTS
XML_ERROR_INVALID_TOKEN
XML_ERROR_UNCLOSED_TOKEN
XML_ERROR_PARTIAL_CHAR
XML_ERROR_TAG_MISMATCH
XML_ERROR_DUPLICATE_ATTRIBUTE
XML_ERROR_JUNK_AFTER_DOC_ELEMENT
XML_ERROR_PARAM_ENTITY_REF
XML_ERROR_UNDEFINED_ENTITY
XML_ERROR_RECURSIVE_ENTITY_REF
XML_ERROR_ASYNC_ENTITY
XML_ERROR_BAD_CHAR_REF
XML_ERROR_BINARY_ENTITY_REF
XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF
XML_ERROR_MISPLACED_XML_PI
XML_ERROR_UNKNOWN_ENCODING
XML_ERROR_INCORRECT_ENCODING
XML_ERROR_UNCLOSED_CDATA_SECTION
XML_ERROR_EXTERNAL_ENTITY_HANDLING

# 字符编码

PHP 的 XML 扩展库支持不同字符编码（character encoding）的 Unicode 字符集。字符编码有两种形式，它们分别是“源编码”（source encoding）和“目标编码”（target encoding）。PHP 对文档内部表示的编码方式是 UTF-8。

源编码将在 XML 文档被解析后完成。源编码可在建立一个 XML 解析器时指明（该编码方式在 XML 解析器的生命周期中不能被再次改变）。支持的编码方式包括 ISO-8859-1，US-ASCII 和 UTF-8。前两种为单字节编码，即每个字符被一个单一的字节表示。 UTF-8 支持 1 至 4 个字节的多 bit（最多 12）字符编码。PHP 默认使用 ISO-8859-1 作为源编码方式。

目标编码将在 PHP 向 XML 处理器函数传送数据时被完成。当 XML 解析器被建立后，目标编码将被设置成与源编码相同的编码方式，但该方式可在任何时候被更改。目标编码将影响字符数据、标记符名称以及处理指令目标（PI target）。

如果 XML 解析器遇到其源编码方式表示能力之外的字符，它将返回一个错误。

当 PHP 在被解析的 XML 文档中遇到当前目标编码无法表示的字符时，这些字符将被“降级”。简单的说，这些字符将被问号替换。

# 例

以下是 PHP 脚本解析 XML 文档的一些范例。

## XML 元素结构范例

第一个范例用缩进格式显示一个文档中起始元素的结构。

例子 1. 显示 XML 元素结构
`<?php $file = "data.xml"; $depth = array(); function startElement($parser, $name, $attrs) { global $depth; for ($i = 0; $i < $depth[$parser]; $i++) { echo " "; } echo "$name\n"; $depth[$parser]++; } function endElement($parser, $name) { global $depth; $depth[$parser]--; } $xml_parser = xml_parser_create(); xml_set_element_handler($xml_parser, "startElement", "endElement"); if (!($fp = fopen($file, "r"))) { die("could not open XML input"); } while ($data = fread($fp, 4096)) { if (!xml_parse($xml_parser, $data, feof($fp))) { die(sprintf("XML error: %s at line %d", xml_error_string(xml_get_error_code($xml_parser)), xml_get_current_line_number($xml_parser))); } } xml_parser_free($xml_parser); ?>`

## XML 标记符映射范例

例子 2. 将 XML 映射为 HTML 以下范例将 XML 文档中的标记符直接映射成 HTML 标记符。在“映射数组”中不存在的元素将被忽略。当然，该范例将只对一个特定的 XML 文档有效。
`<?php $file = "data.xml"; $map_array = array( "BOLD" => "B", "EMPHASIS" => "I", "LITERAL" => "TT" ); function startElement($parser, $name, $attrs) { global $map_array; if (isset($map_array[$name])) { echo "<$map_array[$name]>"; } } function endElement($parser, $name) { global $map_array; if (isset($map_array[$name])) { echo "</$map_array[$name]>"; } } function characterData($parser, $data) { echo $data; } $xml_parser = xml_parser_create(); // 使用大小写折叠来保证我们能在元素数组中找到这些元素名称 xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, true); xml_set_element_handler($xml_parser, "startElement", "endElement"); xml_set_character_data_handler($xml_parser, "characterData"); if (!($fp = fopen($file, "r"))) { die("could not open XML input"); } while ($data = fread($fp, 4096)) { if (!xml_parse($xml_parser, $data, feof($fp))) { die(sprintf("XML error: %s at line %d", xml_error_string(xml_get_error_code($xml_parser)), xml_get_current_line_number($xml_parser))); } } xml_parser_free($xml_parser); ?>`

## XML 外部实体范例

该范例能够高亮显示 XML 源代码。它将说明如何外部实体指向处理器来包含和解析其它文档，如何处理 PIs，以及一种确定包含有 PIs 的代码的可信度。

能被该范例使用的的 XML 文档（`xmltest.xml` 和 `xmltest2.xml`）被列在该范例之后。

例子 3. 外部实体范例
`<?php $file = "xmltest.xml"; function trustedFile($file) { // only trust local files owned by ourselves if (!eregi("^([a-z]+)://", $file) && fileowner($file) == getmyuid()) { return true; } return false; } function startElement($parser, $name, $attribs) { echo "&lt;<font color=\"#0000cc\">$name</font>"; if (sizeof($attribs)) { while (list($k, $v) = each($attribs)) { echo " <font color=\"#009900\">$k</font>=\"<font color=\"#990000\">$v</font>\""; } } echo "&gt;"; } function endElement($parser, $name) { echo "&lt;/<font color=\"#0000cc\">$name</font>&gt;"; } function characterData($parser, $data) { echo "<b>$data</b>"; } function PIHandler($parser, $target, $data) { switch (strtolower($target)) { case "php": global $parser_file; // If the parsed document is "trusted", we say it is safe // to execute PHP code inside it. If not, display the code // instead. if (trustedFile($parser_file[$parser])) { eval($data); } else { printf("Untrusted PHP code: <i>%s</i>", htmlspecialchars($data)); } break; } } function defaultHandler($parser, $data) { if (substr($data, 0, 1) == "&" && substr($data, -1, 1) == ";") { printf('<font color="#aa00aa">%s</font>', htmlspecialchars($data)); } else { printf('<font size="-1">%s</font>', htmlspecialchars($data)); } } function externalEntityRefHandler($parser, $openEntityNames, $base, $systemId, $publicId) { if ($systemId) { if (!list($parser, $fp) = new_xml_parser($systemId)) { printf("Could not open entity %s at %s\n", $openEntityNames, $systemId); return false; } while ($data = fread($fp, 4096)) { if (!xml_parse($parser, $data, feof($fp))) { printf("XML error: %s at line %d while parsing entity %s\n", xml_error_string(xml_get_error_code($parser)), xml_get_current_line_number($parser), $openEntityNames); xml_parser_free($parser); return false; } } xml_parser_free($parser); return true; } return false; } function new_xml_parser($file) { global $parser_file; $xml_parser = xml_parser_create(); xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, 1); xml_set_element_handler($xml_parser, "startElement", "endElement"); xml_set_character_data_handler($xml_parser, "characterData"); xml_set_processing_instruction_handler($xml_parser, "PIHandler"); xml_set_default_handler($xml_parser, "defaultHandler"); xml_set_external_entity_ref_handler($xml_parser, "externalEntityRefHandler"); if (!($fp = @fopen($file, "r"))) { return false; } if (!is_array($parser_file)) { settype($parser_file, "array"); } $parser_file[$xml_parser] = $file; return array($xml_parser, $fp); } if (!(list($xml_parser, $fp) = new_xml_parser($file))) { die("could not open XML input"); } echo "<pre>"; while ($data = fread($fp, 4096)) { if (!xml_parse($xml_parser, $data, feof($fp))) { die(sprintf("XML error: %s at line %d\n", xml_error_string(xml_get_error_code($xml_parser)), xml_get_current_line_number($xml_parser))); } } echo "</pre>"; echo "parse complete\n"; xml_parser_free($xml_parser); ?>`

例子 4. xmltest.xml
    <?xml version='1.0'?>
    <!DOCTYPE chapter SYSTEM "/just/a/test.dtd" [
    <!ENTITY plainEntity "FOO entity">
    <!ENTITY systemEntity SYSTEM "xmltest2.xml">
    ]>
    <chapter>
     <TITLE>Title &plainEntity;</TITLE>
     <para>
      <informaltable>
       <tgroup cols="3">
        <tbody>
         <row><entry>a1</entry><entry morerows="1">b1</entry><entry>c1</entry></row>
         <row><entry>a2</entry><entry>c2</entry></row>
         <row><entry>a3</entry><entry>b3</entry><entry>c3</entry></row>
        </tbody>
       </tgroup>
      </informaltable>
     </para>
     &systemEntity;
     <section id="about">
      <title>About this Document</title>
      <para>
       <!-- this is a comment -->
       <?php echo 'Hi!  This is PHP version ' . phpversion(); ?>
      </para>
     </section>
    </chapter>

以下文档将被 `xmltest.xml` 文件调用：

例子 5. xmltest2.xml
    <?xml version="1.0"?>
    <!DOCTYPE foo [
    <!ENTITY testEnt "test entity">
    ]>
    <foo>
       <element attrib="value"/>
       &testEnt;
       <?php echo "This is some more PHP code being executed."; ?>
    </foo>

后退 | 起点 | 前进
xdiff_string_patch | 上一级 | utf8_decode