3

I am knee deep in foreach purgatory right now trying to come up with a way to traverse this XML file (actual XML text below) with PHP(following the XML file content.) What I am trying to do is the following:

  1. Get all folder element names
  2. If the folder element has yes as a subfolder attribute, then move a level down and grab that folder element's name
  3. If not move on to the next folder element

gallerylist.xml:

<?xml version="1.0" encoding="ISO-8859-1"?>
<gallerylisting exists="yes">
<folder subfolder="yes">
Events
   <folder subfolder="yes">
   Beach_Clean_2010
        <folder subfolder="no">
        Onna_Village
        </folder>
            <folder subfolder="no">
            Sunabe_Sea_Wall
        </folder>
        </folder>
  </folder>
  <folder subfolder="no">
  Food_And_Drink
  </folder>
  <folder subfolder="no">
  Inside
  </folder>
  <folder subfolder="no">
  Location
  </folder>
  <folder subfolder="no">
  NightLife
  </folder>
</gallerylisting>

gallerylisting.php

<?php
$xmlref = simplexml_load_file("gallerylisting.xml");
foreach($xmlref->children() as $child) {
    foreach($child->attributes() as $attr => $attrVal) {
        print $child;
        if($attrVal == "yes") {
            foreach($child->children() as $child) {
                echo $child;
                foreach($child->attributes() as $attr => $attrVal) {
                    if($attrVal == "yes") {
                        foreach($child->children() as $child) {
                            echo $child;
                        }
                    }                   
                }
            }
        }
    }
}

I am...counting...5 foreach loops deep into this PHP script and I do not like it at all, plus if my folders had another subfolder, I would have to add this same

$if(attrVal=="yes")...etc.

in again and well...no! Is there anyway at all that I can avoid this. I'm new to PHP, and especially PHP and XML.

Thanks for any help.

SomeShinyObject
  • 7,581
  • 6
  • 39
  • 59
  • As you tagged this question with efficiency i will bring up this point. You don't need a "subfolder" attribute in your xml. The PHP-Library will help you find out, whether the node has children or not. If they have, use @Matt's function recursively. An you should not mix "text" elements (e.g. your folder names) with nodes. That looks a little nasty. Try a "name" attribute instead. – Frank Jul 26 '11 at 23:40
  • Noted for future reference! Thanks for being frank! (badump ch!) – SomeShinyObject Jul 27 '11 at 00:05

3 Answers3

5

Recursion could be beneficial to you here.

<?php

function display_entities( $xml )
{
    foreach($xml->children() as $child) {
        foreach($child->attributes() as $attr => $attrVal) {
            print $child;
            if($attrVal == "yes") {
              display_entities( $child->children() );
            }
        }
    }
}

$xmlref = simplexml_load_file("gallerylisting.xml");

display_entities($xmlref->children());
Mr Griever
  • 4,014
  • 3
  • 23
  • 41
  • I'll have to try your deal and this xpath Wrikken is talking about below. I'll give my answer then. This seems like something I would do in Javascript though so yea it's far out. – SomeShinyObject Jul 26 '11 at 23:24
  • Yea! Yea! This is it bro, this is totally it. I made some minor changes though in that in the display_entities parameters when actually calling it, I only used $xmlref. That got me everything! You're wicked broseph. – SomeShinyObject Jul 26 '11 at 23:29
  • 1
    You should use a flag and move that _if_ out of the inner _foreach_ loop, otherwise the inner display_entitties might get called multiple times, while only once for each child is really needed. – faham Feb 27 '14 at 09:09
2

Use XPath:

If subfolder=no is unreliable in leaves (i.e. 'no' might not always be set):

foreach($xmlref->xpath('//folder[not(@subfolder) or @subfolder!="yes"]') as $node){

If it is:

foreach($xmlref->xpath('//folder[@subfolder="no"]') as $node){

Or even if you want to check for folders without folder children altogether, disregarding the attribute:

foreach($xmlref->xpath('//folder[not(folder)]') as $node){
Wrikken
  • 69,272
  • 8
  • 97
  • 136
0

You could try use xpath instead of your nested loops method..

Running the query

gallerylisting//folder[@subfolder="yes"]/text() 

on your xml doc given above using this xpath tester gives the results Events and Beach_Clean_2010, which is what I think you want. The query will find all folder elements under the root node gallerylisting, and if they have a subfolder attribute equal to "yes", the text in the node will be returned.

So, in PHP, we have

<?php
    $xmldoc = new DOMDocument();
    $xmldoc->load('gallerylisting.xml');

    $xpathvar = new Domxpath($xmldoc);

    $queryResult = $xpathvar->query('gallerylisting//folder[@subfolder="yes"]/text()');
    foreach($queryResult as $result){
            echo $result->textContent;
    }
?>

Im not a PHP guy, so I am trusting the code in this StackOverflow question works. One thing to be aware of is that using the //name in xpath means find all nodes descended from the current position that match the name. This can be very slow on large documents.

It seems there is multiple ways to process xpath with PHP. An example from this page does the query like so:

   $result = $xml->xpath("gallerylisting//folder[@subfolder="yes"]/text()");

   print_r($result);
?> 
Community
  • 1
  • 1
aj.esler
  • 921
  • 1
  • 8
  • 18