user9094688
user9094688

Reputation:

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

I'm trying to loop through a xml file and save nodes pared with it's value into an array (key => value). I also want it to keep track of the nodes it passed (something like array(users_user_name => "myName", users_user_email => "myEmail") etc.).

I know how to do this but there is a problem. All the nodes could have children and those children might also have children etc. so I need some sort of recursive function to keep looping through the children until it reaches the last child.

So far I got this:

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

The "break;" is to prevent it from giving me the wrong values because "child" is an object and not the last child.

Upvotes: 0

Views: 2492

Answers (4)

WebKenth
WebKenth

Reputation: 136

I'll just add to this I've had some trouble when namespaces come into the mix so i made the following recursive function to solve a node

This method goes into the deepest node and uses it as the value, in my case the top node's nodeValue contains all the values nested within so we have to dig into the lowest level and use that as the true value

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

Upvotes: 0

jatinS
jatinS

Reputation: 804

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

Upvotes: 0

Nigel Ren
Nigel Ren

Reputation: 57121

Using recursion, you can write some 'complicated' processing, but the problem is loosing your place.

The function I use here passed in a couple of things to keep track of the name and the current output, but also the node it's currently working with. As you can see - the method checks if there are any child nodes and calls the function again to process each one of them.

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

This prints out...

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

With this implementation, there are limitations to the content - so for example - repeating content will only keep the last value (say for example there were multiple users).

Upvotes: 1

Goose
Goose

Reputation: 4821

You'll want to use recursion!

Here's a simple example of recursion:

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

You can apply this thinking to your specific use case.

Upvotes: 0

Related Questions