CLVI. 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_parser_create() xml_parser_create_ns() 返回的 xml 资源引用了一个 XML 解析器实例,将被用在本扩展库提供的函数中。

预定义常量

以下常量由本扩展模块定义,因此只有在本扩展模块被编译到 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,PI)应该已经很熟悉了。<?php ?> 就是一个处理指令,其中 php 被称为“PI target”。除了以“XML”开头的 PI target 已被保留以外,对这些 PI 的处理将由应用程序来完成。
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 (
count ( $attribs )) {
                foreach (
$attribs as $k => $v ) {
            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>

目录
utf8_decode  --  将用 UTF-8 方式编码的 ISO-8859-1 字符串转换成单字节的 ISO-8859-1 字符串。
utf8_encode  -- 将 ISO-8859-1 编码的字符串转换为 UTF-8 编码
xml_error_string  -- 获取 XML 解析器的错误字符串
xml_get_current_byte_index  -- 获取 XML 解析器的当前字节索引
xml_get_current_column_number  --  获取 XML 解析器的当前列号
xml_get_current_line_number  -- 获取 XML 解析器的当前行号
xml_get_error_code  -- 获取 XML 解析器错误代码
xml_parse_into_struct  -- 将 XML 数据解析到数组中
xml_parse  -- 开始解析一个 XML 文档
xml_parser_create_ns  --  生成一个支持命名空间的 XML 解析器
xml_parser_create  -- 建立一个 XML 解析器
xml_parser_free  -- 释放指定的 XML 解析器
xml_parser_get_option  -- 从 XML 解析器获取选项设置信息
xml_parser_set_option  -- 为指定 XML 解析进行选项设置
xml_set_character_data_handler  -- 建立字符数据处理器
xml_set_default_handler  -- 建立默认处理器
xml_set_element_handler  -- 建立起始和终止元素处理器
xml_set_end_namespace_decl_handler  --  建立终止命名空间声明处理器
xml_set_external_entity_ref_handler  -- 建立外部实体指向处理器
xml_set_notation_decl_handler  -- 建立注释声明处理器
xml_set_object  -- 在对象中使用 XML 解析器
xml_set_processing_instruction_handler  --  建立处理指令(PI)处理器
xml_set_start_namespace_decl_handler  --  建立起始命名空间声明处理器
xml_set_unparsed_entity_decl_handler  --  建立未解析实体定义声明处理器