PHP如何处理XML数据并从中提取关键信息?使用PHP xml解析器

发布于 2023-05-31  690 次阅读


内容纲要

在PHP手册中,给出了如下几个常用的处理XML数据的函数:

1.xml_parser_create()

xml_parser_create()用于创建一个xml解析器,返回一个可供其他xml解析器函数调用的XMLParser实例。

xml_parser_create($encoding);
  • $encoding 输出内容的编码形式

XMLParser实例对输入的XML数据自动检测编码,因此只需要传入输出编码即可,默认的输出编码时UTF-8,受支持的编码有:

  • UTF-8
  • ISO-8859-1
  • us-ASCII

2.xml_get_error_code()

xml_get_error_code()用于获取当前解析的文档的错误码,可以通过xml_error_string()来获取错误码对应的错误内容。

xml_get_error_code($parser);
  • $parser 一个要获得错误码的xml解析器指针

3.xml_error_string()

xml_error_string()对xml_get_error_code()返回的错误码获得对应的错误描述字符串。

xml_error_string($code);
  • $code 由xml_get_error_code返回的错误码

4.xml_parse()

xml_parse即开始解析xml数据,当此函数被调用时,相应的处理器函数也会被无限次重复调用。

xml_parse($parser,$data,$final);
  • $parser 指向将要使用的xml解析器实例或者指针。
  • $data 将要被解析的xml数据
  • $final 如果为true,则当前被解析的数据是最后一段数据,如果要解析的xml数据只有一段,应该被设置为true

如果对xml数据解析成功,则返回true,否则返回false。

尝试:通过error函数捕获错误。

$my="<xml class='my'><name>小杜</name><age>19</age><errorhere><hobby><a>coding</a><b>sleep</b></hobby></xml>";
$parser=xml_parser_create();
$rs=xml_parse($parser,$my,true);
$rs || var_dump($error_code=xml_get_error_code($parser),xml_error_string($error_code));

结果:int(76) string(14) "Mismatched tag"

我在xml数据中加入的一个没有配对的标签,所以返回的错误码是76 错误的信息是Mismatched tag 没有匹配的标签

单独运行解析器并不能获取xml数据的内容,所以需要配合设置处理器的函数来获取xml数据。

5.xml_set_element_handler()

xml_set_elememt_handler()函数用户设定当xml_parse被运行时所使用的标签起始和终止处理器,处理器即被回调的处理函数。

xml_set_element_hander($parser,$start_handler,$end_handler);
  • $parser 指定要被设定处理器函数的xmlParser实例
  • $start_handler 字符串类型,指定处理标签开头要被调用的处理器函数
  • $end_handler 字符串,指定处理标签结尾被调用的处理器函数

其中的处理器函数必须在进行xml_parse()之前就已经被定义,他们的函数名和函数体由开发者自定义,但是参数是指定的,标签开头的处理器需要:

function start_handler($parser,$name,$attrs){
//code here
}

其中$parser为当前使用的xmlparser指针,$name为当前处理的标签名,$attrs为当前处理的标签的属性键值对。

对于$end_handler:

function func($parser,$name){

}

$parser为当前是使用的xmlparser指针,$name为当前结尾的标签名。

函数定义的参数会在xml_parse进行时传入,使用者只需要对他们进行处理即可。

6.xml_set_character_data_handler()

用于建立字符数据处理程序,当解析到字符数据时,会调用此函数定义的处理器。

xml_set_character_data_handler($parser,$handler);
  • $parser 当前使用的解析器指针。
  • $handler 处理字符串数据时调用的函数名。

字符串处理程序使用以格式:

function handler($parser,$data){
//code here
}

其中:

  • $parser 当前使用的解析器指针
  • $data 当前处理的字符串数据

7.xml_parser_free

xml_parser_free(XMLParser $parser): bool

用于释放一个xml Parser资源。

手册中给出:

除了在解析完成时调用 xml_parser_free() 之外,在 PHP 8.0.0 之前,如果 parser 资源引用自对象,且对象引用 parser 资源,还必须明确取消对 parser 的引用以避免内存泄漏。

所以在利用完parser之后还要对其进行unset。

8.处理简单的xml数据

XMLParser解析数据的流程是:首先获得xml_parse传入的XML数据,以标签对的形式解析,因为XML标签的结构是:

<tagname attrname="attrvalue">content</tagname>

因此XMLParser在对XML数据进行处理时使用的是分块处理的方法,对每个完整标签的分为标签开头、标签属性、标签内容、标签结尾,对应上面的tagname、attrname、content、/tagname。所以对于解析的XML数据,针对这几个区块定义处理器以更好的解析XML数据。

当xml_parse被运行时,每解析到一个xml标签对都会调用通过xml_set_element_handler()和xml_set_character_data_handler()定义的处理器,并把相关的数据传参进函数来使开发者自定义的函数来处理。因为标签的属性是在标签头中包含的,因此将处理标签开始的函数和处理标签属性的函数同时定义,处理完标签头后,处理标签内容字符串,然后处理标签结尾,因为四个函数的调用顺序:

  1. 处理标签开始的函数
  2. 处理标签属性的函数
  3. 处理标签内容的函数
  4. 处理标签结尾的函数

通过一个简单的例子来验证:

// 定义xml数据
$my="<xml class='my'><name>小杜</name><age>19</age><hobby><a>coding</a><b>sleep</b></hobby></xml>";
//创建xml解析器
$parser=xml_parser_create('UTF-8');
// 声明开始标签处理函数
function ele_handler_left($parser,$data){
    echo "这是标签开头:".$data;
}
// 定义标签数据处理函数
function data_handler($parser,$data){
    echo "&nbsp;;&nbsp;&nbsp;这是标签数据:{$data}";
}
// 定义标签结尾处理函数
function ele_handler_end($parser,$data){
    echo "&nbsp;&nbsp;&nbsp;这是标签结尾:{$data}<br>";

}
// 设定标签解析器
xml_set_element_handler($parser,'ele_handler_left',"ele_handler_end");
// 设定数据解析器
xml_set_character_data_handler($parser,"data_handler");
// 开始解析HTML数据
xml_parse($parser,$my);
echo xml_get_error_code($parser);

运行结果:

由输出顺序可以证明xml解析器的就是按照上面提到的执行顺序。

如果要将xml数据解析进数组,并且xml中含有类似上面的多层次的标签,需要避免数据储存的错误应该怎么做?

尝试了很多方法,比如将每个标签都用全局变量储存起来,然后最后拼接成一个数组,或者是把用三个数组分别存储标签名、标签值、最后的结果数组,以及把带有字符串的标签和没有字符串的标签分开储存,再通过判断来合并数组,但是这样做都很麻烦,而且多少都存在点问题。

最后还是发现了一种简单又不容易出错的方法:根据xml解析器的特性和xml文档的格式入手,因为xml解析器分三步执行:首先处理开头标签,然后处理标签内的文本,最后处理标签结尾,如果是多层嵌套的xml标签,就没有标签内的字符串,并且作为父级标签的标签的结尾标签会在子标签的方法都执行完成后再执行,因此可以只通过一个全局的数组就能实现对xml文档的完美解析。

大概思路是,首先定义一个$arr数组来储存数据,这里储存的数据就是最后得到的数据,然后在解析开始标签时以标签名来给数组插入元素,并且先插入一个键为attrs的数组储存标签的属性,在处理标签内字符串的函数中,再给当前元素标签名命名的数组元素新增一个键值对 content=>标签内容。在结尾标签处理中,因为第二步将标签内容赋值到了$arr全局数组中,所以先定义一个变量储存标签值,再把全局数组中的现在的标签删除,并且放到删除后的全局数组最后一个标签名命名的元素下形成新的键值对,这个键值对就是当前解析标签的标签名和标签内容。最后得到的结果是一个根据xml结构产生的多维数组,每个元素对应的值都是数组,如果存在属性则有键值对attrs=>属性值数组,并且都有content=>标签值。

代码实现:

$xml="<xml width='200' height='500'><name style='color:red'>小杜</name><age>19</age><address><province>安徽省</province><city>合肥市</city><school>巢湖学院</school></address><class>互金一</class>";
//创建xml解析器
$parser=xml_parser_create();
//结果存放的数组
$arr=[];
//开始标签函数
function left_tags($parser,$name,$attrs){
    global $arr;
    // 先将标签属性放入数组中
    empty($attrs) ? $arr[$name]=[] : $arr[$name]=['attrs'=>$attrs];
}
// 字符串标签函数
function strData($parser,$data){
    global $arr;
    $arr[end(array_keys($arr))]['content']=$data;
}
// 结尾标签函数、
function right_tags($parser,$name){
    global $arr;
    $value=$arr[$name];
    unset($arr[$name]);
    $arr[end(array_keys($arr))][$name]=$value;
}
//设定节点解析器
xml_set_element_handler($parser,"left_tags",'right_tags');
//设定文本解析器
xml_set_character_data_handler($parser,'strData');
//开始解析xml
xml_parse($parser,$xml);
var_dump($arr);
//释放
xml_parser_free($parser);
unset($parser);

结果:



array(1) {
  ["XML"]=>
  array(5) {
    ["attrs"]=>
    array(2) {
      ["WIDTH"]=>
      string(3) "200"
      ["HEIGHT"]=>
      string(3) "500"
    }
    ["NAME"]=>
    array(2) {
      ["attrs"]=>
      array(1) {
        ["STYLE"]=>
        string(9) "color:red"
      }
      ["content"]=>
      string(6) "小杜"
    }
    ["AGE"]=>
    array(1) {
      ["content"]=>
      string(2) "19"
    }
    ["ADDRESS"]=>
    array(3) {
      ["PROVINCE"]=>
      array(1) {
        ["content"]=>
        string(9) "安徽省"
      }
      ["CITY"]=>
      array(1) {
        ["content"]=>
        string(9) "合肥市"
      }
      ["SCHOOL"]=>
      array(1) {
        ["content"]=>
        string(12) "巢湖学院"
      }
    }
    ["CLASS"]=>
    array(1) {
      ["content"]=>
      string(9) "互金一"
    }
  }
}
届ける言葉を今は育ててる
最后更新于 2023-06-04