2

There's lots of these questions, so forgive me. I've read them all.

I have the following XML document using the namespace http://www.columbasystems.com/cpng/xmlviewer/cal/1-0/ : http://events.manchester.ac.uk/f3vf/calendar/tag:manchester_museum/view:list/p:q_details/calml.xml

I'm attempting to parse this document using SimpleXML. The example code below is an attempt at accessing the value of the title node "Discovery Center" from the below.

<ns:calendar xmlns:ns="http://www.columbasystems.com/cpng/xmlviewer/cal/1-0/">
    <ns:listView>
    <ns:day date="2015-07-08" weekDay="Wed">
    <ns:event>
    <ns:id xmlns:even="http://www.columbasystems.com/customers/uom/gpp/eventid/" query="{http://www.columbasystems.com/customers/uom/gpp/eventid/}b9v-ib270yqf-nmn54k">even:b9v-ib270yqf-nmn54k</ns:id>
    <ns:title>Discovery Centre</ns:title>
    ...
</ns:event>
</ns:day>
</ns:listView>
</ns:calendar>

PHP :

$feed_uri = 'http://events.manchester.ac.uk/f3vf/calendar/tag:manchester_museum/view:list/p:q_details/calml.xml';
$xml = simplexml_load_file($feed_uri);
$xml->registerXPathNamespace("ns", "http://www.columbasystems.com/cpng/xmlviewer/cal/1-0/");

foreach($xml->xpath('//ns:calendar/ns:listView/ns:day') as $day) {
    $events = $day->xpath('//ns:event');
    foreach($events as $event) {
        var_export($event->xpath('//ns:title'));
    }
}

OUTPUTS several empty arrays:

array ( 0 => SimpleXMLElement::__set_state(array( )),

I think i'm using xpath wrong, how do i get to the values of nodes like these?

dg85
  • 490
  • 2
  • 7
  • 21
  • What happens when your code runs? – GalacticCowboy Jul 08 '15 at 16:42
  • It dumps an empty array 47 times. Nested within the first instance of there are 5 individual event elements so the nested foreach is clearly not working as i'd expect. Output was : array ( 0 => SimpleXMLElement::__set_state(array( )), and so on. edit: updated question to include output sample – dg85 Jul 08 '15 at 16:46
  • @digitalpencil look update in my answer – splash58 Jul 09 '15 at 10:18

2 Answers2

4

Your output is not an empty array. An empty array looks like this:

array()

But you have this:

array ( 0 => SimpleXMLElement::__set_state(array( )),

So, clearly the XPath is working, and giving you a list (array) of results (SimpleXMLElement objects).

The problem is that var_export isn't very good at inspecting SimpleXMLElement objects, so you can't see what result you actually got.

To get at the text content of a node, you have to cast it to a string - explicitly with (string)$node, or implicitly with something like echo. So the below will work:

foreach($xml->xpath('//ns:calendar/ns:listView/ns:day') as $day) {
    $events = $day->xpath('//ns:event');
    foreach($events as $event) {
        foreach ($event->xpath('//ns:title') as $title ) {
            echo $title;
        }
    }
}

However, you do have a small bug in your XPath expressions (irrelevant of if you used SimpleXML or any other API): the // prefix always starts at the root of the document, not the element being used as context. To search for "any depth within the current element" you need .//, e.g. $day->xpath('.//ns:event').

That said, you don't actually need anything as fancy as XPath here at all, because it's not that deep a structure. So you can just use SimpleXML's normal access methods, as long as you first select the right namespace using the ->children() method:

$cal_items = $xml->children("http://www.columbasystems.com/cpng/xmlviewer/cal/1-0/");

foreach($cal_items->listView->day as $day) {
    foreach($day->event as $event) {
        echo $event->title;
    }
}

Note that your XML contains attributes without namespace prefixes like <ns:day date="2015-07-09" weekDay="Thu">; somewhat unintuitively, these are officially in no namespace at all, so you have to switch back to the null namespace to access them:

echo $day->attributes(null)['date'];
Community
  • 1
  • 1
IMSoP
  • 89,526
  • 13
  • 117
  • 169
2

I placed xml in string, change back to file. I hope, other is clear

$str = '<ns:calendar xmlns:ns="http://www.columbasystems.com/cpng/xmlviewer/cal/1-0/">
    <ns:listView>
    <ns:day date="2015-07-08" weekDay="Wed">
    <ns:event>
    <ns:id xmlns:even="http://www.columbasystems.com/customers/uom/gpp/eventid/" query="{http://www.columbasystems.com/customers/uom/gpp/eventid/}b9v-ib270yqf-nmn54k">even:b9v-ib270yqf-nmn54k</ns:id>
    <ns:title>Discovery Centre</ns:title>
</ns:event>
</ns:day>
</ns:listView>
</ns:calendar>';

$xml = simplexml_load_string($str);
$xml->registerXPathNamespace("ns", "http://www.columbasystems.com/cpng/xmlviewer/cal/1-0/");

foreach($xml->xpath('//ns:calendar/ns:listView/ns:day') as $day) {
    echo $day['date'] . ' ';
    $events = $day->xpath('.//ns:event');
    foreach($events as $event) {
        echo $event->xpath('.//ns:title/text()')[0];

    }
}

result

2015-07-08 Discovery Centre

UPDATE You can use prefix insead full url. And take in mind how get attrbute value in this case

$cal_items = $xml->children("ns",true);
foreach($cal_items->listView->day as $day) {
    echo $day->attributes()['date'] . ' ' ;
    foreach($day->event as $event) {
        echo $event->title;
    }
}
splash58
  • 26,043
  • 3
  • 22
  • 34
  • To be clear, the only thing actually making a difference here is using `echo` rather than `var_export`. The `/text()` on the end of the XPath expression is actually ignored by SimpleXML, because it has no object type for a text node; the text content is extracted by using a string cast (`(string)$simplexml_element`) which `echo` does implicitly. – IMSoP Jul 08 '15 at 19:27
  • @IMSoP I agree about `text` - it is my usual problem, coming from xpath testers :) But, about xpath and direct adreesing, i don't belive OP about correct sorce data :) You right if it as in question. But OP use xpath on all levels – splash58 Jul 08 '15 at 19:50
  • Not sure what you mean by "correct source data"; the URL is in the question, and it looks just as the example implies. I think the OP is using xpath because they don't know they don't need to. – IMSoP Jul 08 '15 at 20:14
  • Your update is incorrect. The `ns` in `->children("ns", true)` has nothing to do with registering the XPath namespace, it just takes whatever prefix happened to be assigned in the source XML. You're right on needing `->attributes()` though (to switch back to the empty-named namespace). – IMSoP Jul 09 '15 at 10:25
  • I tested. you are right. I thought that prefix must be from `registerXPathNamespace` – splash58 Jul 09 '15 at 10:29
  • @splash58 Nope, as the method name says, that's just for XPath (because XPath has no syntax for referencing namespaces by URI). And for that reason, it's generally good practice to use the namespace URI, because the prefix might be auto-generated and change without notice. – IMSoP Jul 10 '15 at 14:55
  • This helped a lot. Good example. – Millar248 Oct 11 '18 at 19:03