在处理复杂的 XML 数据时,PHP 内置的 XML 解析器(基于 Expat 库)是一个非常强大的工具。通过 xml_parser_create() 和配套的处理函数,我们可以对 XML 的结构进行灵活解析。尤其是当你面对嵌套层级深、元素繁多的 XML 文档时,自定义元素处理器(element handler)将显著提升解析效率与可读性。
本文将详细讲解如何使用 xml_set_element_handler() 来自定义 XML 元素处理器,并结合示例代码解析一个复杂的 XML 数据结构。
在使用 xml_parse() 进行 XML 流解析时,我们可以通过 xml_set_element_handler() 为解析器注册两个回调函数:
startElementHandler:开始标签的回调函数
endElementHandler:结束标签的回调函数
这两个函数的签名通常如下:
function startElement($parser, $name, $attrs)
function endElement($parser, $name)
其中 $name 是当前节点的名称,$attrs 是关联数组,表示该节点的属性。
假设我们从一个 API 获取如下格式的 XML 数据:
<catalog>
<book id="001">
<title>PHP 开发实战</title>
<author>张三</author>
<price currency="CNY">89.00</price>
</book>
<book id="002">
<title>深入理解 XML</title>
<author>李四</author>
<price currency="CNY">75.50</price>
</book>
</catalog>
我们将编写一个解析器,提取每本书的标题、作者和价格信息,并输出。
<?php
$xmlData = file_get_contents('https://m66.net/api/books.xml');
// 用于存储解析结果
$books = [];
$currentBook = [];
$currentTag = "";
// 创建 XML 解析器
$parser = xml_parser_create("UTF-8");
// 设置开始和结束标签的处理函数
xml_set_element_handler($parser, "startElement", "endElement");
// 设置字符数据处理函数
xml_set_character_data_handler($parser, "characterData");
// 设置解析器参数
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false); // 保持标签大小写一致
// 定义处理函数
function startElement($parser, $name, $attrs) {
global $currentBook, $currentTag;
$currentTag = $name;
if ($name == "book") {
$currentBook = [
"id" => $attrs['id'] ?? null,
"title" => "",
"author" => "",
"price" => "",
"currency" => ""
];
}
if ($name == "price" && isset($attrs['currency'])) {
$currentBook['currency'] = $attrs['currency'];
}
}
function endElement($parser, $name) {
global $books, $currentBook, $currentTag;
if ($name == "book") {
$books[] = $currentBook;
$currentBook = [];
}
$currentTag = "";
}
function characterData($parser, $data) {
global $currentBook, $currentTag;
$data = trim($data);
if (empty($data)) return;
switch ($currentTag) {
case "title":
$currentBook["title"] .= $data;
break;
case "author":
$currentBook["author"] .= $data;
break;
case "price":
$currentBook["price"] .= $data;
break;
}
}
// 执行解析
if (!xml_parse($parser, $xmlData, true)) {
die(sprintf("XML 错误: %s 在第 %d 行",
xml_error_string(xml_get_error_code($parser)),
xml_get_current_line_number($parser)));
}
xml_parser_free($parser);
// 输出解析结果
foreach ($books as $book) {
echo "书名: {$book['title']}\n";
echo "作者: {$book['author']}\n";
echo "价格: {$book['price']} {$book['currency']}\n";
echo "------------------------\n";
}
使用状态变量追踪上下文
像 $currentTag 和 $currentBook 这种状态变量在嵌套较深时非常关键,能帮你判断当前处于哪个节点下。
过滤空白字符
characterData 中可能会收到大量换行、空格,需 trim() 后判断是否为空。
避免重复赋值
某些标签内容可能分多段返回(特别是长文本),用 .= 拼接可以防止数据截断。
利用命名空间处理复杂 XML
如果 XML 使用了命名空间,建议使用 xml_set_start_namespace_decl_handler() 等高级 API 来配合解析。