SimpleXML获取子元素之间的元素内容

时间:2013-11-21 20:28:13

标签: php xml simplexml

我使用SimpleXML在PHP中解析XML,并且有这样的XML:

<xml>
    <element>
        textpart1
            <subelement>subcontent1</subelement>
        textpart2
            <subelement>subcontent2</subelement>
        textpart3
    </element>
</xml>

当我做$xml->element时,它自然地给了我整个元素,就像在所有三个textparts中一样。

因此,如果我将其解析为一个数组(子项为foreach),我得到:

0 => textpart1textpart2textpart3, 1 => subcontent1, 2 => subcontent2

我需要一种解析<element>节点的方法,以便在子元素处停止或开始的每个textpart被视为自己的元素。

因此,我正在寻找一个可以在这样的数组中表达的有序列表:

0 => textpart1, 1 => subcontent1, 2 => textpart2, 3 => subcontent2, 4 => textpart3

这是否可以在不改变XML文件的情况下实现?提前感谢任何提示!

3 个答案:

答案 0 :(得分:3)

正如其他人所说,SimpleXML并不支持将单个文本节点作为单独的实体进行访问,因此您需要使用某些DOM方法对其进行补充。值得庆幸的是,您可以使用dom_import_simplexmlsimplexml_import_dom随意切换两者。

您需要的DOM功能的关键部分是:

  • DOMElement-&gt; childNodes成员变量,用于直接访问特定元素下的所有节点作为可迭代列表
  • DOMNode-&gt; nodeType变量,用于确定特定子节点是文本节点还是元素
  • DOMNode-&gt; nodeValue变量以获取实际文本

鉴于这些,您可以编写一个函数,该函数返回一个数组,其中包含子元素的SimpleXML对象和子文本节点的字符串,如下所示:

function get_child_elements_and_text_nodes($sx_element)
{
    $return = array();

    $dom_element = dom_import_simplexml($sx_element);
    foreach ( $dom_element->childNodes as $dom_child )
    {
        switch ( $dom_child->nodeType )
        {
            case XML_TEXT_NODE:
                $return[] = $dom_child->nodeValue;
            break;
            case XML_ELEMENT_NODE:
                $return[] = simplexml_import_dom($dom_child);
            break;
        }
    }

    return $return;
}

在你的情况下,你需要递归树,如果你把DOM和SimpleXML混合在一起会让你有点混乱,所以你可以完全在DOM中编写递归并在运行它之前转换SimpleXML对象:

function recursively_find_text_nodes($dom_element)
{
    $return = array();

    foreach ( $dom_element->childNodes as $dom_child )
    {
        switch ( $dom_child->nodeType )
        {
            case XML_TEXT_NODE:
                $return[] = $dom_child->nodeValue;
            break;
            case XML_ELEMENT_NODE:
                $return = array_merge($return, recursively_find_text_nodes($dom_child));
            break;
        }
    }

    return $return;
}

$text_nodes = recursively_find_text_nodes(dom_import_simplexml($simplexml->element));

Here's a live demo of that last function.

答案 1 :(得分:0)

简单的答案是否定的。 SimpleXML不对文本节点实现任何类型的支持  在这种情况下,您最好和首选的选项是使用DOMDocument

答案 2 :(得分:0)

您实际上正在查找作为element元素节点后代的所有文本节点。这可以表示为以下xpath:

/*/element//text()

即使SimpleXML有一个xpath方法,它可以执行此查询而不会出现任何错误,实际的文本节点将转换为其父元素节点。这是因为SimpleXML的工作原理以及它的设计目的。

与:比较:

但是,在姐妹库DOMDocument的帮助下,它可以自己代表文本节点,可以让它工作:

<?php
/**
 * SimpleXML get Element Content between Child Elements
 * @link https://stackoverflow.com/q/20131226/367456
 */

$buffer = <<<BUFFER
<xml>
    <element>
        textpart1
            <subelement>subcontent1</subelement>
        textpart2
            <subelement>subcontent2</subelement>
        textpart3
    </element>
</xml>
BUFFER;

$xml = simplexml_load_string($buffer);

$xpath = new SimpleXMLXpath($xml);
$result = $xpath->query('/*/element//text()');
print_r($result);

结果输出是:

Array
(
    [0] => 
        textpart1

    [1] => subcontent1
    [2] => 
        textpart2

    [3] => subcontent2
    [4] => 
        textpart3

)

这是可能的,因为SimpleXMLXpath类在内部包装DOMXPath并将结果字符串化,以防它是textnode:

/**
 * Class SimpleXMLXpath
 * 
 * @author hakre <http://hakre.wordpress.com/>
 */
class SimpleXMLXpath
{
    private $xml;

    public function __construct(SimpleXMLElement $xml)
    {
        $this->xml = $xml;
    }

    public function query($expression)
    {
        $context = dom_import_simplexml($this->xml);
        $xpath   = new DOMXPath($context->ownerDocument);
        $result  = [];

        foreach ($xpath->query($expression, $context) as $node) {
            switch (TRUE) {
                case $node instanceof DOMText:
                    $result[] = $node->nodeValue;
                    continue;

                case $node instanceof DOMElement:
                case $node instanceof DOMAttr:
                    $result[] = simplexml_import_dom($node);
                    continue;
            }
        }

        return $result;
    }
}