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:其中粗体函数表示下文会使用到
使用主要步骤
- 创建解析器:xml_parser_create()
- 处理标签及数据:xml_parse_into_struct() 或 xml_set_element_handler() 和 xml_set_character_data_handler()
- 释放资源: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文件。缺点也很明显,不易于理解和开发。