简体   繁体   中英

How to convert XML into a hierarchical HTML menu?

I have been trying to build a child/parent navigation for a little while now using PHP (I'm from a .net background) and I can't get close to the desired results. I am loading my data from an XML file using SimpleXML successfully but I am trying to work out how I can map that into arrays or variables so I can write it out.

  <Categories>
  <Category>
    <ID>1</ID>
    <Title>Days</Title>
    <Description />
    <ParentID />
    <Meta />
  </Category>
  <Category>
    <ID>2</ID>
    <Title>Monday</Title>
    <Description />
    <ParentID>1</ParentID>
    <Meta />
  </Category>
  <Category>
    <ID>3</ID>
    <Title>Tuesday</Title>
    <Description />
    <ParentID>1</ParentID>
    <Meta />
  </Category>
  <Category>
    <ID>4</ID>
    <Title>Wednesday</Title>
    <Description />
    <ParentID>1</ParentID>
    <Meta />
  </Category>
  <Category>
    <ID>5</ID>
    <Title>Thursday</Title>
    <Description />
    <ParentID>1</ParentID>
    <Meta />
  </Category>
  <Category>
    <ID>6</ID>
    <Title>Friday</Title>
    <Description />
    <ParentID>1</ParentID>
    <Meta />
  </Category>
  <Category>
    <ID>7</ID>
    <Title>Saturday</Title>
    <Description />
    <ParentID/>
    <Meta />
  </Category>
  <Category>
    <ID>8</ID>
    <Title>Sunday</Title>
    <Description />
    <ParentID/>
    <Meta />
  </Category>
</Categories>

foreach($categories as $category) {

                if ($category->ParentID != "")
                {
                    echo "<li><a href=index.php?Cat=$category->ID>$category->Title</a></li>";
                    echo "<ul>";
                    foreach($categories as $subcategory) {
                    if ($subcategory->ParentID == $category->ID)
                    {
                        echo "<li><a href=index.php?Cat=$subcategory->ID>$subcategory->Title</a></li>";
                    }
                    }
                    echo "</ul>";
                }
                else
                {
                    echo "<li><a href=index.php?Cat=$category->ID>$category->Title</a></li>";
                } 
            } 
            echo "</ul>";

So my desired output would be something like this:

<ul id="p7menubar">
<li><a class="trigger" href="#">Days</a>
<ul>
<li><a href="#">Monday</a></li>
<li><a href="#">Tuesday</a></li>
<li><a href="#">Wednesday</a></li>
<li><a href="#">Thursday</a></li>
<li><a href="#">Friday</a></li>
</ul>
</li>
<li><a href="index.htm">Saturday</a></li>
<li><a href="index.htm">Sunday</a></li>
</ul>

I decided to bash on this code for the last 20 minutes. You need two instances of the list otherwise you are changing the index mid iteration. My advice is the top area:

if (file_exists('cats.xml')) {
    $xml = simplexml_load_file('cats.xml');
    $categories = simplexml_load_file('cats.xml');
} else {
    exit('Failed to open cats.xml.');
}
echo "<ul>";
foreach($categories as $category) {
    if (!(int)$category->ParentID > 0){
        // if the parentid is not set this is a root element and we want to print it and
        // it's children.
        categorylist($category, $xml);
    }
} 
echo "</ul>";
function categorylist($current, $list){
    // so echo the item list and link opener, but not the closer
    echo "<li><a href='index.php?Cat=$current->ID'>$current->Title</a>";
    // we need to count the number of children
    $count = 0;
    foreach($list as $item){
            // just iterate through the list for a match
        if ((int)$item->ParentID == (int)$current->ID){
            if($count == 0){
                            // if its the first match open the new child list tag
                echo "<ul>";
            }
                    // print the child link and item and iterate the counter
            echo "<li><a href=index.php?Cat=$item->ID>$item->Title</a></li>";
            $count = $count + 1;
        }
    }
    if($count > 0){
            // if their were children print the close of the list
        echo "</ul>";
    }
    // now close the list item.
    echo "</li>";
}

this gets the output you describe but no more, though it hints at the method you would use to create a recursive version. Of course cats.xml contains your xml content above.

If your data is going to be tree-structured (which it seems to be — each parent has multiple children), why not store them in an XML like this?

<?xml version="1.0"?>
<Categories>
  <Category>
    <Title>Days</Title>
    <Description/>
    <Meta/>
    <Categories>
      <Category>
        <Title>Monday</Title>
        <Description/>
        <Meta/>
      </Category>
      <Category>
        <Title>Tuesday</Title>
        <Description/>
        <Meta/>
      </Category>
      <Category>
        <Title>Wednesday</Title>
        <Description/>
        <Meta/>
      </Category>
      <Category>
        <Title>Thursday</Title>
        <Description/>
        <Meta/>
      </Category>
      <Category>
        <Title>Friday</Title>
        <Description/>
        <Meta/>
      </Category>
    </Categories>
  </Category>
  <Category>
    <Title>Saturday</Title>
    <Description/>
    <Meta/>
  </Category>
  <Category>
    <Title>Sunday</Title>
    <Description/>
    <Meta/>
  </Category>
</Categories>

This way the transformation is far simpler — you can use a very basic XSLT transform.

Of course a lot of optimizing needed but that's do the trick

$str = <<<XML
<?xml version='1.0'?>
<Categories>
  <Category>
    <ID>1</ID>
    <Title>Days</Title>
    <Description />
    <ParentID />
    <Meta />
  </Category>
  <Category>
    <ID>2</ID>
    <Title>Monday</Title>
    <Description />
    <ParentID>1</ParentID>
    <Meta />
  </Category>
  <Category>
    <ID>3</ID>
    <Title>Tuesday</Title>
    <Description />
    <ParentID>1</ParentID>
    <Meta />
  </Category>
  <Category>
    <ID>4</ID>
    <Title>Wednesday</Title>
    <Description />
    <ParentID>1</ParentID>
    <Meta />
  </Category>
  <Category>
    <ID>5</ID>
    <Title>Thursday</Title>
    <Description />
    <ParentID>1</ParentID>
    <Meta />
  </Category>
  <Category>
    <ID>6</ID>
    <Title>Friday</Title>
    <Description />
    <ParentID>1</ParentID>
    <Meta />
  </Category>
  <Category>
    <ID>7</ID>
    <Title>Saturday</Title>
    <Description />
    <ParentID/>
    <Meta />
  </Category>
  <Category>
    <ID>8</ID>
    <Title>Sunday</Title>
    <Description />
    <ParentID/>
    <Meta />
  </Category>
</Categories>
XML;

$xml = simplexml_load_string($str);
$htmllist = '<ul id="p7menubar">';

foreach($xml->Category as $category)
{
    switch($category->ID)
    {
        case '1':
            $htmllist .= '<li><a class="trigger" href="#">' . $category->Title . '</a><ul>';
            break;
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
            $htmllist .= '<li><a href="#">' . $category->Title . '</a></li>';
            break;
        case '7':
            $htmllist .= '</ul><li><a href="index.htm">' . $category->Title . '</a></li>';
            break;
        case '8':
            $htmllist .= '</ul><li><a href="index.htm">' . $category->Title . '</a></li></ul>';
            break;
    }
}

echo $htmllist;

?>

Some optimization tips:

  • If you can modify you XML source, you could insert you href attributes, classes or any other variables for cleaner code at the end.
  • You can even want to create nested XML nodes so your ul almost can take place in the XML itself

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM