Forum Moderators: coopster

Message Too Old, No Replies

Convert array of file paths into UL list

         

techtheatre

5:28 am on Jan 26, 2011 (gmt 0)

10+ Year Member



I have a table in a database that contains a variety of file paths to pages of my website. Each path is listed only one time. I currently have a very long and convoluded series of queries and php to pull all these and rewrite the data into an unordered list (to create a menu for my website). It seems that there is probably a relatively simple looping approach that would work MUCH more efficiently, but i cannot even think of where to begin. Anyone have any ideas to help me get started? Here is a sample:

DB returns the following in results array:
about/contact/
about/contact/form/
about/history/
about/staff/
about/staff/bobjones/
about/staff/sallymae/
products/
products/gifts/
products/widgets/

and i want to create the following output:
<ul>
<li>about/
<ul>
<li>about/contact/
<ul>
<li>about/contact/form/</li>
</ul>
</li>
<li>about/history/</li>
<li>about/staff/
<ul>
<li>about/staff/bobjones/</li>
<li>about/staff/sallymae/</li>
</ul>
</li>
</ul>
</li>
<li>products/
<ul>
<li>products/gifts/</li>
<li>products/widgets/</li>
</ul>
</li>
</ul>

Any help you can give to mapping a file directory directly to a UL list will be very helpful. Thanks!

vincevincevince

11:47 am on Jan 26, 2011 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



I'm not sure I can think of a clever way to do it, however - I would suggest that usually when we've stored this kind of data it has been in a hierarchical table, that each row knows its parent, and we read it in loops starting with those which have no parent.

rocknbil

5:58 pm on Jan 26, 2011 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



Don't have time to look up the code (I know I have this somewhere, but might be in perl.) What I figured out is

- create a subroutine (function in PHP) to read a directory. Initially you point this at the "parent directory." You want this to be a function because you will call it recursively. Structure this function so that it a) sets the return string null on entry, so it doesn't concatenate previous data, and b) returns a string chunk that is a complete <ul>(list items)</ul>

- As you begin stepping through the directory, you do a test against -f and -d (file or directory.) if file, add to list as an li. Don't close the li yet until you read the next object.

- if the next object is a directory, call the function again to get that directory's contents - this of course will occur within the function itself. (For development, it's a Really Good Idea to create a global variable $count and keep track of iterations in case you corner yourself in an infinite loop. When $maxcount is exceeded, exit immediately. Trust me on this one, it's easy to do when calling a function from within itself. :-) )

- When the depth of directories is reached, it will close the previous <li> and move to the next object. When the directory is fully traversed, close with a <ul> and return.

Or - the smart thing to do - would be to look for an open source class that's already been built for this purpose. It's great to explore this as an exercise if you have the time, but I have no doubt there's probably several hundred sites out there that offer a class or script that does this already.

techtheatre

5:14 am on Jan 28, 2011 (gmt 0)

10+ Year Member



I have found some code that seems to be just about what i need...i am now working to get it customized to work for me and i will post that when it is working, but for now the original code is here:
[daniweb.com...]

techtheatre

7:23 am on Jan 30, 2011 (gmt 0)

10+ Year Member



So...i have run into a problem. It turns out that the script that i found creates improperly formatted UL lists. In a CORRECT situation, a sub-list is contained within the <li> of the parent element. In this scripting, the parent <li> is closed and then a <ul> block is inserted. The overall script is actually fairly elegant in the way that it keeps up with the levels and such, but i cannot wrap my head around it enough to figure out how to fix it. I have the whole thing in a function here:

function generateMainMenu()
{
//modified from post found here: [daniweb.com...]
global $db;

$MenuListOutput = '';
$PathsArray = array();

$sql = "SELECT PageUrlName FROM `table`";
$result = mysql_query($sql, $db) or die('MySQL error: ' . mysql_error());
while ($PageDataArray = mysql_fetch_array($result))
{
$PathsArray[] = rtrim($PageDataArray['PageUrlName'],"/"); //this function does not like paths to end in a slash, so remove trailing slash before saving to array
}

sort($PathsArray);// These need to be sorted.
$MenuListOutput .= '<ul id="nav">'."\n";//get things started off right
$directories=array ();
$topmark=0;
$submenu=0;
foreach ($PathsArray as $value) {
// break up each path into it's constituent directories
$limb=explode("/",$value);
for($i=0;$i<count($limb);$i++) {
if ($i+1==count($limb)){
// It's the 'Leaf' of the tree, so it needs a link
if ($topmark>$i){
// the previous path had more directories, therefore more Unordered Lists.
$MenuListOutput .= str_repeat("</ul>",$topmark-$i); // Close off the Unordered Lists
$MenuListOutput .= "\n";// For neatness
}
$MenuListOutput .= '<li><a href="/'.$value.'">'.$limb[$i]."</a></li>\n";// Print the Leaf link
$topmark=$i;// Establish the number of directories in this path
}else{
// It's a directory
if($directories[$i]!=$limb[$i]){
// If the directory is the same as the previous path we are not interested.
if ($topmark>$i){// the previous path had more directories, therefore more Unordered Lists.
$MenuListOutput .= str_repeat("</ul>",$topmark-$i);// Close off the Unordered Lists
$MenuListOutput .= "\n";// For neatness
}

// (next line replaced to avoid duplicate listing of each parent)
//$MenuListOutput .= "<li>".$limb[$i]."</li>\n<ul>\n";
$MenuListOutput .= "<ul>\n";
$submenu++;// Increment the dropdown.
$directories[$i]=$limb[$i];// Mark it so that if the next path's directory in a similar position is the same, it won't be processed.
}
}
}
}
$MenuListOutput .= str_repeat("</ul>",$topmark+1);// Close off the Unordered Lists

return $MenuListOutput."\n\n\n";
}



and it returns something like this:
<ul id="nav">
<li><a href="/about">about</a></li>
<ul>
<li><a href="/about/history">history</a></li>
<li><a href="/about/job-opportunities">job-opportunities</a></li>
<li><a href="/about/mission">mission</a></li>
<li><a href="/about/privacy-policy">privacy-policy</a></li>
</ul>
<li><a href="/giftcards">giftcards</a></li>
<li><a href="/locations">locations</a></li>
<ul>
<li><a href="/locations/main-office">main-office</a></li>
<li><a href="/locations/branch-office">branch-office</a></li>
</ul>
<li><a href="/packages">packages</a></li>
</ul>


Anyone have an idea of where i need to add in some additional logic and how i can accomplish this? THANKS!

techtheatre

3:35 am on Feb 2, 2011 (gmt 0)

10+ Year Member



anyone?

Wittner

10:01 am on Feb 2, 2011 (gmt 0)

10+ Year Member



You've shown us the result array from your DB for the categories but not how the tables are set up. Can you post an idea of how the tables are structred?

cheers,

Wittner

rocknbil

6:24 pm on Feb 2, 2011 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



It's not the DB, it's the way the script is reading directories.

No time to mod this script ATM, but looking at it I'd say - keep looking, find another one. :-)

techtheatre

3:30 pm on Feb 3, 2011 (gmt 0)

10+ Year Member



@wittner: The database is not the issue on this one...the query i am using is returning complete path from root for each row like this:
/about/
/about/history/
/about/job-opportunities/
/about/mission/
etc...

@rocknbil: your original post is very solid logic on how to write this from scratch...but i feel like with the current code i found i am SO CLOSE to it working that i should just continue with it. I simply cannot figure out where to move the <ul> open/close section to get it in the proper place. Will this really require major logic or is there not a simple approach?

@everyone: Is there not a straight-forward approach to processing directory trees in PHP...it seems like this would be one of those "basic core functions" that everyone needs and uses all the time.

techtheatre

8:15 pm on Feb 5, 2011 (gmt 0)

10+ Year Member



SOLUTION:

<?php

$paths = array(
'about/contact/' => 'Contact Us',
'about/contact/form/' => 'Contact Form',
'about/history/' => 'Our History',
'about/staff/' => 'Our Staff',
'about/staff/bobjones/' => 'Bob',
'about/staff/sallymae/' => 'Sally',
'products/' => 'All Products',
'products/gifts/' => 'Gift Ideas!',
'products/widgets/' => 'Widgets'
);

function build_tree($path_list) {
$path_tree = array();
foreach ($path_list as $path => $title) {
$list = explode('/', trim($path, '/'));
$last_dir = &$path_tree;
foreach ($list as $dir) {
$last_dir =& $last_dir[$dir];
}
$last_dir['__title'] = $title;
}
return $path_tree;
}

function build_list($tree, $prefix = '') {
$ul = '';
foreach ($tree as $key => $value) {
$li = '';
if (is_array($value)) {
if (array_key_exists('__title', $value)) {
$li .= sprintf('%s%s/ <a href="/%s%s/">%s</a>', $prefix, $key, $prefix, $key, $value['__title']);
} else {
$li .= "$prefix$key/";
}
$li .= build_list($value, "$prefix$key/");
$ul .= strlen($li) ? sprintf('<li>%s</li>', $li) : '';
}
}
return strlen($ul) ? sprintf('<ul>%s</ul>', $ul) : '';
}

$tree = build_tree($paths);
$list = build_list($tree);
echo $list;

?>