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
"Found {$entry->previousSibling->previousSibling->nodeValue}," .
" by {$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
"Found {$entry->previousSibling->previousSibling->nodeValue}," .
" by {$entry->previousSibling->nodeValue}\n";
}
?>

参见

  • DOMXPath::evaluate() - 评估给定的 XPath 表达式,并在可能的情况下返回类型化的结果

添加注释

用户贡献的注释 18 个注释

129
kkez at example dot com
13 年前
如果 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" 的直接子节点,这与文档中描述的不符。
我不知道为什么这个说明被删除了,我刚刚测试了一下,它是正确的。
这不是错误,只是文档中没有写。
31
Hayley Watson
17 年前
请注意,如果您的 DOMDocument 从 HTML 加载,其中元素和属性名称不区分大小写,则 DOM 解析器会将它们全部转换为小写,因此您的 XPath 查询也必须如此;'//A/@HREF' 即使原始 HTML 包含 "<A HREF='example.com'>" 也不会找到任何东西。
23
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');

希望这能节省一些时间...
6
RiKdnUa at mail dot ru
10 年前
XPath 查询 XML 文档的示例。XML 文档包含名称为非拉丁字符(西里尔字母)的元素。在 XPath 查询中使用谓词时,函数 DOMXPath::query() 会发出警告,并且查询不起作用。为了使查询起作用,有必要显式指定轴。此示例的文件应使用 WINDOWS-1251 编码。在 PHP 5.2.9-2 和 PHP 5.2.17 中测试

Example XPath-query to the XML-document. XML-document contains an elements with the names of non-Latin characters (cyrillic). When used predicate in XPath-query, function DOMXPath::query() gives a warning and query does not work. In order to earned the query, it is necessary to explicitly specify the axis. The file of this example is to be in the encoding WINDOWS-1251. Tested in PHP 5.2.9-2 and 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";
?>
2
jakob dot voss at nichtich dot de
18 年前
您可以通过这种方式将结果节点转换为新的 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>";
?>
5
jbarnett at flowershopnetwork dot com
16 年前
返回值中节点的顺序没有保证。

当我的代码在旧服务器上时,返回的 DOMNodeList 按文档顺序排列。在新服务器上,返回的 DOMNodeList 顺序一致,但不是按文档顺序排列。

PHP 将此函数调用传递给 libxml 中的 xmlXPathEvalExpression() 函数。libxml 中的该函数只接受两个参数 - 与此 PHP 函数接受的两个参数相同。从旧服务器到新服务器的 libxml 版本一定发生了变化,并且该 libxml 的行为有所不同。

如果 PHP 有一种方法可以比较节点,以便我可以手动对节点进行排序,那就没问题了,但没有。

因此,没有保证的方法可以像 DOM 3 XPath 提供的那样获取节点的有序列表。
3
adam dot prall at thinkingman dot com
16 年前
如果您像我一样想知道,为什么您的 XPath 查询没有返回您在 (X)HTML 文档中创建的任何新的 DOMElements,而只返回最初使用 (例如) loadXML() 加载的那些,这就是原因;如果您做对了,您已经在创建 DOMXPath 对象后注册了 nameSpace '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','This is a new paragraph!');
$body->appendChild($pTag);

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

output: 0

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

output: 1

?>

所以这样做

<?php

// $doc 是之前加载的 XHTML 文档,包含正常的 html、head 和 body 结构
// $body 是使用 $doc->getElementsByTagName('body') 选择的第一个标签

$pTag = $doc->createElementNS('http://www.w3.org/1999/xhtml','p','This is a new paragraph!');
$body->appendChild($pTag);

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

output: 2

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

output: 0

?>

这两个示例脚本生成的 XHTML 文件看起来很像这样

<html>
<head></head>
<body>
<p>This is a hardcoded paragraph.</p>
<p>This is a new paragraph!</p>
</body>
</html>

...所以你会认为段落就是段落,因为你从来没有看到过前缀,就像“<html:p>This is a new paragraph!</html:p>”。

这可能看起来很明显,但是我正在编写一个将 CSS 查询转换为 XPath 查询的类,而名称空间是否已注册这一事实却被埋藏在代码中。

我们喜欢 DOM,DOM 对我们很好。
1
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="like this!";
echo
$entry->firstChild->nodeValue;
}

$xml->save($file);

?>
1
chris dot russo99 at gmail dot com
7 年前
如果您找不到 PHP XPATH 区分大小写的解决方案,您可以尝试这种方法

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

不要将 PHP 函数插入 XPATH 对象,而是将 XPATH 对象转换为 Array(),然后以常规方式使用任何 PHP 函数。
0
ikmahesh at cdac dot in
9 年前
$xPath->query() 方法的参数区分大小写。
它匹配 ID 的确切词语。
0
info at syncgw dot com
12 年前
警告所有使用此函数的 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>

一切正常
0
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); // string(16) "//fakeprefix:foo"
}

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

?>
-1
Anonymous
14 年前
我发现这对构建页面模板很有用

<?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');
?>
-1
ondrej dot fischer at 4internet dot cz
16 年前
不幸的是,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 文件并输出结果。
-1
Niklas
15 年前
对于 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).']');

?>
-1
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");
?>
-2
yuriscom at gmail dot com
12 年前
我希望这对某些人有所帮助

我花了一些时间来解决当你在字符串中查询带有引号时的问题。

假设你拥有
$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;
}
?>
-2
Eric Hanson
19 年前
以下是两个很棒的 XPath 参考。

五段话(终于!)中的 XPath
http://www.rpbourret.com/xml/XPathIn5.htm

w3c 规范实际上有一些有用的示例
http://www.w3.org/TR/xpath#location-paths
To Top