PHP RecursiveDirectoryIterator(...) refuses to validate

For the past couple of days I have been searching for a script to show a directory tree for use in an photo album, etc. I have yet to find one that validates without HTML errors.

I stumbled across the above source code which seems ideal and have a demo here:

Source code and W3.org validation links are supplied.

I would be grateful if someone could eliminate the W3 Validation Errors.

A word of warning, although the source code is only 25 lines a recursive function is called and makes tracing difficult.

Perhaps this could be a competition :slight_smile:


$path = '/downloads/sp-b';
$path = $_SERVER['DOCUMENT_ROOT'] .$path;

function isDocument($filePath)
{
    $allowedTypes = array('pdf', 'djvu', 'doc', 'rtf');
    $extension = substr(strtolower($filePath), (strrpos($filePath, '.')+1));

    return in_array($extension, $allowedTypes);
}

function recurse($it)
{
   echo '<ul>';
      for( ; $it->valid(); $it->next()):
         if($it->isDir() && !$it->isDot()):
           echo sprintf('<li class="d">%s</li>', $it->getFileName());
           if($it->hasChildren()):
              $bleh = $it->getChildren();
              echo '<ul>' . recurse($bleh) . "</ul>\
";
           endif;

         elseif($it->isFile() && isDocument($it->getPathName())):
                # echo '<li class="f">'. $it->current() . ' (' . $it->getSize(). " Bytes)</li>\
";
               echo 'NOT CURRENTLY USED';
         endif;
      endfor;
    echo '</ul>';
}
recurse(new RecursiveDirectoryIterator( $path ));

This validates, but I had to change how it was implemented. Instead of echoing out the content as it runs it, it builds it into a string and you need to echo it at the call.

$path = '/downloads/sp-b';
$path = $_SERVER['DOCUMENT_ROOT'] .$path;

function isDocument($filePath)
{
	$allowedTypes = array('pdf', 'djvu', 'doc', 'rtf');
	$extension = substr(strtolower($filePath), (strrpos($filePath, '.')+1));

	return in_array($extension, $allowedTypes);
}

function recurse($it, $content = '')
{
	for( ; $it->valid(); $it->next()):
		if($it->isDir() && !$it->isDot()):
			if (strlen(trim($content)) === 0)
				$content = '<ul>';
			$content .= sprintf('<li class="d">%s', $it->getFileName());

			if($it->hasChildren()):
				$bleh = $it->getChildren();
				$content .= recurse($bleh);
			endif;
			$content .= '</li>';
		elseif($it->isFile() && isDocument($it->getPathName())):
			#if (strlen(trim($content)) === 0)
			#	$content = '<ul>';
			#$content .= '<li class="f">'. $it->current() . ' (' . $it->getSize(). " Bytes)</li>";
		endif;
	endfor;

	if (strlen(trim($content)) !== 0)
		$content .= '</ul>';
	return $content;
}

echo recurse(new RecursiveDirectoryIterator( $path ));

Many thanks for the rather neat solution.

I have uploaded your version and amended the menu:

Any other solutions available?

One quick adjustment, as when I took a second look, I found a redundant statement.

				$recursiveContent = recurse($bleh);
				if (strlen(trim($recursiveContent)) !== 0)
					$content .= $recursiveContent;

Can be simplified to

				$content .= recurse($bleh);

This is only possible because of the changes I made on the recurse implementation (so it only writes output, if there is something to write). I’ve also updated my above post to reflect these changes.

Many thanks once again,

Updated revisions.

Any other solutions available?

I changed things to shorten the code a bit.
I have never gotten along with endif, prefer {}.
I am assuming there will always be content so not checking strlen. worst case scenario, <ul></ul>
I left everything in and commented out everything that I would remove / change.


$path = './';
function isDocument($filePath)
{
    $allowedTypes = array('php', 'djvu', 'doc', 'rtf');
    $extension = substr(strtolower($filePath), (strrpos($filePath, '.')+1));

    return in_array($extension, $allowedTypes);
}

function recurse($it, $content = '')
{

  $content = '<ul>';

    for( ; $it->valid(); $it->next()){
        if($it->isDir() && !$it->isDot()){
//            if (strlen(trim($content)) === 0){
//				}
            $content .= sprintf('<li class="d">%s', $it->getFileName());

            if($it->hasChildren()){
//                $bleh = $it->getChildren();
                $content .= recurse($it->getChildren());
            }
            $content .= '</li>';
//        elseif($it->isFile() && isDocument($it->getPathName())): // can be moved to blow the }
            #if (strlen(trim($content)) === 0)
            #    $content = '<ul>';
            #$content .= '<li class="f">'. $it->current() . ' (' . $it->getSize(). " Bytes)</li>";
        }
    }

//    if (strlen(trim($content)) !== 0){
        $content .= '</ul>';
//		}
    return $content;
}

echo recurse(new RecursiveDirectoryIterator( $path ));



Unfortunately, that may produce invalid markup, as you’ll end up with <ul></ul> due to where you moved the <ul> statements to (hence the reason for the checks) :).

I’m also not a huge fan of the if: endif; but I was just making the existing code produce valid XHTML

As an FYI, I verified that empty <ul></ul> does make it invalid markup. So you actually do need to have those strlen (or a similar check) checks to ensure valid markup is applied.

Are we interested in separating the concerns a bit?

tree.php

<?php

$path = '/downloads/sp-b';
$path = $_SERVER['DOCUMENT_ROOT'] .$path;

function isDocument($filePath)
{
    $allowedTypes = array('pdf', 'djvu', 'doc', 'rtf');
    $extension = substr(strtolower($filePath), (strrpos($filePath, '.')+1));

    return in_array($extension, $allowedTypes);
}

// "recurse" now echos nothing
// it returns an associative array where each key is a file name
// and each value is an array of children, or null if there were no children
function recurse($it)
{
    $contents = array();

    for ( ; $it->valid(); $it->next()) {
        if ($it->isDir() && !$it->isDot()) {
            if ($it->hasChildren()) {
                $children = recurse($it->getChildren());
            }
            $contents[$it->getFileName()] = $children;
        }
        elseif ($it->isFile() && isDocument($it->getPathName())) {
            // 'NOT CURRENTLY USED';
        }
    }

    return $contents;
}

// "render" takes a template filename and an associative array of parameters
// that should be available to that template, and returns the rendered output
function render($template, $params)
{
    extract($params);

    ob_start();
    require $template;
    $content = ob_get_clean();

    return $content;
}

echo render('recursiveDirectory.html.php', array(
    'contents' => recurse(new RecursiveDirectoryIterator( $path )))
);

recursiveDirectory.html.php

<ul>
    <?php foreach ($contents as $name => $children): ?>
        <li>
            <?php echo htmlspecialchars($name) ?>

            <?php if ($children): ?>
                <?php echo render('recursiveDirectory.html.php', array('contents' => $children)) ?>
            <?php endif ?>
        </li>
    <?php endforeach ?>
</ul>

Many thanks Jeff, your version has been uploaded:

Any other solutions available?