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
"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 条注释

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" 的直接子节点。
我不知道为什么这段注释被删除了,我刚测试过,它是正确的。
这不是一个bug,只是文档中没有写。
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年前
XML文档的XPath查询示例。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";
?>
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 5.0.0.0 函数的 PHP 程序员:我们需要一个等效于 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); // string(16) "//fakeprefix:foo"
}

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

?>
匿名用户
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