PHP操作XML方法之 XML Expat Parser

XML Expat Parser 简介

此PHP扩展实现了使用PHP支持JamesClark编写的expat。此工具包可解析(但不能验证)XML文档。它支持PHP所提供的3种字符编码:US-ASCII, ISO-8859-1和UTF-8。不支持 UTF-16。
此扩展可创建XML解析器并为不同的XML事件定义处理程序(handler)。每个XML解析器还存在少数可以调节的参数。

提供的函数

  • 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 — 建立未解析实体定义声明处理器

更多请参考官网:http://php.net/manual/zh/book.xml.php

PS:其中粗体函数表示下文会使用到

使用主要步骤

  1. 创建解析器:xml_parser_create()
  2. 处理标签及数据:xml_parse_into_struct() 或 xml_set_element_handler() 和 xml_set_character_data_handler()
  3. 释放资源:xml_parser_free() 释放资源

使用xml_parse_into_struct函数把XML导入数组中

读取XML并导入到数组中,打印看结果

$xml = <<<XML
<document><item><id>1</id><name>luyuqiang</name><age>90s</age></item><item><id>2</id><name>lixiaolai</name></item><item><id>3</id><name>ruanyifeng</name><age>70s</age></item></document>
XML;
$p = xml_parser_create();
xml_parse_into_struct($p, $xml, $vals, $index);
xml_parser_free($p);
echo "Index array\n";
print_r($index);
echo "\nVals array\n";
print_r($vals);

输出结果如下:

Index array
Array
(
    [DOCUMENT] => Array
        (
            [0] => 0
            [1] => 12
        )

    [ITEM] => Array
        (
            [0] => 1
            [1] => 4
            [2] => 5
            [3] => 7
            [4] => 8
            [5] => 11
        )

    [NAME] => Array
        (
            [0] => 2
            [1] => 6
            [2] => 9
        )

    [AGE] => Array
        (
            [0] => 3
            [1] => 10
        )

)

Vals array
Array
(
    [0] => Array
        (
            [tag] => DOCUMENT
            [type] => open
            [level] => 1
        )

    [1] => Array
        (
            [tag] => ITEM
            [type] => open
            [level] => 2
        )

    [2] => Array
        (
            [tag] => NAME
            [type] => complete
            [level] => 3
            [value] => luyuqiang
        )

    [3] => Array
        (
            [tag] => AGE
            [type] => complete
            [level] => 3
            [value] => 90s
        )

    [4] => Array
        (
            [tag] => ITEM
            [type] => close
            [level] => 2
        )

    [5] => Array
        (
            [tag] => ITEM
            [type] => open
            [level] => 2
        )

    [6] => Array
        (
            [tag] => NAME
            [type] => complete
            [level] => 3
            [value] => lixiaolai
        )

    [7] => Array
        (
            [tag] => ITEM
            [type] => close
            [level] => 2
        )

    [8] => Array
        (
            [tag] => ITEM
            [type] => open
            [level] => 2
        )

    [9] => Array
        (
            [tag] => NAME
            [type] => complete
            [level] => 3
            [value] => ruanyifeng
        )

    [10] => Array
        (
            [tag] => AGE
            [type] => complete
            [level] => 3
            [value] => 70s
        )

    [11] => Array
        (
            [tag] => ITEM
            [type] => close
            [level] => 2
        )

    [12] => Array
        (
            [tag] => DOCUMENT
            [type] => close
            [level] => 1
        )

)

官网是这么解释xml_parse_into_struct函数的:

该函数将XML文件解析到两个对应的数组中,index参数含有指向values数组中对应值的指针。最后两个数组参数可由指针传递给函数。

结合输出很好理解'index参数含有指向values数组中对应值的指针'这句话了。

其中值指针变量的第二维数组中包含tag,type,level,value等索引,tag是标签名,type是类型,level是维度,value是标签值,type包括:open(起始标签),close(闭合标签),complete(完整标签)

PS:所有标签都是大写,是因为大写转换

遍历获取数据

$peopleNum = count(($index['ITEM']))/2;
for($i=0;$i<$peopleNum;$i++){
    $person[$i]['name']   = !empty($vals[$index['NAME'][$i]]['value']) ? $vals[$index['NAME'][$i]]['value'] : '';
    $person[$i]['age']    = !empty($vals[$index['AGE'][$i]]['value']) ? $vals[$index['AGE'][$i]]['value'] : 'unknown';
}
print_r($person);
//结果如下:
Array
(
    [0] => Array
        (
            [name] => luyuqiang
            [age] => 90s
        )

    [1] => Array
        (
            [name] => lixiaolai
            [age] => 70s
        )

    [2] => Array
        (
            [name] => ruanyifeng
            [age] => unknown
        )

)

从结果可以看出:XML中的数据明明是lixiaolai的年龄未知,但是直接用for遍历,就会出现‘错位’的情况,结果ruanyifeng的年龄是未知。

因此如果不能保证XML中每组数据都是完整的,写代码时就要小心!可以用下列代码得到正确结果:

$itemNum = count($index['ITEM');
for($i = 0;$i < $itemNum;$i += 2){
    $startItem = $index['ITEM'][$i];
    $closeItem = $index['ITEM'][$i+1];
    $idx = $startItem + 1;
    $num = ($closeItem - 1) - $startItem;
    $tempArr = array_slice($vals,$idx,$num);
    //标签默认值
    $person[$i] = array('NAME'=>null,'AGE'=>'unknown');
    foreach($tempArr as $one){
        !empty($one['value'])  && $person[$i][$one['tag']] = $one['value'];
    }
}
print_r($person);

使用事件处理器读取XML

官网提供了7种事件处理函数,一些函数不易于理解且示例又少,所以本文只对 xml_set_element_handler()xml_set_character_data_handler()xml_set_default_handler() 主要的3个函数做了实践:

//收集数据变量
$currentData = null;
$data = array();
$index = -1;

//创建XML解析器
$parser = xml_parser_create();
//创建带有命名空间的解析器,用':'分隔
//$parser = xml_parser_create_ns('utf-8',':');

//获取大写转换 默认:1 大写
//$xmloption = xml_parser_get_option ($parser,XML_OPTION_CASE_FOLDING);
//设置不大写转换
//xml_parser_set_option($parser,XML_OPTION_CASE_FOLDING,0);
//此时再获取为'0'
//$xmloption = xml_parser_get_option ($parser,XML_OPTION_CASE_FOLDING);

//获取目标编码 默认:utf-8 可设置 ISO-8859-1、US-ASCII
//$xmloption = xml_parser_get_option ($parser,XML_OPTION_TARGET_ENCODING);
//设置为'US-ASCII'
//xml_parser_set_option($parser,XML_OPTION_TARGET_ENCODING,'US-ASCII');
//此时再获取为'US-ASCII'
//$xmloption = xml_parser_get_option ($parser,XML_OPTION_TARGET_ENCODING);

//是否略过由白空字符组成的值
//xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
//xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 0);

//指明在一个标记名前应略过几个字符。
//xml_parser_set_option($parser, XML_OPTION_SKIP_TAGSTART, 3);

//libXML下不支持此事件,因此不会调用注册的处理程序。
//xml_set_start_namespace_decl_handler($parser,function ($fp,$prefix,$uri){
//    //命名空间开始
//    echo $prefix.$uri.PHP_EOL;
//});
//xml_set_end_namespace_decl_handler($parser,function($fp,$prefix,$uri){
//    //命名空间结束
//    echo $prefix.$uri.PHP_EOL;
//});


xml_set_element_handler($parser,function($fp,$element,$attributes){
    //标签开始回调函数
    global $data,$index;
    switch($element){
        case 'ITEM' : $index++;$data[$index] = array('ID'=>null,'NAME'=>null,'AGE'=>'known',);break;
    }

    //处理标签属性
    if($attributes){
        foreach($attributes as $attrKey => $attrVal){
//            echo 'attributes: '.$attrKey.'='.$attrVal.PHP_EOL;
            switch($element){
                case 'ITEM' :
                    $data[$index][$attrKey] = $attrVal;
                    break;
            }
        }
    }
},function ($fp,$element) {
    //标签结束回调函数
    global $data, $index, $currentData;
    if(in_array($element,array('NAME','AGE'))){
        $data[$index][$element] = $currentData;
    }

});

xml_set_character_data_handler($parser,function ($fp,$data){
    //处理数据回调函数
    global $index, $currentData;
    $currentData = $data;
});




//没有定义开始,结束,处理数据的回调,默认回调函数,返回标签和值
//xml_set_default_handler($parser,function ($fp,$data){
//默认函数
//});

$file = 'baseXML.xml';

$fp = fopen($file,'r') or die('no file!');

while ($xmlData = fread($fp,4096)){
    xml_parse($parser, $xmlData, feof($fp));
}
//释放解析器
xml_parser_free($parser);

print_r($data);

baseXML文件如下:

<document><item id="1"><name>luyuqiang</name><age>90s</age></item><item id="2"><name>lixiaolai</name></item><item id="3"><name>ruanyifeng</name><age>70s</age></item></document>

结果跟上边用xml_set_character_data_handler函数获取的是一样,这种方式显然更可控些。

类中使用XML解析器

把创建、销毁解析器、事件函数放到类的构造方法中,把事件回调函数作为类方法的实现,其实很简单:只需要使用xml_set_object函数即可。

官网示例已经很清楚了,这里就不做赘述了

结论

通过学习发现:解析器这种方式处理XML的兼容老版本的PHP(php4),其优点在于不是一次把XML加载到内存,其可以动态读取XML文件。缺点也很明显,不易于理解和开发。

猜你喜欢

转载自www.cnblogs.com/luyuqiang/p/xml-expat-xml-parser.html
今日推荐