tags:

views:

2097

answers:

5

How would I get the last item (or any specific item for that matter) in a simplexml object? Assume you don't know how many nodes there will be.

ex.

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="/xsl.xml"?>
<obj 
  href="http://xml.foo.com/" 
  display="com.foo.bar" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns="http://obix.org/ns/schema/1.0" 
>
 <list name="data" of="HistoryRecord">
  <obj>
   <abstime name="timestamp" val="1876-11-10T00:00:00-08:00"></abstime>
   <int name="energy_in_kwh" val="1234"></int>
   <int name="energy_out_kwh" val="123456"></int>
  </obj>
  <obj>
   <abstime name="timestamp" val="1876-11-10T00:15:00-08:00"></abstime>
   <int name="energy_in_kwh" val="1335"></int>
   <int name="energy_out_kwh" val="443321"></int>
  </obj>
 </list>
 <int name="count" val="2"></int>
</obj>

And I want to grab the last <obj></obj> chunk (or even just part of it).

A: 

I think SimpleXML loads the whole XML anyway and (if I remember correctly) you can use the SimpleXML nodes as if they were arrays, so you could just use array functions to get the last node. It's a while since I used PHP but you should be able to get the length and then get the item at length-1...

Edit: You can of course use XPath too, I thought I should mention that too but I wasn't sure if last() worked in the SimpleXML XPath implementation.

I'm not sure wich is fastest, using array indexes or XPath, I would guess that array indexes were faster but you should try both in a loop a few thousand times getting the time before and after the loop to check.

But as allways in CS: what you choose depends on many things.

Is it time critical or used very often: then find the fastest solution.

If you will need more complicated queries and speed isn't an issue: then use whatever is easiest to implement and gives the power you need (XPath is good for complex tree navigation, array indexing is good for quick random access in list-type datastructures; XML can be used for both.)

Stein G. Strindhaug
+1  A: 

Quickest way to access nodes in XML, for a programmer, is XPath. Take a look at the xpath methods and xpath itself.

digitala
A: 

You can start with this.. You'll be able to dig it out.. I don't like that they used the same tag name at 2 levels.. I try to avoid duplicate tag names.

<?php
$s = simplexml_load_file('in.xml');
$s->registerXPathNamespace('obix', 'http://obix.org/ns/schema/1.0');
$items = $s->xpath('//obix:list');
?>
DreamWerx
+3  A: 

Use XPath's last() function, which solves this very problem:

<?php 
$xml = simplexml_load_file('HistoryRecord.xml'); 
$xml->registerXPathNamespace('o', 'http://obix.org/ns/schema/1.0');

$xpath = "/o:obj/o:list/o:obj[position() = last()]/o:int[@name = 'energy_in_kwh']";
$last_kwh = $xml->xpath($xpath); 
?>

Here it looks for the last inner <obj>, and therein for the <int> with the name of "energy_in_kwh".

Watch out for the namespace registration. (All your elements are part of the "http://obix.org/ns/schema/1.0" namespace, the XPath query must reflect that.

Tomalak
Have you tested this code? I just ran it, and it didn't work. $last_kwh return false;
DreamWerx
Probably because the namespace registration is missing. I did not notice it because it is off screen in the question.
Tomalak
Worked for me. Thx!
gaoshan88
Excellent, your edited version works perf. +1
DreamWerx
+3  A: 

There is a XPath expression that'll do exactly what you want:

$xml='<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="/xsl.xml"?>
<obj href="http://xml.foo.com/" display="com.foo.bar" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://obix.org/ns/schema/1.0" >
 <list name="data" of="HistoryRecord">
  <obj>
   <abstime name="timestamp" val="1876-11-10T00:00:00-08:00"></abstime>
   <int name="energy_in_kwh" val="1234"></int>
   <int name="energy_out_kwh" val="123456"></int>
  </obj>
  <obj>
   <abstime name="timestamp" val="1876-11-10T00:15:00-08:00"></abstime>
   <int name="energy_in_kwh" val="1335"></int>
   <int name="energy_out_kwh" val="443321"></int>
  </obj>
 </list>
 <int name="count" val="2"></int>
</obj>';
$x=simplexml_load_string($xml);
$x->registerXPathNamespace('obix', 'http://obix.org/ns/schema/1.0');
$objects=$x->xpath('/obix:obj/obix:list/obix:obj[last()]');
print_r($objects);

For example /bookstore/book[last()] will select the last book element that is the child of the bookstore element.

Stefan Gehrig
Have you tested this code? $objects returns false.
DreamWerx
Forgot the namespace-prefix in the XPath expression... Sorry!
Stefan Gehrig