将路径数组转换为UL列表

时间:2011-02-04 22:45:42

标签: php recursion path tree

我在数据库中有一个表,其中包含我网站页面的各种路径。每个路径仅列出一次。我目前有一个非常漫长而复杂的查询系列和PHP来提取所有这些并将数据重写为无序列表(为我的网站创建菜单)。似乎可能有一个相对简单的循环方法可以更有效地工作,但我似乎无法得到任何工作。我发现TONS的PHP脚本从文件树创建UL列表,但是它们都不起作用或者无法处理我的查询结果的固有非递归性质(有些需要我的路径的多维数组,其中没问题,除非我创造那些麻烦。我找到了一个非常接近的脚本,但它通过在<ul>部分之外放置子列表来错误地格式化<li>部分(我将在下面解释)

以下是一个示例:

DB在结果数组中返回以下内容:

about/contact/
about/contact/form/
about/history/
about/staff/
about/staff/bobjones/
about/staff/sallymae/
products/
products/gifts/
products/widgets/

我想创建以下输出:

<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>

所以我非常接近这里找到的脚本:http://www.daniweb.com/forums/thread285916.html但我遇到了一个问题。事实证明,我发现的脚本会创建格式不正确的UL列表。在CORRECT情况下,子列表包含在父元素的<li>内。在此脚本中,父<li>已关闭,然后插入<ul>块。整个脚本实际上是相当优雅的,它保持水平等等,但我无法绕过它足以弄清楚如何解决它。我在这里有一个函数:

function generateMainMenu()
{
  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";
}

并返回如下内容:

<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>

任何人都知道我需要在一些额外的逻辑中添加哪些内容以及如何实现这一目标?有关更好方法的其他想法吗?看起来这是一个常见的问题,有一个简单/标准的方法来处理这样的事情。也许如果我能弄清楚如何从我的路径创建一个多维数组,那么可以迭代这些以使其工作?


编辑:更复杂: - (

我尝试了卡萨布兰卡的反应并且它完美地工作......除了我后来意识到现在我有一个后续工作让事情变得更加困难。为了显示页面的“名称”,我还需要在数组中包含该信息,因此路径可能更适合作为数组键和值中的名称。关于改变的任何想法都是这样的:

$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"
);

然后在buildUL函数中使用类似的行:

echo '<a href="'.$prefix.$key.'/">'.$paths[$prefix.$key].'</a>';

2 个答案:

答案 0 :(得分:13)

修改

更改为迎合更新的问题。

我正在使用__title的数组索引来保存页面标题。只要您的树中没有名为__title的目录,这应该没问题。您可以随意将此哨兵值更改为您想要的任何值。

我也更改了它,因此列表构建函数返回一个字符串,以便您可以存储该值以便稍后在页面中使用。 (您当然可以echo build_list(build_tree($paths))直接输出列表。

<?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 .= "$prefix$key/ <a href=\"/$prefix$key/\">${value['__title']}</a>";
            } else {
                $li .= "$prefix$key/";
            }
            $li .= build_list($value, "$prefix$key/");
            $ul .= strlen($li) ? "<li>$li</li>" : '';
        }
    }
    return strlen($ul) ? "<ul>$ul</ul>" : '';
}

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

?>

答案 1 :(得分:5)

确实,多维度会有所帮助。您可以通过将每个路径拆分为组件并使用它们索引到数组中来构建一个。假设$paths是您的初始数组,下面的代码将构建一个多维数组$array,其中的键对应于路径组件:

$array = array();
foreach ($paths as $path) {
  $path = trim($path, '/');
  $list = explode('/', $path);
  $n = count($list);

  $arrayRef = &$array; // start from the root
  for ($i = 0; $i < $n; $i++) {
    $key = $list[$i];
    $arrayRef = &$arrayRef[$key]; // index into the next level
  }
}

然后,您可以使用递归函数迭代此数组,您可以使用该函数自然地构建递归UL列表,如示例所示。在每次递归调用中,$array是当前正在处理的整个数组的子数组,$prefix是从根到当前子数组的路径:

function buildUL($array, $prefix) {
  echo "\n<ul>\n";
  foreach ($array as $key => $value) {
    echo "<li>";
    echo "$prefix$key/";
    // if the value is another array, recursively build the list
    if (is_array($value))
      buildUL($value, "$prefix$key/");
    echo "</li>\n";
  }
  echo "</ul>\n";
}

初始通话只是buildUL($array, '')