Файл 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>

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

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

Мне удалось создать элемент foreach, который получает все дочерние элементы первого уровня, а затем другой элемент foreach, в котором содержатся все дочерние элементы второго уровня. Проблема в том, что это ограничивает количество слоев, которые я могу визуализировать, потому что у меня не может быть дюжины вложенных 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. Был бы очень признателен, если у кого-то есть идея 🙂

2

Решение

Два по существу идентичных примера приведены ниже. В каждом мы определяем функцию 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);

example.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

Другие решения

В XML-файле есть древовидная структура элементов.

Одним из распространенных способов отображения таких структур в PHP является использование RecursiveTreeIterator который отображает 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

Его использование относительно прямолинейно, но требует, чтобы вы написали RecursiveIterator свой собственный для структуры данных, которую вы имеете. Вот пример кода, который использует такой рекурсивный итератор, а именно 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 требуется сверху с собственным файлом RecursiveChildIterator.php который содержит следующий код, который является определением класса.

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

/**
* 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))
);
}
}
}

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

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

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

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

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

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

1

По вопросам рекламы [email protected]