Rick Hellewell
Rick Hellewell

Reputation: 1122

XML recursive iteration grouping

An XML file with varying depths of 'children' in a family needs to be parsed to get all the descendents into a list.

As shown in the example code at https://3v4l.org/GV0UV , which includes the XML, the code that processes the XML is this, where $buffer contains the XML:

$xml       = new SimpleXMLIterator($buffer);
$recursive = new RecursiveIteratorIterator($xml);

foreach ($recursive as $tag => $object) {
    if ($tag !== 'Name') { 
        continue;
    }

        echo "-> " . $object ;
}

The output is this

-> 1David-> 1Samuel-> 1Fred-> 1John-> 1Frank-> 1Robert-> 2David-> 2Samuel-> 2Fred-> 2John-> 3David-> 3Samuel-> 3Fred-> 3John-> 3Frank

I need to separate this into families ('1David' is a person in a different family than '2David'; in real world, assume different names for each family member).

The desired output should be like this:

-> 1David-> 1Samuel-> 1Fred-> 1John-> 1Frank-> 1Robert
-> 2David-> 2Samuel-> 2Fred-> 2John
-> 3David-> 3Samuel-> 3Fred-> 3John-> 3Frank

So the RecursiveIteratorIterator is finding all of the 'name' values in the XML, but the output needs to be separated into 'families'.

How do I separate the extraction of the names into families?

(Note: my previous question is here Iterating through a multi-level XML file ; it was suggested that I start a new question.)

Upvotes: 1

Views: 61

Answers (2)

ThW
ThW

Reputation: 19492

Here is a different approach for this using Xpath expressions. It is not recursive, but a lot more specific. Xpath has axis like ancestor and descendant to make the expression go in a specific direction on the node tree.

$document = new DOMDocument();
$document->loadXml($buffer);
$xpath = new DOMXpath($document);

// for each top level Family
foreach ($xpath->evaluate('/root/Family') as $family) {
    echo $xpath->evaluate('string(Name)', $family), "\n-------------------\n";
    // find all ancestors without ancestors themself
    foreach ($xpath->evaluate('.//Ancestors[not(.//Ancestors)]/Family', $family) as $ancestor) 
        $names = [];
        // get the Family nodes on the path upwards
        foreach ($xpath->evaluate('ancestor::Family', $ancestor) as $descendant) {
            // collect the name
            $names[] = $xpath->evaluate('string(Name)', $descendant);
        }
        echo implode(' -> ', $names), "\n";
    }
    echo "\n";   
}

Output:

1David
-------------------
1David -> 1Samuel -> 1Fred -> 1John -> 1Frank

2David
-------------------
2David -> 2Samuel -> 2Fred

3David
-------------------
3David -> 3Samuel -> 3Fred -> 3John

Upvotes: 0

Nigel Ren
Nigel Ren

Reputation: 57121

You could simply check the level of the RecursiveIteratorIterator (using getDepth()) which will tell you how far into the nesting you are. 1 means you are at the top level...

foreach ($recursive as $tag => $object) {
    if ($tag !== 'Name') {
        continue;
    }

    if ( $recursive->getDepth() == 1 )  {
        echo PHP_EOL;
    }
    echo "-> " . $object ;
}

which will echo out...

-> 1David-> 1Samuel-> 1Fred-> 1John-> 1Frank-> 1Robert
-> 2David-> 2Samuel-> 2Fred-> 2John
-> 3David-> 3Samuel-> 3Fred-> 3John-> 3Frank

Although it does have a blank line at the front, this should be easy to remove.

To turn this into something which stores the data...

$output = [];
$family = [];
foreach ($recursive as $tag => $object) {
    if ($tag !== 'Name') {
        continue;
    }

    if ( $recursive->getDepth() == 1 )  {
        if (!empty($family) )   {
            $output[] = $family;
            $family = [];
        }
    }
    $family[] = (string)$object;
}
if (!empty($family) )   {
    $output[] = $family;
}

print_r($output);

gives...

Array
(
    [0] => Array
        (
            [0] => 1David
            [1] => 1Samuel
            [2] => 1Fred
            [3] => 1John
            [4] => 1Frank
            [5] => 1Robert
        )

    [1] => Array
        (
            [0] => 2David
            [1] => 2Samuel
            [2] => 2Fred
            [3] => 2John
        )

    [2] => Array
        (
            [0] => 3David
            [1] => 3Samuel
            [2] => 3Fred
            [3] => 3John
            [4] => 3Frank
        )

)

Upvotes: 3

Related Questions