0

I'm trying to parse this XML structure but can't find a way to parse "n"-deep nested tags using recursion. The xml structure:

<plist version="1.0">
    <key>1.1.1</key>
    <dict>
        <key>nag</key>
        <integer>1</integer>
    </dict>

    <key>2.2.2</key>
    <dict>
        <key>nag</key>
        <integer>1</integer>
    </dict>

    <key>3.3.3</key>
    <dict>
        <key>show_upgrade_button</key>
        <integer>0</integer>

        <key>nag_startup</key>
        <dict>
            <key>nag_gameover</key>
            <integer>3</integer>
        </dict>

        <key>my_stuff</key>
        <string>1=cb 2=rm 3=cb+rm =leave banner ads off</string>

    </dict>

    <key>4.4.4</key>
    <dict>
        <key>nag</key>
        <integer>1</integer>
    </dict>
</plist>

The nodes are matched key - dict being the key node a version number for the data inside the dict node but the xml structure has arbrittrary dict nesting as you can see in the above code. I've got this recursive function which accepts a dict node so far but I can't see the light.

<? php
function recursiveNodes($nodes, $arr){
        $count=0;
        if($nodes->hasChildNodes() === true  ){ 

            foreach($nodes->childNodes as $node){

                $temp = array();
                if($node->nodeName === 'key'){

                    $temp['key_name'] = $node->nodeValue;
                    if($node->nextSibling->nodeName !== 'dict'){
                        $sibling = $node->nextSibling;                      
                        $temp['type_name'] = $sibling ->nodeName;
                        $temp['value_name'] = $sibling ->nodeValue;
                    }
                    if($sibling->nodeName === 'dict'){
                    return recursiveNodes($sibling, $arr[$count++][]=$temp);
                }   
                }

            }

        }
            return $arr;
    }
    ?>
5
  • What are you trying to do with this XML? It's not clear what you're actually recursing to find. Commented Jul 5, 2013 at 3:07
  • Are you trying to flatten this structure into an array? Are you trying to represent it in a recursive array data structure? Commented Jul 5, 2013 at 3:37
  • Sorry if it's not clear, I'm trying to get the key nodeValue, and the next key sibling data as an array I need that data to insert into the db. Let's say first <key> <dict> pair, <key>1.1.1</key> <dict> <key>nag</key> <integer>1</integer> </dict> from the <key> node I got "1.1.1", from the <dict> node an array with the following data of its children nodes key_name ="nag", type_name= "integer" and type_value="1". Commented Jul 5, 2013 at 3:38
  • @nickb yes, the problem it's that I don't know how many nested dicts it will have. And it could be a pair of key-dict inside a dict tag which will have more nested tags or a pair of key-anytag which doesn't have more childnodes Commented Jul 5, 2013 at 3:45
  • If recursion gives you troubles with an XML document, look into xpath which is normally able to return you such as a list already. E.g. you can query all <dict> elements that have <key> as sibling. Commented Jul 5, 2013 at 7:18

2 Answers 2

1

The recursion in your function is broken. It might be more easy to wrap it into an object instead of a single function.

That would also allow to more easily extend it as needed.

See the following usage-example:

$parser = new PlistXMLParser();
$parser->loadXML($xml);
print_r($parser->parse());

With your exemplary input it gives the following:

Array
(
    [1.1.1] => Array
        (
            [nag] => 1
        )

    [2.2.2] => Array
        (
            [nag] => 1
        )

    [3.3.3] => Array
        (
            [show_upgrade_button] => 0
            [nag_startup] => Array
                (
                    [nag_gameover] => 3
                )

            [my_stuff] => 1=cb 2=rm 3=cb+rm =leave banner ads off
        )

    [4.4.4] => Array
        (
            [nag] => 1
        )

)

Internally this works basically how you already do, see here an excerpt from the sources:

...
switch ($type) {
    case self::NODE_INTEGER:
        $result[$keyString] = sprintf('%0.0f', trim($value));
        break;

    case self::NODE_STRING:
        $result[$keyString] = (string)$value;
        break;

    case self::NODE_DICT:
        $parser = new self();
        $parser->loadSimpleXMLElement($value);
        $result[$keyString] = $parser->parse();
        break;

    default:
        throw new UnexpectedValueException(sprintf('Unexpected type "%s" for key "%s"', $type, $key));
}
...

The parser is using a switch construct to better deal with the individual type-tokens that come up in the XML structure. The exception highlights you features you've not yet implemented and the recursion is triggered for NODE_DICT which just instantiates a new parser and let it do the work. A very simple form of recursion.

Sign up to request clarification or add additional context in comments.

2 Comments

thanks alot, it seems that it should be working, when trying to run the code it gives me an operand error list($value) = $key->xpath('following-sibling::*[1]') + [NULL]; here, line 33, also will this work with an xml with a regular header <?xml version="1.0" encoding="UTF-8" .... thanks again! simple and powerfull code
The code is written in the oldest current non-end-of-life PHP version: 5.4. Please upgrade PHP to 5.4 minimum or backport the code ([NULL] -> array(NULL)). And yes, everything of XML that is supported by SimpleXMLElement is support by that one. It's comparable to DOMDocument in this case.
0
//Using SimpleXML library
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;
}   

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.