XML-файл - Получение определенных дочерних узлов с неограниченной глубиной узлов


Я работаю над веб-сайтом, который использует иерархические данные. После того, как я попытался сделать это с базами данных MySQL (действительно сложно...), я решил погрузиться в XML, потому что похоже, что XML идеально подходит для моих нужд.

Сейчас я экспериментирую с XML-файлом и SimpleXML. Но прежде всего, вот как выглядит мой XML-файл:

<?xml version="1.0" encoding="ISO-8859-1" ?>

<content>
    <parent>
        <child id="1">
            <title>child 1</title>

            <child id="1">
                <title>child 1.1</title>

                <child id="1">
                    <title>child 1.1.1</title>
                </child>
            </child>

            <child id="2">
                <title>child 1.2</title>

                <child id="1">
                    <title>child 1.2.1</title>

                        <child id="1">
                            <title>child 1.2.1.1</title>
                        </child>
                </child>

                <child id="2">
                    <title>child 1.2.2</title>
                </child>
            </child>

            <child id="3">
                <title>child 1.3</title>
            </child>
        </child>
    </parent>
</content>

Как вы можете видеть, он имеет различную "глубину" дочерних узлов. Я также не знаю глубины детей, так как они создаются веб-приложением. Этот глубина или "количество слоев" может быть довольно высокой.

Теперь я хочу прочитать этот XML-файл на своем веб-сайте. Например, я хочу визуализировать его в виде дерева, со всеми дочерними узлами, представленными в виде кругов, соединенных с их родительским кругом.

Мне удалось сделать так, чтобы каждый элемент получал все "дочерние" элементы первого уровня, а затем еще один элемент в нем получал все "дочерние" элементы второго уровня. Проблема в том, что это ограничивает количество слоев, которые я могу визуализировать, потому что у меня не может быть дюжины вложенные foreach'ы.

Теперь у меня уже болит голова, когда я думаю о способе "неограниченных вложенных структур foreach", чтобы получить все слои "дочерних" узлов. Но я не могу найти способ сделать это.

У вас есть идея, как это сделать? Пожалуйста, помогите мне! Заранее спасибо.

PS: Извините за мой английский, я студент-подросток из Германии:)

РЕДАКТИРОВАТЬ: Вот код в моем test.php :

<?php
    if (file_exists('mydata.xml'))
    {
        $xml = simplexml_load_file('mydata.xml');
?>

<ul>
<?php 
        foreach($xml->parent->child as $item) // Go through first layer
        {
            echo "<li>".$item->title;

            echo "<ul>"; // Open second layer <ul>
            foreach($item->child as $item) // Go through second layer
            {
                echo "<li>".$item->title."</li>";
            }
            echo "</ul>"; // Close second layer <ul>

            echo "</li>"; // Close child <li>
        }
    }
    else
    {
       exit('Konnte Datei nicht laden.');
    }
?>
</ul>

Это результат, как раз то, чего я ожидал:

- child 1

    - child 1.1
    - child 1.2
    - child 1.3

Итак, это работает нормально, но, как упоминалось в комментариях, мне это нужно не только для слоев с 1 по 2, но и для слоев с 1 по n. Был бы очень признателен, если у кого-то есть идея:)

Author: ralpsche, 2014-12-20

2 answers

Ниже приведены два по существу идентичных примера. В каждом из них мы определяем функцию renderNode(), которая вызывается рекурсивно для отображения вложенных списков. Кода не так много, так что говорить особо нечего.

Один из них основан на SimpleXML, потому что это то, с чем вы сейчас экспериментируете.

Другой основан на расширении DOM, потому что я лично считаю, что это лучший API для работы (по всем причинам, перечисленным здесь, а затем некоторые.)

Для того, что вы здесь делаете, не очень важно, что вы используете, но варианты всегда хороши.


Пример DOM:

$dom = new DOMDocument();
$dom->load('mydata.xml');
$xpath = new DOMXPath($dom);

echo "<ul>";
foreach ($xpath->query('/content/parent/child') as $node) {
    renderNode($node, $xpath);
}
echo "</ul>";

function renderNode(DOMElement $node, DOMXPath $xpath) {
    echo "<li>", $xpath->evaluate('string(title)', $node);
    $children = $xpath->query('child', $node);
    if ($children->length) {
        echo "<ul>";
        foreach ($children as $child) {
            renderNode($child, $xpath);
        }
        echo "</ul>";
    }
    echo "</li>";
};

Пример SimpleXML:

$xml = simplexml_load_file('mydata.xml');

echo "<ul>";
foreach ($xml->parent->child as $node) {
    renderNode($node);
}
echo "</ul>";

function renderNode($node) {
    echo "<li>", $node->title;
    if ($node->child) {
        echo "<ul>";
        foreach ($node->child as $child) {
            renderNode($child);
        }
        echo "</ul>";
    }
    echo "</li>";
}

Вывод (украшенный, идентичный для обоих примеров):

<ul>
    <li>child 1
        <ul>
            <li>child 1.1
                <ul><li>child 1.1.1</li></ul>
            </li>
            <li>child 1.2
                <ul>
                    <li>child 1.2.1
                        <ul><li>child 1.2.1.1</li></ul>
                    </li>
                    <li>child 1.2.2</li>
                </ul>
            </li>
            <li>child 1.3</li>
        </ul>
    </li>
</ul>

И просто для удовольствия, вот бонусная опция с использованием XSLT. Украшенный вывод такой же, как и выше.

XSLT-файл Пример:

PHP:

$xmldoc = new DOMDocument();
$xmldoc->load('mydata.xml');

$xsldoc = new DOMDocument();
$xsldoc->load('example.xsl');

$xsl = new XSLTProcessor();
$xsl->importStyleSheet($xsldoc);
echo $xsl->transformToXML($xmldoc);

Пример.xsl:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="html" encoding="UTF-8" indent="no"/>

    <xsl:template match="/content/parent">
        <ul>
            <xsl:apply-templates select="child"/>
        </ul>
    </xsl:template>
    <xsl:template match="child">
        <li>
            <xsl:value-of select="title"/>
            <xsl:if test="child">
                <ul>
                    <xsl:apply-templates select="child"/>
                </ul>
            </xsl:if>
        </li>
    </xsl:template>
</xsl:stylesheet>
 0
Author: Paul Crovella, 2017-05-23 12:07:50

То, что у вас есть в XML-файле, представляет собой древовидную структуру элементов.

Одним из распространенных способов отображения таких структур в PHP является использование рекурсивного рей-литератора, который отображает деревья ASCII:

\-child 1
  |-child 1.1
  | \-child 1.1.1
  |-Chapter 1.2
  | |-child 1.2.1
  | | \-child 1.2.1.1
  | \-child 1.2.2
  \-child 1.3

Его использование относительно прямолинейно, но для этого требуется, чтобы вы написали Рекурсивный генератор свой собственный для имеющейся у вас структуры данных. Вот пример кода, в котором используется такой рекурсивный итератор, а именно recursivechilditerator специально созданный для вашего варианта использования:

<?php
/**
 * recursive display of XML contents
 */

require 'RecursiveChildIterator.php';

$content  = simplexml_load_file('content.xml');
$iterator = new RecursiveChildIterator($content->parent->child);
$tree     = new RecursiveTreeIterator($iterator);

foreach ($tree as $line) {
    echo $line, "\n";
}

Как показано в этом примере, рекурсивный генератор требуется сверху со своим собственным файлом RecursiveChildIterator.php, который содержит следующий код, являющийся определением класса.

В конструкторе большая часть выполняемой работы заключается в проверке того, что параметр $children является либо ложным, либо доступным для каждого, и если возможно, что каждая итерация дает Элемент SimpleXML:

/**
 * Class RecursiveChildIterator
 */
class RecursiveChildIterator extends IteratorIterator implements RecursiveIterator
{
    /**
     * @var SimpleXMLElement
     */
    private $children;

    public function __construct($children)
    {
        if ($children) {
            foreach ($children as $child) {
                if (!$child instanceof SimpleXMLElement) {
                    throw new UnexpectedValueException(
                        sprintf('SimpleXMLElement expected, %s given ', var_export($child, true))
                    );
                }
            }
        }

Затем конструктор продолжает создавать соответствующий Проходимый из параметра, чтобы родительский класс Итератор мог использовать его в качестве зависимости:

        if ($children instanceof Traversable) {
            $iterator = $children;
        } elseif (!$children) {
            $iterator = new EmptyIterator();
        } elseif (is_array($children) || is_object($children)) {
            $iterator = new ArrayObject($children);
        } else {
            throw new UnexpectedValueException(
                sprintf("Array or Object expected, %s given", gettype($children))
            );
        }

        $this->children = $children;

        parent::__construct($iterator);
    }

Затем определяется значение текущего элемента, которое для текстового дерева является значением заголовка:

    public function current()
    {
        return parent::current()->title;
    }

А затем необходимая реализация в виде рекурсивного генератора для обработки рекурсивной итерации с помощью двух дочерних методов интерфейса:

    public function hasChildren()
    {
        $current = parent::current();
        return (bool)$current->child->count();
    }

    public function getChildren()
    {
        $current = parent::current();
        return new self($current->child);
    }
}

Реализация логики для обхода детей в классе, реализующем интерфейс RecursiveIterator, ваш собственный позволяет передавать его всему, что принимает Рекурсивный генератор, как в случае с Рекурсивный генератор.

 1
Author: hakre, 2014-12-20 15:35:24