我需要一个递归 php 函数来遍历 xml 文件

I need a recursive php function to loop through a xml file

我正在尝试遍历 xml 文件并将与其值相比较的节点保存到数组中(键 => 值)。我还希望它跟踪它传递的节点(类似于 array(users_user_name => "myName", users_user_email => "myEmail") 等)。

我知道该怎么做,但有一个问题。所有的节点都可以有 children 而那些 children 也可能有 children 等所以我需要某种递归函数来不断循环 children 直到它到达最后一个 child。

到目前为止我得到了这个:

//loads the xml file and creates simpleXML object
        $xml = simplexml_load_string($content);

        // for each root value
        foreach ($xml->children() as $children) {
            // for each child of the root node
            $node = $children;
            while ($children->children()) {
                foreach ($children as $child) {

                    if($child->children()){
                        break;
                    }
                    $children = $node->getName();
                    //Give key a name
                    $keyOfValue = $xml->getName() . "_" . $children . "_" . $child->getName();
                    // pass value from child to children
                    $children = $child;

                    // no children, fill array: key => value
                    if ($child->children() == false) {
                        $parent[$keyOfValue] = (string)$child;
                    }
                }
            }
            $dataObject[] = $parent;
        }

"break;" 是为了防止它给我错误的值,因为 "child" 是一个 object 而不是最后一个 child.

您需要使用递归!

这是一个简单的递归示例:

function doThing($param) {
    // Do what you need to do
    $param = alterParam($param);
    // If there's more to do, do it again
    if ($param != $condition) {
        $param = doThing($param);
    }
    // Otherwise, we are ready to return the result
    else {
        return $param;
    }
}

您可以将这种想法应用到您的特定用例中。

使用递归,你可以写一些'complicated'处理,但问题是失去你的位置。

我在这里使用的函数传递了一些东西来跟踪名称和当前输出,还有它当前正在使用的节点。如您所见 - 该方法检查是否有任何子节点并再次调用该函数来处理它们中的每一个。

$content = <<< XML
<users>
    <user>
        <name>myName</name>
        <email>myEmail</email>
        <address><line1>address1</line1><line2>address2</line2></address>
    </user>
</users>
XML;

function processNode ( $base, SimpleXMLElement $node, &$output )  {
    $base[] = $node->getName();
    $nodeName = implode("_", $base);
    $childNodes = $node->children();
    if ( count($childNodes) == 0 )  {
        $output[ $nodeName ] = (string)$node;
    }
    else    {
        foreach ( $childNodes as $newNode ) {
            processNode($base, $newNode, $output);
        }
    }
}

$xml = simplexml_load_string($content);
$output = [];
processNode([], $xml, $output);
print_r($output);

这打印出来...

Array
(
    [users_user_name] => myName
    [users_user_email] => myEmail
    [users_user_address_line1] => address1
    [users_user_address_line2] => address2
)

使用此实现,内容会受到限制 - 例如 - 重复内容只会保留最后一个值(例如有多个用户)。

//Using SimpleXML library 


// Parses XML but returns an Object for child nodes

public function getNodes($root) 
{   
    $output = array();

    if($root->children()) {
        $children = $root->children();   

        foreach($children as $child) {
            if(!($child->children())) {
                $output[] = (array) $child;
            }
            else {
                $output[] = self::getNodes($child->children());
            } 
        }
    }   
    else {
        $output = (array) $root;
    }   

    return $output;
}   

我会添加到这个 当名称空间混入时我遇到了一些麻烦所以我制作了以下递归函数来解决节点

此方法进入最深的节点并将其用作值,在我的例子中,顶级节点的 nodeValue 包含嵌套在其中的所有值,因此我们必须深入到最低级别并将其用作真实值

    // using the XMLReader to read an xml file ( in my case it was a 80gig xml file which is why i don't just load everything into memory )
    $reader = new \XMLReader;
    $reader->open($path); // where $path is the file path to the xml file
    
    // using a dirty trick to skip most of the xml that is irrelevant where $nodeName is the node im looking for
    // then in the next while loop i skip to the next node
    while ($reader->read() && $reader->name !== $nodeName);
    while ($reader->name === $nodeName) {
        $doc = new \DOMDocument;
        $dom = $doc->importNode($reader->expand(), true);
        $data = $this->processDom($dom);
        $reader->next($dom->localName);
    }
   
    public function processDom(\DOMNode $node)
    {
        $data = [];
        /** @var \DomNode $childNode */
        foreach ($node->childNodes as $childNode) {
            // child nodes include of a lot of #text nodes which are irrelevant for me, so i just skip them
            if ($childNode->nodeName === '#text') {
                continue;
            }
            $childData = $this->processDom($childNode);
            if ($childData === null || $childData === []) {
                $data[$childNode->localName] = $childNode->nodeValue;
            } else {
                $data[$childNode->localName] = $childData;
            }
        }
        return $data;
    }