PHP Conference Japan 2024

xml_set_element_handler

(PHP 4, PHP 5, PHP 7, PHP 8)

xml_set_element_handler设置开始和结束元素处理程序

描述

xml_set_element_handler(XMLParser $parser, callable|string|null $start_handler, callable|string|null $end_handler): true

为 XML parser 设置元素处理程序函数。

当打开新的 XML 元素时调用 start_handler。当关闭 XML 元素时调用 end_handler

参数

parser

XML 解析器。

start_handler

如果传递 null,则处理程序将重置为其默认状态。

警告

空字符串也会重置处理程序,但是从 PHP 8.4.0 开始已弃用。

如果 handler 是一个 callable,则将该可调用对象设置为处理程序。

如果 handler 是一个 string,它可以是使用 xml_set_object() 设置的对象的方法名称。

警告

从 PHP 8.4.0 开始已弃用。

警告

从 PHP 8.4.0 开始,可调用对象在设置处理程序时会被检查其有效性,而不是在调用时检查。这意味着必须在将方法字符串设置为回调之前调用 xml_set_object()。但是,由于此行为从 PHP 8.4.0 开始也已弃用,因此建议改为使用合适的 callable 来表示方法。

处理程序的签名必须为

start_element_handler(XMLParser $parser, string $name, array $attributes): void
parser
调用处理程序的 XML 解析器。
name
包含为此处理程序调用的元素的名称。如果对该解析器生效 大小写折叠,则元素名称将大写。
attributes
一个关联数组,包含元素的属性。如果元素没有属性,则数组为空。此数组的键是属性名称,值是属性值。属性名称与元素名称相同,遵循 大小写折叠 准则。属性值进行大小写折叠。 遍历 attributes 的顺序与声明属性的顺序相同。

end_handler

如果传递 null,则处理程序将重置为其默认状态。

警告

空字符串也会重置处理程序,但是从 PHP 8.4.0 开始已弃用。

如果 handler 是一个 callable,则将该可调用对象设置为处理程序。

如果 handler 是一个 string,它可以是使用 xml_set_object() 设置的对象的方法名称。

警告

从 PHP 8.4.0 开始已弃用。

警告

从 PHP 8.4.0 开始,可调用对象在设置处理程序时会被检查其有效性,而不是在调用时检查。这意味着必须在将方法字符串设置为回调之前调用 xml_set_object()。但是,由于此行为从 PHP 8.4.0 开始也已弃用,因此建议改为使用合适的 callable 来表示方法。

处理程序的签名必须为

end_element_handler(XMLParser $parser, string $name): void
parser
调用处理程序的 XML 解析器。
name
包含为此处理程序调用的元素的名称。如果对该解析器生效 大小写折叠,则元素名称将大写。

返回值

始终返回 true

变更日志

版本 描述
8.4.0 将非 callable string 传递给 handler 现在已弃用,请对方法使用合适的可调用对象,或使用 null 来重置处理程序。
8.4.0 现在在设置处理程序时检查 handler 作为 callable 的有效性,而不是在调用时检查。
8.0.0 parser 现在需要一个 XMLParser 实例;以前,需要一个有效的 xml resource
添加注释

用户贡献的注释 15 条注释

2
rubentrancoso at gmail dot com
18 年前
我的拙见。此示例演示如何将 XML 解析成关联数组树。

<?php

$file
= "flow/flow.xml";
$depth = 0;
$tree = array();
$tree['name'] = "root";
$stack[count($stack)] = &$tree;

function
startElement($parser, $name, $attrs) {
global
$depth;
global
$stack;
global
$tree;

$element = array();
$element['name'] = $name;
foreach (
$attrs as $key => $value) {
//echo $key."=".$value;
$element[$key]=$value;
}

$last = &$stack[count($stack)-1];
$last[count($last)-1] = &$element;
$stack[count($stack)] = &$element;

$depth++;
}

function
endElement($parser, $name) {
global
$depth;
global
$stack;

array_pop($stack);
$depth--;
}

$xml_parser = xml_parser_create();
xml_set_element_handler($xml_parser, "startElement", "endElement");
if (!(
$fp = fopen($file, "r"))) {
die(
"无法打开XML输入文件");
}

while (
$data = fread($fp, 4096)) {
if (!
xml_parse($xml_parser, $data, feof($fp))) {
die(
sprintf("XML错误: %s 在第 %d 行",
xml_error_string(xml_get_error_code($xml_parser)),
xml_get_current_line_number($xml_parser)));
}
}
xml_parser_free($xml_parser);
$tree = $stack[0][0];
echo
"<pre>";
print_r($tree);
echo
"</pre>";
3
aw at avatartechnology dot com
20年前
回复 landb at mail dot net...

如注释中所述,当需要时,您可以传递一个包含对象引用和方法名称的数组……因此您可以像这样调用您自己类中的方法作为处理程序

xml_set_element_handler($parser, array($this,"_startElement"), array($this,"_endElement"));

希望对您有所帮助……
2
youniforever at naver dot com
19年前
<html>
<head>
<title>SAX 演示</title>
<META HTTP-EQUIV='Content-type' CONTENT='text/html; charset=euc-kr'>
</head>
<body>
<h1>RSS 解析</h1>

<?php

$file
= "data.xml";

$currentTag = "";
$currentAttribs = "";

function
startElement($parser, $name, $attribs)
{
global
$currentTag, $currentAttribs;
$currentTag = $name;

$currentAttribs = $attribs;
switch (
$name) {

default:
echo(
"<b>&lt$name&gt</b><br>");
break;
}
}

function
endElement($parser, $name)
{
global
$currentTag;
switch (
$name) {
default:
echo(
"<br><b>&lt/$name&gt</b><br><br>");
break;
}
$currentTag = "";
$currentAttribs = "";
}

function
characterData($parser, $data)
{
global
$currentTag;
switch (
$currentTag) {
case
"link":
echo(
"<a href=\"$data\">$data</a>\n");
break;
case
"title":
echo(
"title : $data");
break;
default:
echo(
$data);
break;
}
}

$xmlParser = xml_parser_create();

$caseFold = xml_parser_get_option($xmlParser,
XML_OPTION_CASE_FOLDING);

$targetEncoding = xml_parser_get_option($xmlParser,
XML_OPTION_TARGET_ENCODING);

if (
$caseFold == 1) {
xml_parser_set_option($xmlParser, XML_OPTION_CASE_FOLDING, false);
}

xml_set_element_handler($xmlParser, "startElement", "endElement");
xml_set_character_data_handler($xmlParser, "characterData");

if (!(
$fp = fopen($file, "r"))) {
die(
"无法打开XML数据文件: $file");
}

while (
$data = fread($fp, 4096)) {
if (!
xml_parse($xmlParser, $data, feof($fp))) {
die(
sprintf("XML错误: %s 在第 %d 行",
xml_error_string(xml_get_error_code($xmlParser)),
xml_get_current_line_number($xmlParser)));
xml_parser_free($xmlParser);
}
}
xml_parser_free($xmlParser);
?>
</table>
</body>
</html>
1
lloeki at gmail dot com
18 年前
我修改了之前的脚本,使其成为关联数组。我觉得这样更有用。顺便说一句,我更喜欢 `strtolower()` 函数,但这完全不是必须的。

<?php

$file
= "data.xml";
$depth = 0;
$tree = array();
$tree['name'] = "root";
$stack[] = &$tree;

function
startElement($parser, $name, $attrs) {
global
$depth;
global
$stack;
global
$tree;

$element = array();
foreach (
$attrs as $key => $value) {
$element[strtolower($key)]=$value;
}

end($stack);
$stack[key($stack)][strtolower($name)] = &$element;
$stack[strtolower($name)] = &$element;

$depth++;
}

function
endElement($parser, $name) {
global
$depth;
global
$stack;

array_pop($stack);
$depth--;
}

$xml_parser = xml_parser_create();
xml_set_element_handler($xml_parser, "startElement", "endElement");
if (!(
$fp = fopen($file, "r"))) {
die(
"无法打开XML输入文件");
}

while (
$data = fread($fp, 4096)) {
if (!
xml_parse($xml_parser, $data, feof($fp))) {
die(
sprintf("XML错误: %s 在第 %d 行",
xml_error_string(xml_get_error_code($xml_parser)),
xml_get_current_line_number($xml_parser)));
}
}
xml_parser_free($xml_parser);
$tree = end(end($stack));
echo
"<pre>";
print_r($tree);
echo
"</pre>";

?>
1
hendra_g at hotmail dot com
19年前
我也遇到了与'ibjoel at hotmail dot com'相同的问题,关于自闭合标签,我发现他/她编写的脚本并没有按我的预期工作。
我尝试了一些PHP函数和示例,并编译了一些内容,这可能不是最简洁的解决方案,但它适用于'ibjoel at hotmail dot com'提供的数据。
数据需要从文件中读取,以便可以使用文件指针fp。它仍然使用xml_get_current_byte_index(resource parser)技巧,但这一次,我检查了索引之前的最后两个字符,并测试它是否为"/>”。

<?php
/* myxmltest.xml:
<normal_tag>
<self_close_tag />
data
<normal_tag>data
<self_close_tag attr="value" />
</normal_tag>
data
<normal_tag></normal_tag>
</normal_tag>
*/

//## 全局变量 ##//
$file = "myxmltest.xml";
$character_data_on = false;
$tag_complete = true;

function
startElement($parser, $name, $attrs)
{
global
$character_data_on;
global
$tag_complete;

echo
"&lt;<font color=\"#0000cc\">$name</font>";
//## 打印属性 ##//
if (sizeof($attrs)) {
while (list(
$k, $v) = each($attrs)) {
echo
" <font color=\"#009900\">$k</font>=\"<font
color=\"#990000\">
$v</font>\"";
}
}
//## 标签仍然不完整,
//## 将在endElement或characterData中完成 ##//
$tag_complete = false;
$character_data_on = false;
}

function
endElement($parser, $name)
{
global
$fp;
global
$character_data_on;
global
$tag_complete;

//#### 测试自闭合标签 ####//
//## xml_get_current_byte_index(resource parser)在此函数中运行时,给出索引位于(用*表示):
//## 对于自闭合标签:<br />*
//## 对于单个闭合标签:<div>character data*</div>
//## 因此,要测试自闭合标签,我们只需测试索引的最后两个字符
//###################################//

if (!$character_data_on) {
//## 记录当前文件指针位置 ##//
$temp_fp = ftell($fp);

//## 将文件指针指向结束元素字节索引之前的2个字节 ##//
$end_element_byte_index = xml_get_current_byte_index($parser);
fseek($fp,$end_element_byte_index-2);

//## 获取结束元素字节索引之前的最后两个字符 ##//
$validator = fgets($fp, 3);

//## 恢复文件指针位置 ##//
fseek($fp,$temp_fp);

//## 如果最后两个字符是"/>" ##//
if ($validator=="/>") {
//// 完成自闭合标签 ////
echo " /&gt";
//// 否则它是一个单独的闭合标签 ////
} else echo "&gt&lt/<font color=\"#0000cc\">$name</font>&gt";
$tag_complete = true;
} else echo
"&lt/<font color=\"#0000cc\">$name</font>&gt";

$character_data_on = false;
}

function
characterData($parser, $data)
{
global
$character_data_on;
global
$tag_complete;

if ((!
$character_data_on)&&(!$tag_complete)) {
echo
"&gt";
$tag_complete = true;
}
echo
"<b>$data</b>";
$character_data_on = true;
}

$xml_parser = xml_parser_create();
xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, false);
xml_set_element_handler($xml_parser, "startElement", "endElement");
xml_set_character_data_handler($xml_parser, "characterData");
if (!(
$fp = fopen($file, "r"))) {
die(
"无法打开XML输入文件");
}

echo
"<pre>";
while (
$file_content = fread($fp, 4096)) {
if (!
xml_parse($xml_parser, $file_content, feof($fp))) {
die(
sprintf("XML错误: %s 在第 %d 行",
xml_error_string(xml_get_error_code($xml_parser)),
xml_get_current_line_number($xml_parser)));
}
}
echo
"</pre>";
xml_parser_free($xml_parser);
?>
1
jg at jmkg dot net
23年前
如果您正在使用一个类来解析XML,并且想要检查xml_set_element_handler的返回值以防失败,则必须在类的构造函数之外执行此操作。在构造函数内部,PHP-4.0.5 将会终止。

基本上,将所有XML初始化代码放在类的另一个函数中,不要放在构造函数中。
0
darien at etelos dot com
17年前
这份文档有些错误。我知道之前已经说过很多次了,但是值得重复……

如果使用PHP4,你可能需要使用`xml_set_object()`来代替使用包含两个元素的数组调用任何`xml_set_*_handler()`函数。在PHP5中它可以正常工作,但是如果将相同的代码迁移到PHP4,则会为每个你设置的处理器创建一个`$this`的副本!(即使你使用了`&$this`)

<?php
// 这段代码在PHP4下会神秘地失败。
$this->parser = xml_parser_create();
xml_set_element_handler(
$this->parser,
array(&
$this,"start_tag"),
array(&
$this,"end_tag")
);
xml_set_character_data_handler(
$this->parser,
array(&
$this,"tag_data")
);
?>

<?php
// 这段代码在PHP4下可以正常工作。
$this->parser = xml_parser_create();
xml_set_object($this->parser,&$this);
xml_set_element_handler(
$this->parser,
"start_tag",
"end_tag"
);
xml_set_character_data_handler(
$this->parser,
"tag_data"
);
?>
0
[email protected]
19年前
`[email protected]`描述的方法需要libxml2作为xml解析器,它不适用于expat。简要说明,请参见`xml_get_current_byte_index`。
0
[email protected]
19年前
我注意到,在下面的示例中,以及我在这个网站上看到的用于在html中查看xml的所有示例中,自闭合标签(如<br />)的外观并没有保留。解析器无法区分<tag />和<tag></tag>,如果你的开始和结束元素函数像这些示例一样,则这两个实例都将输出单独的开始和结束标签。我需要保留自闭合标签,并且花了我一段时间才找到这个解决方法。希望这对某些人有所帮助……

开始标签保持打开状态,然后由其第一个子节点、下一个开始标签或其结束标签完成。结束标签将以“ />”或</tag>结尾,具体取决于解析数据中开始和结束标签之间的字节数。
<?php
//$data=文件路径或字符串
$data=<<<DATA
<normal_tag>
<self_close_tag />
data
<normal_tag>data
<self_close_tag attr="value" />
</normal_tag>
data
<normal_tag></normal_tag>
</normal_tag>
DATA;

function
startElement($parser, $name, $attrs)
{
xml_set_character_data_handler($parser, "characterData");
global
$first_child, $start_byte;
if(
$first_child) //如有必要,关闭开始标签
echo "><br />";
$first_child=true;
$start_byte=xml_get_current_byte_index ($parser);
if(
count($attrs)>=1){
foreach(
$attrs as $x=>$y){
$attr_string .= " $x=\"$y\"";
}
}
echo
htmlentities("<{$name}{$attr_string}"); //未关闭的开始标签
}

function
endElement($parser, $name)
{
global
$first_child, $start_byte;
$byte=xml_get_current_byte_index ($parser);
if(
$byte-$start_byte>2){ //如果结束标签距离开始标签超过2个字节
if($first_child) //如有必要,关闭开始标签
echo "><br />";
echo
htmlentities("</{$name}>")."<br />"; //单独的结束标签
}else
echo
" /><br />"; //自闭合标签
$first_child=false;

}

function
characterData($parser, $data)
{
global
$first_child;
if(
$first_child) //如果$data是第一个子节点,则关闭开始标签
echo "><br />";
if(
$data=trim($data))
echo
"<font color='blue'>$data</font><br />";
$first_child=false;
}

function
ParseData($data)
{
$xml_parser = xml_parser_create();
xml_set_element_handler($xml_parser, "startElement", "endElement");
xml_parser_set_option($xml_parser,XML_OPTION_CASE_FOLDING,0);
if(
is_file($data))
{
if (!(
$fp = fopen($file, "r"))) {
die(
"无法打开XML输入");
}

while (
$data = fread($fp, 4096)) {
if (!
xml_parse($xml_parser, $data, feof($fp))) {
$error=xml_error_string(xml_get_error_code($xml_parser));
$line=xml_get_current_line_number($xml_parser);
die(
sprintf("XML错误: %s 在第 %d 行",$error,$line));
}
}
}else{
if (!
xml_parse($xml_parser, $data, 1)) {
$error=xml_error_string(xml_get_error_code($xml_parser));
$line=xml_get_current_line_number($xml_parser);
die(
sprintf("XML错误: %s 在第 %d 行",$error,$line));
}
}

xml_parser_free($xml_parser);
}

ParseData($data);
?>
0
[email protected]
21年前
似乎标签处理器不会相互阻塞(无论开始处理器是否已完成,都会调用结束处理器)。如果你在规划应用程序时没有意识到这一点,这可能会让你陷入困境。
-1
[email protected]
16年前
这是另一个检测空元素的示例。它适用于libxml2。请注意,它处理缓冲区边界。

<?php

$depth
= 0; //当前深度,用于漂亮打印
$empty = false; //标签是否为空
$offset = 0; //当前缓冲区在流中的起始索引

function tagStart($parser, $name, array $attribs) {
global
$depth, $empty, $data, $offset, $lastchar;
$idx = xml_get_current_byte_index($parser);
/* xml_get_current_byte_index 返回流中的索引,而不是
缓冲区内的索引。*/

/* 检查索引是否在缓冲区内。*/
if (isset($data[$idx - $offset])) {
$c = $data[$idx - $offset];
} else {
/* 如果不在,则简单地使用缓冲区的最后一个字符。*/
$c = $lastchar;
}
$empty = $c == '/';
echo
str_repeat("\t", $depth), "<$name", ($empty ? '/>' : '>'), "\n";
if (!
$empty) ++$depth;
}

function
tagEnd($parser, $name) {
global
$depth, $empty;
if (!
$empty) {
--
$depth;
echo
str_repeat("\t", $depth), "</$name>\n";
} else {
$empty = false;
}
}

$parser = xml_parser_create();
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false);
xml_set_element_handler($parser, 'tagStart', 'tagEnd');

$data1 = '
<test>
<empty att="3" />
<nocontent></nocontent>
<content>
<empty/>
<empty/>
</content>
<empty/'
;

$data2 = '>
<empty att="5" />
</test>
'
;

$data = &$data1;
$length = strlen($data1);
$lastchar = $data[$length-1];
xml_parse($parser, $data1);
$offset .= $length;
$data = &$data2;
xml_parse($parser, $data2);
-2
uiowa.edu的vladimir-leontiev
18 年前
看起来 characterData() 函数一次获取1024个字符的块;因此,如果标签之间的字符串长度超过1024个字符,则对于单个标签对,characterData() 函数将被多次调用。我不知道这个特性(错误?)是否在任何地方有文档记录,我只是想提醒大家注意这一点;它曾经让我困扰过。我在 Linux 上使用的是 php 4.3.10。
-1
redb
18 年前
下面的示例(BadParser)在进行一些更改后可以正常工作。

xml_set_element_handler ( $parser, array ( &$this, 'tagStart' ), array ( &$this, 'tagEnd' ) );
xml_set_character_data_handler ( $parser, array ( &$this, 'tagContent' ) );
-2
匿名用户
19年前
回复 avatartechnology.com 的 aw...
回复 landb at mail dot net...

当你的函数在对象中时
小心!不要忘记添加:&(引用)到你的参数。

xml_set_element_handler($parser, array(&$this,"_startElement"), array(&$this,"_endElement"));
——> xmlparse 将在你的对象上工作(很好)。

而不是
xml_set_element_handler($parser, array($this,"_startElement"), array($this,"_endElement"));
——> xmlparse 将在你的对象的副本上工作(通常不好)

Vin-s
(抱歉我的英语)
-2
匿名用户
23年前
你可以使用类来解析 XML。只需查看以下函数

xml_set_object
To Top