Member Avatar for iamthwee

Right, I've searched all day long, html2array, html to json, html dom parser blah blah and I am struggling with this seemingly simple code.

I have the following html:

<ul>
    <li>menu1</li>
    <li>menu2
        <ul>
            <li>menu2.1</li>
        </ul>
    </li>
    <li>menu3</li>
</ul>

And I need to convert them to a parent child relationship. Or a php multi-dimensional array. I'm totally lost and frustrated. I'm hoping someone has an idea.

You may have seen this already in doing your research, but there might be a solution here.

Ill keep looking.

Member Avatar for iamthwee

That link actually looks pretty useful, and no I've somehow missed that in my searches.

I want to nail this by the end of the weekend so I'll going to fully explore that. Yeah, simple_xml definitely sounds like the proper way to do this without any bugs.

Member Avatar for iamthwee

What a waste of time, sorry to vent but the retard in that thread seems to create his list where each title is surrounded by a li.

Using the exact same code with my snippet

<?php

  $xml = <<<EOD
<li>menu1</li>
    <li>menu2
        <ul>
            <li>menu2.1</li>
        </ul>
    </li>
    <li>menu3</li>
EOD;

  function ul_to_array ($ul) {
    if (is_string($ul)) {
      if (!$ul = simplexml_load_string("<wrapper>$ul</wrapper>")) {
        trigger_error("Syntax error in UL/LI structure");
        return FALSE;
      }
      return ul_to_array($ul);
    } else if (is_object($ul)) {
      $output = array();
      foreach ($ul->li as $li) {
        $output[] = (isset($li->ul)) ? ul_to_array($li->ul) : (string) $li;
      }
      return $output;
    } else return FALSE;
  }

  print_r(ul_to_array($xml));

And I get this useless crap as my output:

Array ( [0] => menu1 [1] => Array ( [0] => menu2.1 ) [2] => menu3 )

Missing out menu2!

Here's a start, you just need to convert the output into a tree:

<?php
$html = <<<EOT
<ul>
    <li>menu 1</li>
    <li>menu 2<ul>
        <li>menu 2.1</li>
        <li>menu 2.2</li></ul></li>
    <li>menu 3</li>
</ul>
EOT;

function find_ul_or_li($source)
{
    $result = false;

    $pos_ul = strpos($source, '<ul>');
    $pos_li = strpos($source, '<li>');
    $pos_cul = strpos($source, '</ul>');
    $pos_cli = strpos($source, '</li>');

    if ($pos_ul === false)
        $pos_ul = PHP_INT_MAX;

    if ($pos_li === false)
        $pos_li = PHP_INT_MAX;

    if ($pos_cul === false)
        $pos_cul = PHP_INT_MAX;

    if ($pos_cli === false)
        $pos_cli = PHP_INT_MAX;

    if ($pos_ul < min($pos_li, $pos_cul, $pos_cli))
        $result = array ('token' => 'ul', 'pos' => $pos_ul);
    else if ($pos_li < min($pos_ul, $pos_cul, $pos_cli))
        $result = array ('token' => 'li', 'pos' => $pos_li);
    else if ($pos_cul < min($pos_ul, $pos_li, $pos_cli))
        $result = array ('token' => 'cul', 'pos' => $pos_cul);
    else if ($pos_cli < min($pos_ul, $pos_li, $pos_cul))
        $result = array ('token' => 'cli', 'pos' => $pos_cli);

    return $result;
}

function ul_to_array($source)
{
    $done = false;
    $result = array ();
    $level = -1;
    $previous_token = '';

    while (!$done)
    {
        $pos_result = find_ul_or_li($source);
        if (!$pos_result)
        {
            $done = true;
            continue;
        }

        if ($pos_result['token'] == 'ul')
        {
            if ($previous_token == 'li')
            {
                $value = substr($source, 0, $pos_result['pos']);
                $result[] = array ('level' => $level, 'value' => $value);
            }

            $level++;
            $source = substr($source, $pos_result['pos'] + 4);
            $previous_token = 'ul';
        }
        else if ($pos_result['token'] == 'li')
        {
            $source = substr($source, $pos_result['pos'] + 4);
            $previous_token = 'li';
        }
        else if ($pos_result['token'] == 'cul')
        {
            $level--;
            $source = substr($source, $pos_result['pos'] + 5);
            $previous_token = 'cul';
        }
        else if ($pos_result['token'] == 'cli')
        {
            $value = substr($source, 0, $pos_result['pos']);
            if ($value != '')
                $result[] = array ('level' => $level, 'value' => $value);

            $source = substr($source, $pos_result['pos'] + 5);
            $previous_token = 'cli';
        }
    }

    return $result;
}

print_r(ul_to_array($html));
?>

The output is this:

Array
(
[0] => Array
    (
        [level] => 0
        [value] => menu 1
    )
[1] => Array
    (
        [level] => 0
        [value] => menu 2
    )
[2] => Array
    (
        [level] => 1
        [value] => menu 2.1
    )
[3] => Array
    (
        [level] => 1
        [value] => menu 2.2
    )
[4] => Array
    (
        [level] => 0
        [value] => menu 3
    )
)
commented: nice! +13
commented: Exactly what I was looking for! +14

Here's how to convert a single level. If you need deeper, then you'll have to make it recursive:

$flat_array = ul_to_array($html);
print_r($flat_array);

$tree_array = array ();
foreach ($flat_array as $item)
{
    if ($item['level'] == 0)
        $tree_array[] = $item;
    else
    {
        for ($i = count($tree_array) - 1; $i >= 0; $i--)
        {
            if ($tree_array[$i]['level'] == $item['level'] - 1)
            {
                $tree_array[$i]['children'][] = $item;
                break;
            }
        }
    }
}

print_r($tree_array);

Outputs:

Array
(
    [0] => Array
        (
            [level] => 0
            [value] => menu 1
        )
    [1] => Array
        (
            [level] => 0
            [value] => menu 2
            [children] => Array
                (
                    [0] => Array
                        (
                            [level] => 1
                            [value] => menu 2.1
                        )
                    [1] => Array
                        (
                            [level] => 1
                            [value] => menu 2.2
                        )
                )
        )
    [2] => Array
        (
            [level] => 0
            [value] => menu 3
        )
)

A version using objects to simplify the nesting:

<?php
class MenuItem
{
    public $Parent = NULL;
    public $Name = '';
    public $Children = array ();
}

$html = <<<EOT
<ul>
    <li>menu 1</li>
    <li>menu 2<ul>
        <li>menu 2.1<ul>
            <li>menu 2.1.1</li>
            <li>menu 2.1.2</li>
            <li>menu 2.1.3</li></ul></li>
        <li>menu 2.2</li>
        <li>menu 2.3</li></ul></li>
    <li>menu 3</li>
</ul>
EOT;

function find_ul_or_li($source)
{
    // same as in previous post
}

function ul_to_objects($source)
{
    $menu = new MenuItem();
    $menu->Name = 'root';

    $currentItem = $menu;

    $done = false;
    $previous_token = '';

    while (!$done)
    {
        $pos_result = find_ul_or_li($source);
        if (!$pos_result)
        {
            $done = true;
            continue;
        }

        if ($pos_result['token'] == 'ul')
        {
            if ($previous_token == 'li')
            {
                $value = substr($source, 0, $pos_result['pos']);

                $newItem = new MenuItem();
                $newItem->Name = $value;
                $newItem->Parent = $currentItem;

                $currentItem->Children[] = $newItem;
                $currentItem = $newItem;
            }

            $source = substr($source, $pos_result['pos'] + 4);
            $previous_token = 'ul';
        }
        else if ($pos_result['token'] == 'li')
        {
            $source = substr($source, $pos_result['pos'] + 4);
            $previous_token = 'li';
        }
        else if ($pos_result['token'] == 'cul')
        {
            $currentItem = $currentItem->Parent;

            $source = substr($source, $pos_result['pos'] + 5);
            $previous_token = 'cul';
        }
        else if ($pos_result['token'] == 'cli')
        {
            $value = substr($source, 0, $pos_result['pos']);
            if ($value != '')
            {
                $newItem = new MenuItem();
                $newItem->Name = $value;
                $newItem->Parent = $currentItem;

                $currentItem->Children[] = $newItem;
            }

            $source = substr($source, $pos_result['pos'] + 5);
            $previous_token = 'cli';
        }
    }

    return $menu;
}

$menu = ul_to_objects($html);
print_r($menu);
?>

Output:

MenuItem Object
(
    [Parent] => 
    [Name] => root
    [Children] => Array
        (
            [0] => MenuItem Object
                (
                    [Parent] => MenuItem Object
                    [Name] => menu 1
                    [Children] => Array
                        (
                        )
                )
            [1] => MenuItem Object
                (
                    [Parent] => MenuItem Object
                    [Name] => menu 2
                    [Children] => Array
                        (
                            [0] => MenuItem Object
                                (
                                    [Parent] => MenuItem Object
                                    [Name] => menu 2.1
                                    [Children] => Array
                                        (
                                            [0] => MenuItem Object
                                                (
                                                    [Parent] => MenuItem Object
                                                    [Name] => menu 2.1.1
                                                    [Children] => Array
                                                        (
                                                        )
                                                )
                                            [1] => MenuItem Object
                                                (
                                                    [Parent] => MenuItem Object
                                                    [Name] => menu 2.1.2
                                                    [Children] => Array
                                                        (
                                                        )
                                                )
                                            [2] => MenuItem Object
                                                (
                                                    [Parent] => MenuItem Object
                                                    [Name] => menu 2.1.3
                                                    [Children] => Array
                                                        (
                                                        )
                                                )
                                        )
                                )
                            [1] => MenuItem Object
                                (
                                    [Parent] => MenuItem Object
                                    [Name] => menu 2.2
                                    [Children] => Array
                                        (
                                        )
                                )
                            [2] => MenuItem Object
                                (
                                    [Parent] => MenuItem Object
                                    [Name] => menu 2.3
                                    [Children] => Array
                                        (
                                        )
                                )
                        )
                )
            [2] => MenuItem Object
                (
                    [Parent] => MenuItem Object
                    [Name] => menu 3
                    [Children] => Array
                        (
                        )
                )
        )
)
Member Avatar for iamthwee

Prit that flat file array is exactly what I was looking for. How many levels does it go or does it go to any level deep?

Second question now I just need a way to convert that flat array to a parent child relationship and I'm done!

does it go to any level deep?

Yes.

Now I just need a way to convert that flat array to a parent child relationship

Take my last post, probably more useful. Much easier to convert to whatever you need.

Member Avatar for iamthwee

Too be honest Looping through the multidimensional array is confusing me, especially as it is an array of objects.

I think I should be able to convert the flat file to a parent child relationship which is just what I need.

I also used a bit of regex to remove whitespace between the uls and lis, I noticed that you gotta be very careful where you put a carriage return otherwise it creates additional array elements.

$html = preg_replace('~>\s+<~', '><', $html);

I think I should be able to convert the flat file to a parent child relationship which is just what I need.

Ok. If not, post the output you're looking for, and I'll add a method to convert the object list.

Member Avatar for iamthwee

That would be great!

    <ul>
        <li>menu 1</li>
        <li>menu 2
            <ul>
                <li>menu 2.1
                    <ul>
                        <li>menu 2.1.1</li>
                        <li>menu 2.1.2</li>
                        <li>menu 2.1.3</li>
                    </ul>
                </li>
                <li>menu 2.2</li>
                <li>menu 2.3</li>
            </ul>
        </li>
        <li>menu 3</li>
    </ul>

    id   parent     content
    1    null       menu 1
    2    null       menu 2
    3    2          menu 2.1
    4    3          menu 2.1.1


    and so on...

That's the format of the array I'm looking for!

Id just starts at 1, like an autoincrement?

Member Avatar for iamthwee

Yeah I think that would be best.

Member Avatar for iamthwee

OK I managed to build the parent child relationship it wasn't too difficult. Thanks Prit if I ever release my menu builder I will be sure to give you credit.

 /**
      *  @Description: build the parent child relationship from array
      *       @Params: array
      *
      *      @returns: none
      */
    public function build_array($flat_array)
    {
        $test = array();

        $my_counter = 0;
        foreach ($flat_array as $key) 
        {
            $test[$my_counter]['level'] = $key['level'];

            //IMPORTANT TO TRIM THE PIPE SYMBOL
            $test[$my_counter]['value'] = trim($key['value'],"|");
            $test[$my_counter]['id'] = $my_counter;
            $my_counter++;

        }

        $arr_items = count($test);
        //echo $arr_items;
        for ($i=$arr_items - 1; $i >= 0 ; $i--) { 
            echo "id ".$test[$i]['id'];
            echo " parent id ".$this->get_parent_id($test,$i);
            echo " content ".$test[$i]['value'];
            echo br();
        }   

    }

     /**
      *  @Description: loop up to get the parent id
      *       @Params: array
      *
      *      @returns: parent id
      */
    public function get_parent_id($test,$offset)
    {
        $current_level = $test[$offset]['level'];
        $arr_items = count($test);
        //echo $arr_items;
        for ($i=$offset; $i >= 0 ; $i--) { 
            if($test[$i]['level']<$current_level)
            {
                //return the parent id
                return $test[$i]['id'];
                break;
            }

        }
    }

Too late I see, good. This is my updated version (the rest is in the previous post):

function ul_to_objects($source)
{
    $menu = new MenuItem();
    $menu->Name = 'root';

    $tableInfo = array ();

    $currentItem = $menu;
    $currentId = 1;

    $done = false;
    $previous_token = '';

    while (!$done)
    {
        $pos_result = find_ul_or_li($source);
        if (!$pos_result)
        {
            $done = true;
            continue;
        }

        if ($pos_result['token'] == 'ul')
        {
            if ($previous_token == 'li')
            {
                $value = substr($source, 0, $pos_result['pos']);

                $newItem = new MenuItem();
                $newItem->Id = $currentId;
                $newItem->Name = $value;
                $newItem->Parent = $currentItem;

                $tableInfo[] = array ('id' => $currentId, 'parent' => $currentItem->Id, 'content' => $value);

                $currentItem->Children[] = $newItem;
                $currentItem = $newItem;

                $currentId++;
            }

            $source = substr($source, $pos_result['pos'] + 4);
            $previous_token = 'ul';
        }
        else if ($pos_result['token'] == 'li')
        {
            $source = substr($source, $pos_result['pos'] + 4);
            $previous_token = 'li';
        }
        else if ($pos_result['token'] == 'cul')
        {
            $currentItem = $currentItem->Parent;

            $source = substr($source, $pos_result['pos'] + 5);
            $previous_token = 'cul';
        }
        else if ($pos_result['token'] == 'cli')
        {
            $value = substr($source, 0, $pos_result['pos']);
            if ($value != '')
            {
                $newItem = new MenuItem();
                $newItem->Id = $currentId;
                $newItem->Name = $value;
                $newItem->Parent = $currentItem;

                $tableInfo[] = array ('id' => $currentId, 'parent' => $currentItem->Id, 'content' => $value);

                $currentItem->Children[] = $newItem;

                $currentId++;
            }

            $source = substr($source, $pos_result['pos'] + 5);
            $previous_token = 'cli';
        }
    }

    //return $menu;
    return $tableInfo;
}
Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.