简体   繁体   中英

Using XPath to extract XML in PHP

I have the following XML:

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

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

How can I extract a <level> directly under <root> so that I can run an XPath query such as $xml->xpath('//some-query') relative to the extracted <level> ?

DOMXpath::evaluate() allows you to fetch node lists and scalar values from a DOM.

So you can fetch a value directly using an Xpath expression:

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

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

Output:

string(6) "level2"

The Xpath expression

All level element nodes in root :
/root/level

That have a specific name attribute:
/root/level[@name="level2"]

The value you like to fetch ( name attribute for validation):
/root/level[@name="level2"]/@name

Cast into a string, if node was found the result will be an empty string:

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

Loop over nodes, use them as context

If you need to execute several expression for the node it might be better to fetch it separately and use foreach() . The second argument for DOMXpath::evaluate() is the context node.

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

Node list length

If you need to handle that no node was found you can check the DOMNodeList::$length property.

$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() expression

You can validate that here are elements before with a count() expression, too.

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

Output:

float(1)

Boolean result

It is possible to make that a condition in Xpath and return the boolean value.

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

Output:

bool(true)

DOMXPath::query 's second parameter is the context node. Just pass the DOMNode instance you have previously "found" and your query runs "relative" to that node. Eg

<?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;
}

But unless you have to perform multiple separate (possible complex) subsequent queries, this is most likely not necessary

<?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;
}

has the same output using just one query.

This should work:

$dom = new DOMDocument;
$dom->loadXML($xml);
$levels = $dom->getElementsByTagName('level');

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

I personally prefer the DOMNodeList class for parsing XML.

Using querypath for parsing XML/HTML makes this all super easy.

$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() ; //
}

Excellent beginner tutorial for Querypath

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM