XPath in SimpleXML

SimpleXML as it name imply, is a very simple API to traverse XML implemented specially in PHP language. It is very similar to the XPath, but since it has more PHP friendly syntax PHP developers really like to use it.
As an Example for this XML:

<dwml>
  <data>
    <location>
      <location-key>point1</location-key>
      	<point latitude="37.39" longitude="-122.07"></point>
      </location>
  </data>
  .....
</dwml>

XPATH Query to take the latitude in more general way

/dwml/data/location/point/@latitude

Where as with simple XML it is just a familiar PHP statement,

$simplexml->data->location->point->attributes()->latitude

Anyway still you can use the xpath inside your simplexml code. You can execute xpath queries by calling xpath function from any SimpleXMLEelment. It will return an array of SimpleXMLElement that match your query. So for the above example your XPath query would be something  like this,

$simplexml= new SimpleXMLElement($xml);
$lats =  $simplexml->xpath('/dwml/data/location/point/@latitude');
echo $lats[0];

This simplicity allows you to choose between these two methods interchangeably as best fit per your application. Here are some cases that I think use of XPath is more easy.

Ability to use of XPath shorthand
Take the above example XML it self. If there is only one attribute named ‘latitude’ throughout the XML you can call that value by

//@latitude

If XML node name or attribute name contains characters like ‘-‘ which are not allowed in PHP for variable names
In the example if you want to access the value inside ‘location-key’ node using simplexml it would be like,

echo $simplexml->data->location->location-key;

This will not give you the expected result as PHP will try to think ‘location’ and ‘key’ as two taken in ‘location-key’. So this particular code can be replaced with the xpath function.

$keys =  $simplexml->xpath('/dwml/data/location/location-key');
echo $keys[0];

You want to iterate through node with a same name in an XML

If the nodes which we want to iterate is in organized positions in an XML (like the one in following) both approaches can be used with same easiness.

<root>
  <mynode>value1</mynode>
  <mynode>value2</mynode>
  <mynode>value3</mynode>
  <mynode>value4</mynode>
  <mynode>value5</mynode>
</root>

But how if the ‘mynode’ was in different locations in an XML like this,

<root>
  <anothernode>
     <mynode>value1</mynode>
  </anothernode>
  <anotheranothernode>
     <anotheranotheranothernode>
       <mynode>value2</mynode>
     </anotheranotheranothernode>
     <mynode>value3</mynode>
  </anotheranothernode>
  <mynode>value4</mynode>
</root>

You can iterate all the ‘mynode’ nodes with the following xpath query.

//mynode

Note that this case can be handled easily in DOM with the getElementsByName.

To use the power of XPath functions and Axes
You can use the XPath functions like last(), position() and even string manipulation functions like substring() in a XPath statement.
For an example in the above example, if you want only take the value of last ‘mynode’ just use this expression

//mynode[last()]

And you can use the power of axes in Xpath Queries. If you want to iterate all the ancestors from current node just use this query

'ancestor::*'

Access elements with different namespaces

<saleItems>
   <ns1:car xmlns:ns1="http:/toyota.xxx.com">$3000</ns1:car>
   <ns2:car xmlns:ns2="http:/suziki.rrr.com">$4000</ns2:car>
</saleItems>

You want to extract the cars from simpleXML. You can do this by following code.

$simplexml= new SimpleXMLElement($xml);
$ns1_childs = $simplexml->children("http:/toyota.xxx.com");
echo $ns1_childs->car;

$ns2_childs = $simplexml->children("http:/suziki.rrr.com");
echo $ns2_childs->car;

Every time you access a different namespace you have to call the children method with the namespace as an argument.

If you use XPath approach, you first register the namespces with an prefix and just use those prefix in your XPath queries.

$simplexml= new SimpleXMLElement($xml);

$simplexml->registerXPathNamespace("p1", "http:/toyota.xxx.com");
$simplexml->registerXPathNamespace("p2", "http:/suziki.rrr.com");

$toyota_cars = $simplexml->xpath('//p1:car');
$suziki_cars = $simplexml->xpath('//p2:car');

echo $toyota_cars[0];
echo $suziki_cars[0];

SimpleXML is simple and powerful in its native form. But whenever it is impossible or difficult to use you don’t need to go back for tedious DOM or manual string manipulation. You can use the xpath queries to get the work done within the simplexml environment itself.

This entry was posted in php, xml, xpath and tagged , , , . Bookmark the permalink.

20 Responses to XPath in SimpleXML

  1. Pims says:

    Excellent article. Well illustrated with useful examples, and well explained. Keep it up !

  2. Kevin says:

    What if a want to access a value by both the namespace and attribute. For instance the following xml:

    10000
    20000

    How do I access the value 20000 by searching based on centextRef and namespace “xxxxx”. Here is my code so far:

    $xml = simplexml_load_string($string);
    $xml2= $xml->children(‘xxxxx’);
    print $xml2->Inledning;

  3. dimuthu says:

    Hi Kevin,
    Can you retype the xml again using wildcard characters for ‘< ' and '>‘. (you have to use &lt in place of ‘< ' and &t for '>‘). Otherwise it is not correctly rendering.

    Thanks
    Dimuthu

  4. Kevin says:

    New try:

    My xml test code:

    &lt?xml version=”1.0″ encoding=”UTF-8″?&t
    &ltxbrli
    xmlns:se-gen-base=”xxxxx”
    &t
    &ltse-gen-base:Inledning contextRef=”DURcy”&t 10000&lt/se-gen-base:Inledning&t
    &ltse-gen-base:Inledning contextRef=”DURpy”&t 20000&lt/se-gen-base:Inledning&t

    &lt/xbrli&t

    Let’s say I want to access the value 20000. Here is my code so far but it doesn’t work when mixing both namespace and attribute.

    $xml = simplexml_load_string($string);

    foreach ($xml->children(‘xxxxx’)->Inledning as $test) {
    if ((string)$test[‘contextRef’] == ‘DURpy’) echo $test;
    }

  5. Kevin says:

    Got it now:

    function data($field,$context)
    {
    $xml = simplexml_load_file(‘test.xml’);

    foreach ($xml->children(‘http://www.xbrl.se/se/fr/gen-base/2007-05-25’)->$field as $test) {
    if ((string)$test->attributes()->contextRef == $context) echo $test;
    }
    }

  6. dimuthu says:

    Nice to hear you got it working 🙂

    Thanks
    Dimuthu

  7. vsp says:

    Does simplexml xpath support string functions described
    http://www.w3schools.com/Xpath/xpath_functions.asp#string

    I am trying to use them on attribute ‘class’
    but they do not seem to work

    for example
    myxml->xpath(“//*[matches(@class,’classnm’)]”)

    does not work, PHP complains

    Message: SimpleXMLElement::xpath() [simplexmlelement.xpath]: xmlXPathCompOpEval: function matches not found

  8. dimuthu says:

    Hi vsp,
    I think PHP still doesn’t have the full support for xpath 2.0 functions. Please try out ‘contains’ function instead of ‘matches’.

    myxml->xpath(”//*[contains(@class,’classnm’)]“)

    Thanks
    Dimuthu

  9. jonas says:

    Can anyone please inform how to turn the resulting array of an xpath query into a DomDocument for xslt processing?

  10. dimuthu says:

    If you want to do xslt processing and work with DOM, that best thing you can do is forget simplexml and rather use dom in all the places.
    You can use DOMXPath class to do xpath queries with DOM.

    Thanks
    Dimuthu

  11. Guillermo says:

    Why this doesn´t work?

    Con titulo

    Con atom

    $busqueda = “//album[@foto=’a2.jpg’]/titulo”;
    $descripcion = $xml->xpath($busqueda);

  12. dimuthu says:

    This worked!
    <?php
     
    $xml_str = <<<XML
    <a>
    <b>
    <album foto=“a2.jpg”>

    <titulo>found</titulo>
    </album>
    </b>
    </a>
    XML;
     
    $xml = new SimpleXMLElement($xml_str);

    $busqueda = “//album[@foto=’a2.jpg’]/titulo”;
    $description = $xml->xpath($busqueda);
     
     

    echo $description[0];
    ?>

  13. Luis says:

    Perfectly easy! thanks!

  14. Guillermo says:

    Thanks a lot. I appreciate your solution. I´ll try it.

    Is it working bacause of this call:
    $xml = new SimpleXMLElement($xml_str);

    instead of this one?:
    $xml = simplexml_load_file ($xml_str);

    Then i should load the text by include() and then convert it to SimpleXMLElement…

  15. Guillermo says:

    Ah! Now I understand. This was making the difference:

    echo $description[0];

    I was just using

    echo $description;

    Then results should always use $x[0] index, even if they are a unique element. Right?

  16. dimuthu says:

    Yea. It always returns an array.

  17. Joyce says:

    Great blog you got here…keep up the good work.

  18. boxoft says:

    Very good tutorial. Thank you very much.

  19. bba says:

    Thanks. this was a very good tutorial.
    God bless you

  20. djheru says:

    Thanks for the explanation of using namespaces with the xpath method

Leave a Reply

Your email address will not be published. Required fields are marked *