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

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

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

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

EDIT: Вот код в моей 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. Было бы очень полезно, если у кого-то есть идея 🙂

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

То, что у вас есть в 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 как false-y или foreach-able, и если 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)) ); } } } 

Затем конструктор продолжает создавать подходящий Traversable из параметра, чтобы родительский класс 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); } 

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

  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, который позволяет вам передать его всем, что принимает рекурсивный элемент, как в случае с рекурсивнымTreeIterator .