7

Here's my php script:

<?php

$path = $_SERVER['DOCUMENT_ROOT'].'/test';

$objects = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path), RecursiveIteratorIterator::SELF_FIRST);

foreach($objects as $name => $object){  
    echo  $objects->getDepth() . " " . $object->getFilename() . "<br/>";
    }

?>

Here is the directory/file tree the script is iterating over. (It's in a trivial root directory called $_SERVER['DOCUMENT_ROOT'].'/test'):

/food
/food/drinks
/food/drinks/water.html
/food/drinks/milk.html
/food/drinks/soda.html
/food/entrees
/food/entrees/hot
/food/entrees/hot/hamburger.html
/food/entrees/hot/pizza.html
/food/entrees/cold
/food/entrees/cold/icecream.html
/food/entrees/cold/salad.html
/cosmetics
/cosmetics/perfume
/cosmetics/perfume/chic.html
/cosmetics/perfume/polo.html
/cosmetics/perfume/lust.html
/cosmetics/lipstick
/cosmetics/lipstick/colors
/cosmetics/lipstick/colors/red.html
/cosmetics/lipstick/colors/pink.html
/cosmetics/lipstick/colors/purple.html

This is what the script outputs:

0 food
1 drinks
2 milk.html
2 water.html
2 soda.html
1 info.php
1 entrees
2 hot
3 pizza.html
3 hamburger.html
2 cold
3 ice_cream.html
3 salad.html
0 cosmetics
1 lipstick
2 colors
3 pink.html
3 red.html
3 purple.html
1 perfume
2 polo.html
2 lust.html
2 chic.html
0 error_log
0 test.php

Ignore the $objects->getDepth() integer; it's just for reference

Question: How can I modify my script to output nested unordered lists instead, like this:

<ul>
  <li>food</li>
    <ul>
      <li>drinks</li>
        <ul>
          <li>water.html</li>
          <li>milk.html</li>
          <li>soda.html</li>
        </ul>
      <li>entrees</li>
        <ul>
          <li>hot</li>
            <ul>
              <li>hamburger.html</li>
              <li>pizza.html</li>
            </ul>
          <li>cold</li>
            <ul>
              <li>icecream.html</li>
              <li>salad.html</li>
            </ul>      
        </ul>
    </ul>
  <li>cosmetics</li>
    <ul>
      <li>perfume</li>
        <ul>
          <li>chic.html</li>
          <li>polo.html</li>
          <li>lust.html</li>
        </ul>
      <li>lipstick</li>
        <ul>
          <li>colors</li>
            <ul>

Thanks!

2 Answers 2

17

Here is one using DomDocument

The basic idea is the contents of each directory is represented by a <ul> and each element in the directory by a <li>
If element is a non-empty directory it will contain a <ul> to represent its contens and so on.

$path = $_SERVER['DOCUMENT_ROOT'].'/test';
$objects = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path), RecursiveIteratorIterator::SELF_FIRST);
$dom = new DomDocument("1.0");
$list = $dom->createElement("ul");
$dom->appendChild($list);
$node = $list;
$depth = 0;
foreach($objects as $name => $object){  
    if ($objects->getDepth() == $depth){
//the depth hasnt changed so just add another li
        $li = $dom->createElement('li', $object->getFilename());
        $node->appendChild($li);
    }
    elseif ($objects->getDepth() > $depth){
//the depth increased, the last li is a non-empty folder 
        $li = $node->lastChild;
        $ul = $dom->createElement('ul');
        $li->appendChild($ul);
        $ul->appendChild($dom->createElement('li', $object->getFilename()));
        $node = $ul;
    }
    else{
//the depth decreased, going up $difference directories
        $difference = $depth - $objects->getDepth();
        for ($i = 0; $i < $difference; $difference--){
            $node = $node->parentNode->parentNode;
        }
        $li = $dom->createElement('li', $object->getFilename());
        $node->appendChild($li);
    }
    $depth = $objects->getDepth();
}
echo $dom->saveHtml();

The out put will be along the lines of

<ul>
    <li>dir1</li>
    <li>dir2
        <ul>
            <li>in dir2</li>
        <ul>
    <li>file in root</li>
<ul> 
Sign up to request clarification or add additional context in comments.

1 Comment

Yes! This does it perfectly! Thanks
1

Store the depth in a variable and check if the variable has been increased or decreased each loop?

If the depth has been increased, you add another <ul> tag and if it has been decreased you add a closing </ul> tag.

Each loop would always output the <li> tags to wrap the filename.

e.g.

$depth = 0;
foreach ($objects as $name => $object) {
    $depth2 = $objects->getDepth();
    if($depth2 > $depth) { echo '<ul>'; }
    echo "<li>" . $object->getFilename() . "</li>";
    if($depth2 < $depth) { echo '</ul>'; }
    $depth = $depth2;
}

That would need some additional testing and modification to suit your existing code, but hopefully it gives you some ideas.

3 Comments

this needs to put multiple </ul> tags if the last $object was a depth 3 and a next object is a depth 1, for example:
E.g., if the last $object was a depth 3 (like salad.html) and the next $object is a depth 0 (like "cosmetics), then we have to echo </ul></ul></ul> before the <li>. I.e., the higher depth ULs have to be fully closed out.
@user743094: You just have to replace the if with a loop. while($depth2 < $depth) { echo '</ul>'; $depth--; or something to that effect.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.