XML文件 - 获取无限节点深度的特定子节点

时间:2014-12-20 11:30:25

标签: php xml simplexml nodes

我正在使用分层数据的网站。在努力使用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'es。

现在我已经头脑中想到了一种“无限嵌套的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 个答案:

答案 0 :(得分:1)

XML文件中的内容是元素的树结构。

在PHP中显示此类结构的一种常见方法是使用显示ASCII树的 RecursiveTreeIterator

\-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,并且如果每个迭代都提供 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);
    }

然后它定义了当前元素的值是文本树的标题值:

    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 :(得分:0)

下面是两个基本相同的例子。在每一个中,我们定义一个函数renderNode(),它被递归调用以呈现嵌套列表。没有很多代码,所以没有太多可说的。

一个基于SimpleXML,因为那是你目前正在尝试的。

另一个基于DOM extension,因为我个人认为它是一个更好的API(由于here列出的所有原因,然后是一些。)

对于你在这里所做的事情,你使用的并不是非常相关,但选择总是很好。


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>