使用XPath在PHP中提取XML

4

I have the following XML:

<root>
   <level name="level1">
       <!-- More children <level> --> 
   </level>

   <level name="level2"> 
       <!-- Some more children <level> --> 
   </level> 
</root>

我如何直接提取根节点下的,以便我可以运行一个XPath查询(例如$xml->xpath('//some-query')),相对于提取的?

你不明白吗?你想要一个 XPath 来获取第一级节点吗? /root/level[1] - splash58
你想/需要将这些查询分成两部分的特定原因是什么?(可能有...) - VolkerK
我设计的 XPath 只有在根节点内只有一个 <level> 时才能正常工作。 - dr_rk
您可以通过在查询中添加谓词,例如 level[@name="level2"],将其限制/固定到一个级别元素。 - VolkerK
我的唯一问题是,我的XPath查询应该按照这个步骤进行,首先通过识别XML中的叶节点来工作:http://stackoverflow.com/questions/32478608/php-and-xml-iterate-an-xmls-leaf-elements - dr_rk
4个回答

7

DOMXPath::query 的第二个参数是上下文节点。只需传递您以前“找到”的 DOMNode 实例,您的查询将相对于该节点运行。例如:

<?php
$doc = new DOMDocument;
$doc->loadxml( data() );

$xpath = new DOMXPath($doc);
$nset = $xpath->query('/root/level[@name="level1"]');
if ( $nset->length < 1 ) {
    die('....no such element');
}
else {
    $elLevel = $nset->item(0);

    foreach( $xpath->query('c', $elLevel) as $elC) {
        echo $elC->nodeValue, "\r\n";
    }
}


function data() {
    return <<< eox
<root>
    <level name="level1">
        <c>C1</c>
        <a>A</a>
        <c>C2</c>
        <b>B</b>
        <c>C3</c>
    </level>
    <level name="level2"> 
        <!-- Some more children <level> --> 
    </level> 
</root>
eox;
}

但是,除非你必须执行多个单独的(可能是复杂的)后续查询,否则这通常是不必要的。

<?php
$doc = new DOMDocument;
$doc->loadxml( data() );

$xpath = new DOMXPath($doc);
foreach( $xpath->query('/root/level[@name="level1"]/c') as $c ) {
    echo $c->nodeValue, "\r\n"; 
}


function data() {
    return <<< eox
<root>
    <level name="level1">
        <c>C1</c>
        <a>A</a>
        <c>C2</c>
        <b>B</b>
        <c>C3</c>
    </level>
    <level name="level2"> 
        <c>Ahh</c>
        <a>ouch</a>
        <c>no</c>
        <b>wrxl</b>
    </level> 
</root>
eox;
}

只使用一个查询就能产生相同的输出。

7

DOMXpath::evaluate()允许您从DOM中获取节点列表和标量值。

因此,您可以使用XPath表达式直接获取值:

$document = new DOMDocument();
$document->loadXml($xml);
$xpath = new DOMXpath($document);

var_dump(
  $xpath->evaluate('string(/root/level[@name="level2"]/@name)')
);

输出:

string(6) "level2"

Xpath表达式

root中的所有level元素节点:
/root/level

具有特定名称属性的节点:
/root/level[@name="level2"]

您想获取的值(用于验证的name属性):
/root/level[@name="level2"]/@name

转换为字符串,如果找到节点,则结果将为空字符串:

string(/root/level[@name="level2"]/@name)

循环遍历节点,将其用作上下文

如果您需要为节点执行多个表达式,最好单独获取它并使用foreach()DOMXpath::evaluate()的第二个参数是上下文节点。

foreach ($xpath->evaluate('/root/level[@name="level2"]') as $level) {
  var_dump(
    $xpath->evaluate('string(@name)', $level)
  );
}

节点列表长度

如果需要处理未发现节点,则可以检查 DOMNodeList::$length 属性。

$levels = $xpath->evaluate('/root/level[@name="level2"]');
if ($levels->length > 0) {
  $level = $levels->item(0);
  var_dump(
    $xpath->evaluate('string(@name)', $level)
  );
} else {
  // no level found
}

count()表达式

您也可以使用count()表达式来验证是否存在元素。

var_dump(
  $xpath->evaluate('count(/root/level[@name="level2"])')
);

输出:

float(1)

布尔结果

可以将其作为Xpath条件,并返回布尔值。

var_dump(
  $xpath->evaluate('count(/root/level[@name="level2"]) > 0')
);

输出:

bool(true)

2
这应该可以工作:
$dom = new DOMDocument;
$dom->loadXML($xml);
$levels = $dom->getElementsByTagName('level');

foreach ($levels as $level) {
   $levelname = $level->getAttribute('name');
      if ($levelname == 'level1') {
        //do stuff
      } 
}

我个人更喜欢使用DOMNodeList类来解析XML。

据我理解,OP只需要第一个。在这种情况下,他将使用元素内部的“name”标签。 - Peter Noble
...但是getElementsByTagName不会以这种方式执行。 name是一个属性,level1是示例文档中属性name的值之一。getElementsByTagName不关心属性。在示例文档中没有<level1>元素->getElementsByTagName('level1')将返回一个空列表。 - VolkerK
@VolkerK,你完全正确。我修改了我的回答。感谢您的观察。 - Peter Noble
现在到了问题中的 $xml->Path('\\some-query') 部分,这里我可以运行一个 XPath 查询。我的猜测是这是真正的问题,而不是如何“查找/提取”一个特定级别的元素。但也只是猜测。 - VolkerK
我打错了一个字母。不幸的是,它是大小写敏感的。我用$level->getAttribute('name');替换了$level->getAttribute('Name'); - Peter Noble
显示剩余7条评论

2
使用querypath解析XML/HTML使得这一切都变得超级简单。
$qp = qp($xml) ;
$levels = $qp->find('root')->eq(0)->find('level') ;

foreach($levels as $level ){
    //do  whatever you want with it , get its xpath , html, attributes etc.
    $level->xpath() ; //
}

优秀的Querypath初学者教程。

Querypath看起来非常有用。如何仅查找与特定属性(比如level1)匹配的find('level')?它采用什么类型的许可证? - dr_rk
我猜应该是 ->find('level[name="level1"]'),即一个 CSS3 选择器,用于选择具有属性名为 name,值为 level1 的元素 level - 除了在属性名之前没有 @ 之外,在这种情况下它与 xpath 查询完全相同。 - VolkerK
在foreach循环中,使用$level->attr('name')获取级别的属性名称,然后与正则表达式进行比较。我不知道为什么被踩了,但查询部分非常灵活。 - gyaani_guy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接