PHP Conference Japan 2024

DOMXPath::query

(PHP 5, PHP 7, PHP 8)

DOMXPath::query評估給定的 XPath 表達式

描述

public DOMXPath::query(string $expression, ?DOMNode $contextNode = null, bool $registerNodeNS = true): mixed

執行給定的 XPath expression

參數

expression

要執行的 XPath 表達式。

contextNode

可選擇指定 contextNode 以執行相對 XPath 查詢。預設情況下,查詢是相對於根元素。

registerNodeNS

是否自動將上下文節點範圍內的命名空間前綴註冊到 DOMXPath 物件。這可以用來避免需要針對每個範圍內的命名空間手動呼叫 DOMXPath::registerNamespace()。當存在命名空間前綴衝突時,只會註冊最近的後代命名空間前綴。

回傳值

回傳一個 DOMNodeList,其中包含所有符合給定 XPath expression 的節點。任何未回傳節點的表達式都會回傳一個空的 DOMNodeList

如果 expression 格式不正確或 contextNode 無效,DOMXPath::query() 會回傳 false

範例

範例 1 取得所有英文書籍

<?php

$doc
= new DOMDocument;

// 我們不想處理空白字元
$doc->preserveWhiteSpace = false;

$doc->load('book.xml');

$xpath = new DOMXPath($doc);

// 我們從根元素開始
$query = '//book/chapter/para/informaltable/tgroup/tbody/row/entry[. = "en"]';

$entries = $xpath->query($query);

foreach (
$entries as $entry) {
echo
"找到 {$entry->previousSibling->previousSibling->nodeValue}," .
" 作者為 {$entry->previousSibling->nodeValue}\n";
}
?>

以上範例會輸出

Found The Grapes of Wrath, by John Steinbeck
Found The Pearl, by John Steinbeck

我們也可以使用 contextNode 參數來縮短我們的表達式

<?php

$doc
= new DOMDocument;
$doc->preserveWhiteSpace = false;

$doc->load('book.xml');

$xpath = new DOMXPath($doc);

$tbody = $doc->getElementsByTagName('tbody')->item(0);

// 我們的查詢是相對於 tbody 節點
$query = 'row/entry[. = "en"]';

$entries = $xpath->query($query, $tbody);

foreach (
$entries as $entry) {
echo
"找到 {$entry->previousSibling->previousSibling->nodeValue}," .
" 作者為 {$entry->previousSibling->nodeValue}\n";
}
?>

參見

  • DOMXPath::evaluate() - 評估給定的 XPath 表達式,並在可能的情況下回傳類型化的結果

新增註解

使用者貢獻的註解 18 筆註解

kkez at example dot com
14 年前
如果 query() 函式似乎忽略您的 $contextnode,反而回傳文件中所有標籤,請嘗試使用相對路徑(在查詢前面使用 .)

<?php
$xml
= "<?xml version='1.0' encoding='UTF-8'?>
<test>
<tag1>
<uselesstag>
<tag2>test</tag2>
</uselesstag>
</tag1>
<tag2>test2</tag2>
</test>"
;

$dom = new DomDocument();
$dom->loadXML($xml);
$xpath = new DomXPath($dom);

$tag1 = $dom->getElementsByTagName("tag1")->item(0);

echo
$xpath->query("//tag2")->length; //輸出 2 -> 正確
echo $xpath->query("//tag2", $tag1)->length; //輸出 2 -> 錯誤,查詢不是相對的
echo $xpath->query(".//tag2", $tag1)->length; //輸出 1 -> 正確 (注意 // 前面的點)
?>

請注意,我無法按照文件說明使用 $xpath->query("tag2", $tag1),因為 "tag2" 並非 "tag1" 的直接子元素。
我不明白為何這個註解被刪除,我剛剛測試過,它是正確的。
這不是錯誤,只是文件沒有寫到而已。
Hayley Watson
17 年前
請注意,如果你的 DOMDocument 是從 HTML 載入的,其中元素和屬性名稱不區分大小寫,DOM 解析器會將它們全部轉換為小寫,因此你的 XPath 查詢也必須如此;即使原始 HTML 包含 "<A HREF='example.com'>",'//A/@HREF' 也找不到任何東西。
nicolas_rainardNOSPAM at yahoo dot fr
17 年前
請注意,clochix 所說的對於*任何*具有預設命名空間的文件(如同 XHTML 的情況)都是有效的。

這個文件

<?xml version="1.0" encoding="UTF-8" ?>

<root xmlns="http://www.exemple.org/namespace">

<element id="1">
...
</element>

<element id="2">
...
</element>

</element>

必須這樣存取

$document = new DOMDocument();
$document->load('document.xml');

$xpath = new DOMXPath($document);
$xpath->registerNameSpace('fakeprefix', 'http://www.exemple.org/namespace');

$elements = $xpath->query('//fakeprefix:element');

當然,原始文件中沒有前綴,但如果你使用預設命名空間,DOMXPath 類別*需要*一個前綴,無論它是什麼。如果你像這樣指定一個空前綴,它*不起作用*

$xpath->registerNameSpace('', 'http://www.exemple.org/namespace');

希望這能幫助你節省一些時間...
RiKdnUa at mail dot ru
11 年前
Пример XPath запроса к XML документу. XML документ содержить элементы с именами из НЕлатинских символов (кириллица). При использовании в XPath запросе предиката, функция DOMXPath::query() выдает предупреждение и запрос не работает. Чтобы запрос работал, надо явно указывать ось. Файл этого примера должен быть в кодировке WINDOWS-1251. Тестировал в PHP 5.2.9-2 и PHP 5.2.17

XML 文件的 XPath 查詢範例。XML 文件包含具有非拉丁字符(西里爾字母)名稱的元素。在 XPath 查詢中使用述詞時,DOMXPath::query() 函數會發出警告,並且查詢不起作用。為了使查詢生效,必須明確指定軸。此範例檔案應採用 WINDOWS-1251 編碼。已在 PHP 5.2.9-2 和 PHP 5.2.17 中測試過。
<?php
ini_set
("display_errors","on");
error_reporting(-1);
function
utf8encode($str){return iconv('WINDOWS-1251', 'UTF-8', $str);}
$xml="<?xml version='1.0' encoding='WINDOWS-1251'?>
<часть>
<ссылка href='yandex.com'>Яндекс</ссылка>
<ссылка href='rik.dn.ua/fotopan.php'>г.Донецк</ссылка>
</часть>
"
;
$document=new domDocument();
$document->preserveWhiteSpace=false;
$document->loadXML($xml);
$domxpath=new domXpath($document);
$list=$domxpath->query(utf8encode('/child::часть/child::ссылка'));//Ok
echo '$list->length='.$list->length."\n<br/>\n";
$list=$domxpath->query(utf8encode('/часть/ссылка'));//Ok
echo '$list->length='.$list->length."\n<br/>\n";
$list=$domxpath->query(utf8encode('/child::часть/child::ссылка[position()=1]'));//Ok
echo '$list->length='.$list->length."\n<br/>\n";
$list=$domxpath->query(utf8encode('/часть/ссылка[1]'));//Warning: DOMXPath::query() [domxpath.query]: Invalid expression in ...
echo '$list->length='.$list->length."\n<br/>\n";
$list=$domxpath->query(utf8encode('/часть/ссылка[position()=1]'));//Warning: DOMXPath::query() [domxpath.query]: Invalid expression in ...
echo '$list->length='.$list->length."\n<br/>\n";
?>
jakob dot voss at nichtich dot de
19 年前
你可以用這種方式將結果節點轉換為新的 DOMDocument 物件

<?php
$result
= $xpath->query($query);
$resultNode = $result->item(0);
$newDom = new DOMDocument;
$newDom->appendChild($newDom->importNode($resultNode,1));

print
"<pre>" . htmlspecialchars($newDom->saveXML()) . "</pre>";
?>
jbarnett at flowershopnetwork dot com
17 年前
傳回值中節點的順序無法保證。

當我的程式碼在舊伺服器上時,傳回的 DOMNodeList 是按照文件順序排列的。在新伺服器上,傳回的 DOMNodeList 順序一致,但不是按照文件順序排列的。

PHP 將這個函數呼叫傳遞給 libxml 中的 xmlXPathEvalExpression() 函數。libxml 中的該函數僅接受兩個引數,與此 PHP 函數接受的引數相同。從舊伺服器到新伺服器,libxml 版本一定發生了變更,並且該 libxml 的行為有所不同。

如果 PHP 有一種比較節點的方式,讓我能手動重新排序節點,這會沒問題,但是沒有。

因此,沒有保證的方法可以取得像 DOM 3 XPath 那樣的節點排序清單。
adam dot prall at thinkingman dot com
16 年前
如果你像我一樣,想知道為什麼你的 XPath 查詢沒有傳回你在 (X)HTML 文件中建立的任何新 DOMElements,而只傳回最初使用 (例如) loadXML() 載入的元素,原因就在這裡;如果你做對了,在建立 DOMXPath 物件之後,你已經註冊了名為 'html' 的命名空間,如下所示

<?php

class XPathQueryLength {
private
$nameSpace = '';
function
__construct(DOMDocument $doc) {
$this->xpath = new DOMXPath($this->doc);
$this->xpath->registerNamespace(
'html','http://www.w3.org/1999/xhtml' );
}
function
queryLength($query) {
return
$this->xpath->query($query)->length;
}
}

?>

...但是別忘了,當將新元素新增到上述 DOMDocument $doc 時,請使用 createElementNS() 而不是 createElement(),否則你會遇到這個問題

<?php

//$doc 是一個先前載入的 XHTML 文件,包含正常的 html、head 和 body 結構
//$body 是使用 $doc->getElementsByTagName('body') 選取的第一個標籤

$pTag = $doc->createElement('p','這是一個新的段落!');
$body->appendChild($pTag);

$xPath = new XPathQueryLength($doc);
print
$xPath->queryLength('//html:p');

輸出: 0

print $xPath->queryLength('//p');

輸出: 1

?>

所以改為這樣做

<?php

//$doc 是一個先前載入的 XHTML 文件,包含正常的 html、head 和 body 結構
//$body 是使用 $doc->getElementsByTagName('body') 選取的第一個標籤

$pTag = $doc->createElementNS('http://www.w3.org/1999/xhtml','p','這是一個新的段落!');
$body->appendChild($pTag);

$xPath = new XPathQueryLength($doc);
print
$xPath->queryLength('//html:p');

輸出: 2

print $xPath->queryLength('//p');

輸出: 0

?>

這兩個範例腳本產生的 XHTML 檔案看起來很像這樣

<html>
<head></head>
<body>
<p>這是一個硬編碼的段落。</p>
<p>這是一個新的段落!</p>
</body>
</html>

...所以你可能會認為段落就是段落,因為你從未看到前綴,像是 "<html:p>這是一個新的段落!</html:p>"。

這可能看起來很明顯,但當時我正在寫一個將 CSS 查詢轉換為 XPath 查詢的類別,而命名空間已註冊的事實卻被埋藏在程式碼中。

我們愛 DOM,DOM 對我們很好。
Nibinaear
16 年前
我搜尋了整個網路,想找到一種更新/修改/變更 XML 檔案元素的方法,卻一無所獲!

所以這是「用 PHP 變更 XML 元素」的明確方法,而不是添加/附加新的元素。這使用 XPATH

<?php

// 建立一個 DOMDocument 實例
$xml = new DOMDocument;

// 忽略節點之間的空白 (預設:true)
$xml->preserveWhiteSpace = false;

$file='about.xml';

// 載入 XML 資料來源
$xml->Load($file);

$xpath = new DOMXPath($xml);

$query='/regions/branch';

$entries = $xpath->query($query);

foreach (
$entries as $entry)
{
$entry->firstChild->nodeValue="像這樣!";
echo
$entry->firstChild->nodeValue;
}

$xml->save($file);

?>
chris dot russo99 at gmail dot com
8 年前
如果你找不到 PHP XPATH 區分大小寫的解決方案,你可以試試這個方法

http://fsockopen.com/php-programming/your-final-stop-for-php-xpath-case-insensitive

不要將 PHP 函數插入 XPATH 物件,而是將 XPATH 物件轉換為 Array(),然後使用任何 PHP 函數,以常規方式使用。
ikmahesh at cdac dot in
9 年前
$xPath->query() 方法的參數區分大小寫。
它會比對 ID 的確切文字。
info at syncgw dot com
13 年前
警告所有使用此函數的 PHP 程式設計師 PHP 5.0.0.0:我們需要一個等效於 upper-case() 函數的函數 (在 XPath 1.0 中不可用)。

XML 文件

<Rec>
<SourceRef>./c:calendar2</SourceRef>
<SourceRef>./c:calendar</SourceRef>
</Rec>

使用

query('//DataStore[translate(SourceRef,"abcdefghijklmnopqrstuvwxyz","ABCDEFGHIJKLMNOPQRSTUVWXYZ")="./C:CALENDAR"]/.')

傳回零個比對。

如果將 XML 來源變更為

<Rec>
<SourceRef>./c:calendar</SourceRef>
<SourceRef>./c:calendar2</SourceRef>
</Rec>

一切都正常
chris AT cmbuckley DOT co DOT uk
13 年前
為了協助解決 DOMXPath 物件未註冊預設命名空間的問題,您可以使用以下替代方法來更新您的路徑

<?php

$xml
= <<<EOS
<root xmlns="urn:test">
<foo>bar</foo>
</root>
EOS;

$expression = '//foo';
$prefix = 'fakeprefix';

$doc = new DOMDocument();
$doc->loadXML($xml);

$context = $doc->documentElement; // 或您選擇的任何元素
$xpath = new DOMXPath($doc);

// 如下所示註冊命名空間,並將正規表示式套用到運算式
if (null !== $context->namespaceURI) {
$xpath->registerNamespace($prefix, $context->namespaceURI);
$expression = preg_replace('#(::|/\s*|\A)(?![/@].+?|[a-z\-]+::)#', '$1' . $prefix . ':$2', $expression);
var_dump($expression); // 字串(16) "//fakeprefix:foo"
}

$foo = $xpath->query($expression, $context)->item(0);
var_dump($doc->saveXML($foo)); // 字串(14) "<foo>bar</foo>"

?>
Anonymous
15 年前
我發現這個對於建立頁面範本很有用

<?php
$xsl
= new DOMDocument;
$xsl->load('layout.xsl');

// 設定 <xsl:include> href 屬性,要在這個版面配置中包含的內部樣式表
$xpath = new DomXPath($xsl);
$res = $xpath->query('//xsl:include');
$res->item(0)->setAttribute('href','page.xsl');
$xsl->save('media/xsl/layout.xsl');
?>
ondrej dot fischer at 4internet dot cz
17 年前
不幸的是,PHP 的 DOM 擴充功能不支援使用
<?xml-stylesheet type="text/xsl" ... ?>
處理指令。
這裡有一個範例,說明如何使用 XPath 查詢並透過方法 output() 擴充 DOMDocument 來實作。

<?php

// 這個簡單的函式在 PHP5 的參考模型中加入了匿名實例的直接使用
function a($var) {
return
$var;
}

// 擴充的 DOMDocument 類別
class MyDOMDocument extends DOMDocument
{

public function
output()
{
$stylesheets = array();
$PIs = a(new DOMXPath($this))
->
query('/processing-instruction("xml-stylesheet")');

foreach(
$PIs as $PI)
{
// 這個可以透過正規解析 DOMProcessingInstruction::data 屬性來更乾淨地實作
if(ereg('type *= *"text/xsl" +href *= *"([^"]+)"', $PI->data, $mem))
{
// 這裡應該驗證 XSL 檔案是否存在。
a($stylesheets[] = new DOMDocument())->load($mem[1]);
}
}

if(
$stylesheets)
{
$processor = new XSLTProcessor();
foreach(
$stylesheets as $stylesheet)
$processor->importStylesheet($stylesheet);
return
$processor->transformToDoc($this);
}
// 如果沒有樣式表指令,直接回傳自身
else return $this;

}
}

?>

用法

<?php

$document
= new MyDOMDocument();
$document->load('my.xml');
echo
$document->output()->saveXML();

?>

使用以下的 my.xml 檔案

<?xml version="1.0" ?>
<?xml-stylesheet type="text/xsl" href="my.xsl" ?>
<my-root />

以及存在的 my.xsl 檔案,程式碼會使用 my.xsl 轉換 xml 檔案並輸出結果。
Niklas
16 年前
對於 XPath 跳脫字元,請使用以下方法 (當然可以更有效率)。
<?php
public function xpathescape($string)
{
$result = 'concat(';

for(
$i=0, $j=strlen($string); $i<$j; ++$i)
{
if(
$i > 0)
$result .= ",";

if(
$string[$i] == '\'')
$result .= "\"".$string[$i]."\"";
else
$result .= '\''.$string[$i].'\'';
}

$result .= ')';

return
$result;
}
?>

這樣使用它
<php
$xpath->query('//example[sub='.xpathescape($acomplexstring).']');

?>
clochix at clochix dot net
17 年前
如果您想要在 XHTML 文件上執行查詢,您必須修正預設的命名空間

<?php
$doc
= new DOMDocument;
$doc->preserveWhiteSpace = true;
$doc->resolveExternals = true; // 用於字元實體
$doc->load("http://www.w3.org/");
$xpath = new DOMXPath($doc);
// 不會工作
$entries = $xpath->query("//div");
// 您應該使用:
$xpath->registerNamespace("html", "http://www.w3.org/1999/xhtml");
$entries = $xpath->query("//html:div");
?>
yuriscom at gmail dot com
13 年前
我希望這對某些人會有幫助

當您查詢包含引號的字串時,我花了一些時間來解決這個問題。

假設您有
$parameter = "aaa \"bbb\"";
$domxpath->query("//path[text()=\"".$parameter."\""];

在版本 > 5.3.0 中,有 registerPhpFunctions,您可以在其中放置 addslashes。但在較舊的版本中,您無法以簡單的方式做到。

因此,解決方案是使用 concat 函數。所以當您有一個包含 " 的子字串時,請用 ' 包裹它。當您有一個包含 ' 的子字串時,請用 " 包裹它。

程式碼是

<?php
$dom
= new DOMDocument;
$dom->loadXML("<name>'bla' \"bla\" bla</name>");
$xpath = new DOMXPath($dom);
$nodeList = $xpath->query("//name[text()=concat(\"'bla' \" ,'\"bla\"' ,\" bla\")]");
?>

以下是接收字串並回傳 xpath 查詢的 concat 模式的函式。

<?php
function getPattern_MQ($pattern) {
// 初始化子字串陣列
$ar = array();
// 指向字串中目前的位置
$offset = 0;
$strlen = strlen($pattern);
while (
true) {
// 尋找引號的位置
$qPos = strpos($pattern, "\"", $offset);

if (!
$qPos) {
// 沒有更多引號了
$leftOver = $offset - $strlen;
if (
$leftOver < 0) {
$string = substr($pattern, $leftOver);
$ar[] = "\"" . $string . "\"";
}
break;
}
// 將引號前的整個子字串加入陣列
$ar[] = "\"" . substr($pattern, $offset, ($qPos - $offset)) . "\"";
// 加入用單引號包住的引號
$ar[] = "'" . substr($pattern, $qPos, 1) . "'";
$offset = $qPos + 1;
}
// 連結陣列以取得:concat("aaa",'"',"bbb",'"');
$pattern = "concat(''," . join(",", $dynamicPatternsAr) . ")";
return
$pattern;
}
?>
Eric Hanson
19 年前
以下提供兩個很棒的 XPath 參考資料。

五段文字解釋 XPath (終於!)
http://www.rpbourret.com/xml/XPathIn5.htm

W3C 規範實際上有一堆有用的範例
http://www.w3.org/TR/xpath#location-paths
To Top