将路径数组转换为无序列表UL

10
我在数据库中有一个表,其中包含了我网站上各种页面的路径。每个路径只列出一次。目前,我使用非常冗长和复杂的查询和PHP代码来获取所有这些数据,并将其重写为无序列表(以创建网站菜单)。似乎可能存在一种相对简单的循环方法,可以更有效地工作,但我似乎无法让任何东西工作。我找到了大量的PHP脚本,可以从文件树创建UL列表,但它们都不起作用或不能处理我的查询结果的固有非递归性质(有些需要我的路径的多维数组,这是可以接受的,除了我创建这些的麻烦)。我确实找到了一个脚本,它几乎可以正常工作,但它通过在<li>部分之外放置子列表来格式化<ul>部分(我将在下面解释)。
以下是样例:
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列表格式不正确。在正确的情况下,子列表应该包含在父元素的

  • 中。但是在这个脚本中,先关闭父
  • ,然后插入一个
      块。总体而言,这个脚本的设计相当优雅,可以很好地保持与层次结构相关的信息,但我无法理解它的细节以修复它。以下是完整的脚本函数:

      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>
      

      有没有想法在哪里添加额外的逻辑以及我如何实现这个目标?其他更好的方法吗?似乎这是一个常见的问题,应该有一种简单/标准的处理方式。也许如果我能弄清楚如何从我的路径中创建多维数组,那么就可以迭代这些数组来使其工作?
      编辑:更加复杂 :-( 我尝试了casablanca的回答,它完美地解决了我的问题...但我意识到现在我需要跟进,使事情更加困难。为了显示页面的“名称”,我还需要在数组中拥有该信息,因此路径可能更适合用作数组键,而名称则作为数组的值。对于这种改变有任何想法吗?
      $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>';
      

  • 这是一个必须使用递归的经典案例。也许你想要谷歌一下这个术语。 - anroesti
    最简单的解决原问题的方法是删除 </li> 标签。它们在 HTML 中是可选的,删除它们将导致元素自动按正确的层次排序。 - Nisse Engström
    2个回答

    14

    编辑:

    为了适应更新后的问题,我做出了更改。

    我正在使用__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;
    
    ?>
    

    谢谢!我刚意识到它需要再复杂一层,还要包括页面名称(以及路径),这样最终输出就是一个带标题的链接。如果你想试试,我刚在@casablanca发了一些想法...我觉得我走在正确的轨道上,但老实说,我似乎永远也搞不清楚多维数组。谢谢! - techtheatre
    非常感谢!这个完美地运作了。我添加了另一个子程序来应用类到各种层次的<ul>标签,一切都很好。我需要做一些额外的阅读来弄清楚为什么这个能够工作,因为我不能完全跟随所有步骤...但我会弄清楚并从您那里学到很多。非常感谢! - techtheatre

    6

    在这里,多维数组将会很有帮助。您可以通过将每个路径拆分成组件并使用这些组件索引到数组中来构建它。假设$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, '')

    谢谢!这个很好用。现在我有一个后续问题,让事情变得更复杂一些。为了显示页面的“名称”,我还需要在数组中包含该信息,因此路径可能更适合作为数组键,而名称则作为值。对于像这样进行更改的想法吗:$paths = array("about/contact/" => "联系我们", "about/contact/form/" => "联系表单");(等等...),然后在buildUL函数中使用类似以下的代码行: echo '<a href="'.$prefix.$key.'/">'.$paths[$prefix.$key].'</a>'; - techtheatre
    @techtheatre:看起来你差不多做到了,除了键应该与我在代码中打印的一样:“$paths["$prefix$key/"]”--我相信你错过了末尾的斜杠。 - casablanca
    这太棒了,正好是我急需的。谢谢@casablanca! - dprevite

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接