将路径数组转换为 UL 列表

Posted

技术标签:

【中文标题】将路径数组转换为 UL 列表【英文标题】:Convert array of paths into UL list 【发布时间】:2011-02-04 22:45:41 【问题描述】:

我在数据库中有一个表,其中包含指向我网站页面的各种路径。每条路径仅列出一次。我目前有一系列非常长且复杂的查询和 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 列表。在正确的情况下,子列表包含在父元素的&lt;li&gt; 中。在此脚本中,父 &lt;li&gt; 关闭,然后插入 &lt;ul&gt; 块。整个脚本在跟上关卡等方面实际上相当优雅,但我无法完全理解它以弄清楚如何修复它。我在这里有一个函数的全部内容:

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

【问题讨论】:

必须使用递归的经典案例。也许你想用谷歌搜索那个词。 解决原始问题的最简单方法是删除&lt;/li&gt; 标签。它们在 HTML 中是可选的,删除它们将导致元素自动按正确的层次结构排序。 【参考方案1】:

编辑:

已更改以迎合更新的问题。

我使用__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 发布了一些想法……我认为我走在正确的轨道上,但坦率地说,似乎永远无法弄清楚多维数组。谢谢! 非常感谢!这完美地工作。我添加了另一个子例程来将类应用于 标签的各种层次结构,一切都很好。我将不得不做一些额外的阅读来弄清楚为什么这会起作用,因为我不能完全遵循所有步骤......但我会弄清楚并从你那里学到很多东西。非常感谢!【参考方案2】:

确实,多维度在这里会有所帮助。您可以通过将每个路径拆分为组件并使用这些组件来索引数组来构建一个。假设$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/ " => "contact us", "about/contact/form/ " => "contact form"); (等等...),然后在 buildUL 函数中使用类似这一行的内容: echo ''.$paths[$prefix.$key].' '; @techtheatre:看起来你几乎明白了,除了密钥与我在代码中打印的相同:$paths["$prefix$key/"] -- 我相信你错过了尾部斜线。 这太好了,恰好正是我急需的。谢谢@casablanca!

以上是关于将路径数组转换为 UL 列表的主要内容,如果未能解决你的问题,请参考以下文章

bootstrap 4将ul列表转换为移动列

将 ul li 转换为字符串列表 c#

将数组转换为 Ul-Li

iOS 路径如下:将坐标列表转换为贝塞尔曲线

将 JSON 数组转换为 <ul> 和 <li> 元素

将引导下拉 html ul 标记转换为 asp:DropDownList