2

I'm working on a website that uses hierarchical data. After struggling to do it with MySQL databases (really complicated...), I decided to dive into XML because it sounds like XML works perfectly for my needs.

Now I'm experimenting with an XML File and SimpleXML. But first of all, here is what my XML File looks like:

<?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>

As you can see, it has a variing "depth" of child nodes. I also don't know the depth of childs, as they are created by the web app. This depth or "number of layers" can get quite high.

Now I want to read this XML File in my website. For example, I want to visualize it as a tree, with all the child nodes represented as circles connected to their parent circle.

I've managed to have a foreach getting all the first-layer "child" elements an then another foreach in it getting all second-layer "child" elements. The problem is, that this limits the number of layers I can visualize because I cannot have a dozen nested foreach'es.

Now I already have a headache thinking of a way of "unlimited nested foreach structures" to get all the layers of "child" nodes. But I'm not able to find a way of doing it.

Do you have an idea how to do it? Please help me! Thanks in advance.

PS: Sorry for my english, I'm an german teenager student :)

EDIT: Here is the code in my 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>

This is the result, just what I was expecting:

- child 1

    - child 1.1
    - child 1.2
    - child 1.3

So this works fine, but as mentioned in the comments, I need this not only for layer 1 to 2, but for layer 1 to n. Would really appreciate if someone has an idea :)

ralpsche
  • 23
  • 1
  • 4
  • 1
    You should post the code you have with a depth-2 parsing to have people tell you how to make it be n-depth parsing. Basically, you should consider recursive call to the "child node parser" – jpo38 Dec 20 '14 at 11:47
  • Can you post the code you've tried? – Rnet Dec 20 '14 at 11:59
  • Do you want this to appear as an unordered list? –  Dec 20 '14 at 13:44
  • @Peter Darmis exactly, with some sub-lists for the child-childs :) Will post the code as soon as im on the pc – ralpsche Dec 20 '14 at 14:21

2 Answers2

1

What you have in the XML file is a tree structure of elements.

One common way to display such structures in PHP is to make use of the RecursiveTreeIterator which displays ASCII trees:

\-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

It's usage is relatively straight forward but it requires that you write a RecursiveIterator your own for the data-structure you have. Here is the example code that makes use of such an recursive iterator, namely RecursiveChildIterator specifically created for your use-case:

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

As this example shows the RecursiveChildIterator is required on top with its own file RecursiveChildIterator.php that contains the following code which is the class definition.

In the constructor most work that is done is to validate the $children parameter to be either false-y or foreach-able and if foreach-able that each iteration gives a 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))
                    );
                }
            }
        }

The constructor then continues to create an appropriate Traversable out of the parameter so that the parent class IteratorIterator can use it as the dependency:

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

Then it's defined what the value of the current element is which is for the text-tree the title value:

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

And then the needed implementation as a RecursiveIterator to handle the recursive iteration with the two children methods of the interface:

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

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

Implementing the logic to traverse children in a class implementing the interface RecursiveIterator your own does allow you to pass it along to everything accepting a RecursiveIterator like it is the case with RecursiveTreeIterator.

hakre
  • 193,403
  • 52
  • 435
  • 836
  • I tried your code. but it shows error while calling RecursiveChildIterator.php Fatal error: Uncaught exception 'LogicException' with message 'The object is in an invalid state as the parent constructor was not called' in C:\xampp\htdocs\xmldata\new\cont.php:12 Stack trace: #0 [internal function]: IteratorIterator->rewind() #1 [internal function]: CachingIterator->rewind() #2 C:\xampp\htdocs\xmldata\new\cont.php(12): RecursiveTreeIterator->rewind() #3 {main} thrown in C:\xampp\htdocs\xmldata\new\cont.php on line 12 – soniya Raj Mar 11 '16 at 06:16
  • 1
    @soniyaRaj: I miss to see where **CachingIterator** is introduced in the code example I gave in my answer while in your comment it's named as the cause of error. So I'd say the error you have is not specifically because of the answer. – hakre Mar 11 '16 at 08:50
0

Two essentially identical examples are below. In each we define a function renderNode() that gets called recursively to render the nested lists. There's not a lot of code so there's not a lot to say.

One is based on SimpleXML, because that's what you're currently experimenting with.

The other is based on the DOM extension, because I personally find it to be a better API to work with (for all the reasons listed here and then some.)

For what you're doing here it's not terribly relevant which you use, but options are always nice.


DOM Example:

$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 Example:

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

Output (beautified, identical for both examples):

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

And just for kicks, here's a bonus option using XSLT. The beautified output is the same as above.

XSLT Example:

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>
Community
  • 1
  • 1
user3942918
  • 25,539
  • 11
  • 55
  • 67