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 起已棄用。

如果 handlercallable,則將可呼叫物件設定為處理器。

如果 handlerstring,則它可以是使用 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 起已棄用。

如果 handlercallable,則將可呼叫物件設定為處理器。

如果 handlerstring,則它可以是使用 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 現在已棄用將非 callablestring 傳遞給 handler,請使用正確的可呼叫物件作為方法,或使用 null 重設處理器。
8.4.0 現在會在設定處理器時檢查 handler 作為 callable 的有效性,而不是在呼叫時檢查。
8.0.0 parser 現在需要 XMLParser 實例;先前需要有效的 xml resource
新增註解

使用者貢獻註解 15 則註解

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(
"could not open XML input");
}

while (
$data = fread($fp, 4096)) {
if (!
xml_parse($xml_parser, $data, feof($fp))) {
die(
sprintf("XML error: %s at line %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>";
aw at avatartechnology dot com
20 年前
回覆給 landb at mail dot net...

如同筆記中提到的,當你需要時,你可以傳遞一個包含物件參考和方法名稱的陣列... 所以你可以像這樣在你自己的類別中呼叫方法作為處理器

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

希望這有幫助...
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>
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(
"could not open XML input");
}

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>";

?>
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) 這個技巧,但這次我會檢查索引之前的最後 2 個字元,並測試它是否為 "/>"。

<?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>字元資料*</div>
//## 因此,要測試自閉合標籤,我們只需測試索引位置的最後 2 個字元
//###################################//

if (!$character_data_on) {
//## 記錄目前的 fp 位置 ##//
$temp_fp = ftell($fp);

//## 將 fp 指向結束元素位元組索引之前 2 個位元組的位置 ##//
$end_element_byte_index = xml_get_current_byte_index($parser);
fseek($fp,$end_element_byte_index-2);

//## 取得結束元素位元組索引之前的最後 2 個字元 ##//
$validator = fgets($fp, 3);

//## 還原 fp 位置 ##//
fseek($fp,$temp_fp);

//## 如果最後 2 個字元是 "/>" ##//
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);
?>
jg at jmkg dot net
23 年前
如果您正在使用類別進行 XML 解析,並且想要檢查 xml_set_element_handler 的傳回值,以防失敗,您必須在類別的建構子之外執行此操作。在建構子內,PHP-4.0.5 會終止。

基本上,將所有 XML 初始化程式碼放在類別的另一個函數中,並將其保留在建構子之外。
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"
);
?>
turan dot yuksel at tcmb dot gov dot tr
19 年前
「ibjoel at hotmail dot com」所描述的方法需要 libxml2 作為 XML 解析器,它不適用於 expat。如需簡短說明,請參閱 xml_get_current_byte_index。
ibjoel at hotmail dot com
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);
?>
tj at tobyjoe dot com
21 年前
標籤處理器之間似乎不會互相阻擋(無論開始處理器是否完成,都會呼叫結束處理器)。如果在規劃應用程式時沒有意識到這一點,可能會讓您陷入困境。
kok at nachon dot nl
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);
vladimir-leontiev at uiowa dot edu
18 年前
看起來 characterData() 會以 1024 個字元為單位取得字元資料;因此,如果你的標籤之間有超過 1024 個字元的字串,characterData() 將會被多次呼叫,即使它們屬於單一組標籤。我不確定這個特性(或 bug?)是否有文件說明,我只是想提醒大家注意,因為我因此遇到了問題。我使用的環境是 Linux 上的 php 4.3.10。
redb
18 年前
下面的範例(BadParser)經過一些修改後可以正常運作。

xml_set_element_handler ( $parser, array ( &$this, 'tagStart' ), array ( &$this, 'tagEnd' ) );
xml_set_character_data_handler ( $parser, array ( &$this, 'tagContent' ) );
Anonymous
19 年前
回覆給 avatartechnology dot 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
(抱歉我的英文不好)
Anonymous
23 年前
你可以使用類別來解析 XML。參考看看下面的函式

xml_set_object
To Top