How to query a xml file using xpath (php) ?

1.8k views Asked by At

I am trying to query an XML file using XPath. But as return I get nothing. I think I formatted the query false.

XML

<subject id="Tom">
   <relation unit="ITSupport" role="ITSupporter" />
  </subject>

PHP

$xpath = new DOMXpath($doc);
            $role = 'ITSupporter';
           $elements = $xpath-> query("//subject/@id[../relation/@role='".$role."']");              
           foreach ($elements as $element) {    
              $name = $element -> nodeValue;
              $arr[$i] = $name;
              $i = $i + 1;
           }    

How can I get the id TOM? I want to save it to for example $var

3

There are 3 answers

6
ThW On BEST ANSWER

Building up the Xpath expression:

  • Fetch any subject element
    //subject
  • ... with a child element relation
    //subject[relation]
  • ... that has a role attribute with the given text
    //subject[relation/@role="ITSupporter"]
  • ... and get the @id attribute of subject
    //subject[relation/@role="ITSupporter"]/@id

Additionally the source could be cleaned up. PHP arrays can use the $array[] syntax to push new elements into them.

Put together:

$xml = <<<'XML'
<subject id="Tom">
  <relation unit="ITSupport" role="ITSupporter" />
</subject>
XML;

$role = 'ITSupporter';

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

$ids = [];
foreach ($xpath->evaluate("//subject[relation/@role='".$role."']/@id") as $idAttribute) {
  $ids[] = $idAttribute->value;
}
var_dump($ids);

Output:

array(1) { 
  [0]=> 
  string(3) "Tom" 
}

If you expect only a single result you can cast the it in Xpath:

$id = $xpath->evaluate(
  "string(//subject[relation/@role='".$role."']/@id)"
);
var_dump($id);

Output:

string(3) "Tom"

XML Namespaces

Looking at the example posted in the comment your XML uses the namespace http://cpee.org/ns/organisation/1.0 without a prefix. The XML parser will resolve it so you can read the nodes as {http://cpee.org/ns/organisation/1.0}subject. Here are 3 examples that all resolve to this:

  • <subject xmlns="http://cpee.org/ns/organisation/1.0"/>
  • <cpee:subject xmlns:cpee="http://cpee.org/ns/organisation/1.0"/>
  • <c:subject xmlns:c="http://cpee.org/ns/organisation/1.0"/>

The same has to happen for the Xpath expression. However Xpath does not have a default namespace. You need to register an use an prefix of your choosing. This allows the Xpath engine to resolve something like //org:subject to //{http://cpee.org/ns/organisation/1.0}subject.

The PHP does not need to change much:

$xml = <<<'XML'
<subject id="Tom" xmlns="http://cpee.org/ns/organisation/1.0">
  <relation unit="ITSupport" role="ITSupporter" />
</subject>
XML;

$role = 'ITSupporter';

$document = new DOMDocument();
$document->loadXML($xml);
$xpath = new DOMXpath($document);
// register a prefix for the namespace
$xpath->registerNamespace('org', 'http://cpee.org/ns/organisation/1.0');

$ids = [];
// address the elements using the registered prefix
$idAttributes = $xpath->evaluate("//org:subject[org:relation/@role='".$role."']/@id");
foreach ($idAttributes as $idAttribute) {
  $ids[] = $idAttribute->value;
}
var_dump($ids);
2
zx485 On

Try this XPath

//subject[relation/@role='".$role."']/@id

You were applying the predicate on the id attribute and not on the subject element.

0
Frank On

Getting element by id is the same as doing by $role contents. So, like the followings; $xpath->query("//*[@id='$id']")->item(0); In other words, @id should be in '[' bracket.