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. Был бы очень признателен, если у кого-то есть идея:)
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>
То, что у вас есть в 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, ваш собственный позволяет передавать его всему, что принимает Рекурсивный генератор, как в случае с Рекурсивный генератор.