Как из дерева сделать ul-li структуру?

5 апреля 2008 г. Просмотров: 6848 RSS 2
Дневник » PHP

Получилось, что я немного меньше стал публиковать записей в блоге, но на это есть довольно веские причины. Помимо работы я постоянно занимаюсь MaxSite CMS.

Столкнулся с одной проблемой, может кто поможет её решить. (Самому уже не хвататет уже ни терпения, ни ума).

Задача связана с выводом рубрик. Есть таблица рубрик, где есть поля:

  • category_id (номер рубрики)
  • category_id_parent (номер родителя)

Теоретически этого хватает, чтобы организовать древовидную структуру (массив).

Я её реализовал классическим способом: через рекурсию. То есть для поиска «детей» выполняется еще один SQL-запрос. Соответственно при большом количестве рубрик и её «ветвистости» получается довольно много SQL-запросов.

Частично эту проблему можно решить за счет кэширования: получается где-то раза в два меньше.

Погуглив я нашел еще один способ создания деревьев: в нем указывается отступ слева (level). Например так («Код 1»):

001            level = 0
    002        level = 1
    003        level = 1
        004    level = 2
    005        level = 1

Путем различных манипуляций с массивом я смог сделать аналогичную структуру рубрик. При этом понадобился всего один SQL-запрос. Получилась примерно так:

[1] => Array
        (
            [category_id] => 1
            [category_id_parent] => 0
            [category_name] => Новости
            [category_menu_order] => 4
            [parents] => 0
            [childs] =>
            [level] => 0
        )
    [3] => Array
        (
            [category_id] => 3
            [category_id_parent] => 0
            [category_name] => CodeIgniter
            [category_menu_order] => 1
            [parents] => 0
            [childs] => 6 5
            [level] => 0
        )
    [6] => Array
        (
            [category_id] => 6
            [category_id_parent] => 3
            [category_name] => Еще
            [category_menu_order] => 0
            [parents] => 3
            [childs] =>
            [level] => 1
        )
...

Это «одномерная» структура, где сразу указываются и level (отступ), и все «дети» (childs), и родитель (parents). Всей этой информации по идее должно хватить на то, чтобы выстраивать и получать любые рубрики.

Теперь, чтобы выстоить рубрики по уровню, вполне достаточно одного level. И в принципе сделать это с помощью str_pad - две строчки кода (результат будет выглядеть как «код 1»).

Но дальше - ступор... Потратил несколько дней, но так и не смог придумать, каким образом все это конвертировать в ul-li. Сложность в том, что простыми заменами кажется не обойтись из-за вложенности тэгов. Чтобы было совсем понятно, нужно конвертировать «Код 1» (или сам массив) вот в это:

  • 001
    • 002
    • 003
      • 004

    • 005

HTML-код:

<ul>
    <li>001
        <ul>
            <li>002</li>
            <li>003
                <ul>
                    <li>004</li>
                </ul>
            </li>
            <li>005</li>
        </ul>
    </li>
</ul>

Кто-то знает как решить эту задачку?

[upd] Вот мой вариант решения, основанный только на level. Передполагается, что массив ($r) уже отсортирован в нужной последовательности.

function mso_tree_print($r)
{
    $out = "\n<ul>";
    $open  = "</li>\n<li>";
    $close = "</li>\n</ul>";
    $left  = "\n<ul>\n<li>";
    $old_level = 0;
    $open_li = 0;
    foreach ($r as $key=>$val)
    {
        $level = $val['level'];
        if ($level == $old_level)
        {
            $out .= $open . $val['category_name'];
        }
        elseif ($level > $old_level)
        {
            $open_li = $open_li + $level - $old_level;
            $out .= $left . $val['category_name'];
        }
        else // $level < $old_level
        {
            if (( $open_li) > 0)
                $out .= str_repeat($close, $open_li);
            $out .= $open . $val['category_name'];
            $open_li = 0;
        }
        $old_level = $level;
    }
    $out .= $close;
    $out = str_replace("<ul></li>", "<ul>", $out);
    return $out;
}

Сорри, при переносе удалились комменты, поэтому здесь, те, которые сохранились.


TedBeer :

6 апреля 2008 г. в 18:51 edit

Вот пример без level и без children(вычисляются по ходу дела) в базе. Порядок неважен, дети в массиве могут быть и до родителей, так что выборку из базы можно делать без сортировки.

$info = Array(
1 => Array
(
'category_id' => 1,
'category_id_parent' => 0,
'category_name' => 'Один'
),
2 => Array
(
'category_id' => 2,
'category_id_parent' => 1,
'category_name' => 'Два'
),
3 => Array
(
'category_id' => 3,
'category_id_parent' => 1,
'category_name' => 'Три'
),
4 => Array
(
'category_id' => 4,
'category_id_parent' => 3,
'category_name' => 'Четыре'
),
5 => Array
(
'category_id' => 5,
'category_id_parent' => 0,
'category_name' => 'Пять'
),
6 => Array
(
'category_id' => 6,
'category_id_parent' => 4,
'category_name' => 'Шесть'
)
);
$top = Array();
foreach($info as &$item){
$parentId = $item['category_id_parent'];
$id = $item['category_id'];
if( $parentId && isset($info[ $parentId])){
if( !isset($info[ $parentId]['children']))
$info[ $parentId]['children'] = Array($id);
else
$info[ $parentId]['children'][] = $id;
} else {
$top[] = $id;
}
}
function glue( $in, &$info){
$out = Array();
foreach( $in as $id){
$out[] = '<li>'. $info[$id]['category_name'];
if( isset( $info[$id]['children'])){
$out[] = '<ul>';
$out[] = glue( $info[$id]['children'], $info);
$out[] = '</ul>';
}
$out[] = '</li>';
}
return implode("\n", $out);
}
echo '<ul>' . glue( $top, $info) . '</ul>';

Galchenkov :

7 апреля 2008 г. в 11:19 edit

'Один', 'level' => 0);
$categories[] = Array('name' => 'Два', 'level' => 1);
$categories[] = Array('name' => 'Три', 'level' => 1);
$categories[] = Array('name' => 'Четыре', 'level' => 2);
$categories[] = Array('name' => 'Пять', 'level' => 3);
$categories[] = Array('name' => 'Шесть', 'level' => 2);
$categories[] = Array('name' => 'Семь', 'level' => 0);
// Инициализируем переменную
$current_level = -1;
foreach ($categories as $category)
{
// Уровень отображаемого категории (узла, если хотите)
$category_level = $category['level'];
if ($current_level <li>'.$category['name'];
}
if ($current_level &gt; $category_level)
{
// Тут важно закрыть все открытые ранее списки (ведь из третьего
// уровня можно сразу попасть в первый)
echo str_repeat('</li></ul>', $current_level - $category_level + 1);
echo '<ul><li>'.$category['name'];
}
if ($current_level == $category_level)
{
echo '</li><li>' . $category['name'];
}
$current_level = $category_level;
}
// Тут тоже закрываем все открытые ранее списки
echo str_repeat('</li></ul>', $current_level + 1);
?>

twitter.com facebook.com vkontakte.ru odnoklassniki.ru mail.ru friendfeed.com google.com yandex.ru
Комментариев: 2
  1. Дааа, можно всё сделать на много проще grin Над такой задачей я бился недели две тож пишу свою ЦМС

  2. у меня была таблица [id name owner type]

    owner - ID родителя [0 - корень]

    type (0 если это каталог, 1 если конечный элемент), грубо говоря папка и файл

    нужно было вывести дерево.

    логика примерно такая:

    - берем все где владелец КОРЕНЬ.

    - запоминаем idшники этих записей, а саму выборку сохраняем в results[0] - тоесть хранит все элементы первого уровня

    - теперь выбираем "второй уровень", тоесть у кого owner'ы - те что нашли раньше =)

    - ну и т.д.

    тоесть получаем массив results где каждый элемент массива содержит выборку по определенному уровню... количество запросов сколько уровней.. редко превышает 5

    function WriteTree() {
        global $result; //делаем глобальной
        $in_string = '0'; // параметры запроса, тоесть сначала делаем выборку "WHERE owner in (0)"
        $result = array();
        $level = 0; // уровень дерева
        while ($in_string !='') {
            $query = "SELECT * FROM catalog WHERE owner in (".$in_string.") ORDER BY id ASC";
            $result[$level] = mysql_query($query) or die('ОШИБКА '.mysql_error());
            $in_string = '';
            $in_array = array();
            $num_rows = mysql_num_rows($result[$level]);
            if ($num_rows) { //если хотябы одна запись вернулась
                while ($catalog_row = mysql_fetch_array($result[$level])) {
                    $in_array[] = $catalog_row['id'];  // выбираем ID текущего уровня, чтобы патом определить узлы следуюшей вложености
                }
                $in_string = implode(',',$in_array);
                $level++;
            }
        }
        unset ($result[$level]); // убиваем последнюю выборку, которая пустая
        //echo "уровней = ".$level;
        PrintBranch(0,0);
    }

    а тут разворачиваем дерево, заходим в каждую ветку и выводим элементы..

    function PrintBranch($level,$id) {
        global $result;
        $ul_write = false;
        echo "\n<ul>";
        mysql_data_seek($result[$level], 0);
        while ($row = mysql_fetch_assoc($result[$level])) {
            if ($row['owner'] == $id) {
            //if (!$ul_write) echo "\n<ul>";
                echo "<li><span><a href = \"#".$row['id']."\" itemindex = \"".$row['id']."\" type =\"".$row['type']."\">";
                echo "<img src =\"img/type".$row['type'].".png\"/>";
                echo "<b>".GetLangText($row['name'],'ru')."</b><b class=\"hide\">".GetLangText($row['name'],'en')."</b></a></span>";
                $new_lev = $level;
                if (isset($result[$level+1]) )
                    PrintBranch(++$new_lev,$row['id']);
                echo "</li>\n";
            }
        }
        echo "</ul>\n";
    }
Оставьте комментарий!

grin LOL cheese smile wink smirk rolleyes confused surprised big surprise tongue laugh tongue rolleye tongue wink raspberry blank stare long face ohh grrr gulp oh oh downer red face sick shut eye hmmm mad angry zipper kiss shock cool smile cool smirk cool grin cool hmm cool mad cool cheese vampire snake excaim question

Используйте нормальные имена

Имя и сайт используются только при регистрации

Зарегистрируйтесь, чтобы получать уведомления о новых комментариях по email.

Авторизация Войти через loginza

(обязательно)