Search code examples
phpxmlsimplexml

PHP simpleXML get value of different nodes depending on value


I have the following XML code:

<administration>
    <notes>
        <note>
            <id>12312312</id>
            <name>Lorem Ipsum</name>
            <reference>Target Value - 1</reference>
        </note>
        <note>
            <id>12312365</id>
            <name>Lorem Ipsum</name>
            <references>
                <code>Dolor it se met.</code>
                <code>Target Value - 2</code>
            </references>
        </note>
        <note>
            <id>12375512</id>
            <name>Target Value - 3</name>
            <reference>S</reference>
        </note>
    </notes>
    <accounting>
        <ledgers>
            <ledger>
                <debits>
                    <debit>
                        <description>Target Value - 4</description>
                        <amount>5467.32</amount>
                    </debit>
                    <debit>
                        <description>My Debit</description>
                        <amount>5467.32</amount>
                        <tags>
                            <tag>Target Value - 5</tag>
                        </tags>
                    </debit>
                </debits>
                <credits>
                    <credit>
                        <title>Target Value - 6</title>
                        <amount>873.00</amount>
                    </credit>
                    <credit>
                        <description>Target Value - 7</description>
                        <amount>23454.12</amount>
                    </credit>
                </credits>
            </ledger>
        </ledgers>
    </accounting>
</administration>

I'm trying to get a PHP array which consists of only the values of the nodes which have a value containing this string: "Target Value". This has to be done on a recursive way, using an XML parser (I'm trying SimpleXML, but I'm new to that).

Up 'till now, I've been trying to use SimpleXmlIterator and foreach- and for-loops to achieve this, but I can't seem to check if a node value contains "Target Value".

Edit: reaching the target nodes by manually referring to them is not what I'm looking for, if I were, there would be no problem

Is there any way to achieve this?

EDIT:

Here is the code of my last try:

function sxiToArray($sxi)
{
    $a = array();
    for( $sxi->rewind(); $sxi->valid(); $sxi->next() )
    {
        if(!array_key_exists($sxi->key(), $a))
        {
            $a[$sxi->key()] = array();
        }
        if($sxi->hasChildren())
        {
            if (strpos((string)$sxi->current(), "Target Value"))
                $a[$sxi->key()][] = sxiToArray($sxi->current());
        }
        else
        {
            if (strpos((string)$sxi->current(), "Target Value"))
                $a[$sxi->key()][] = strval($sxi->current());
        }
    }
    return $a;
}

$xmlArray = xml2array('../Document.xml');
print_r($xmlArray);

This gives the following result after running:

Array ( [notes] => Array ( ) [accounting] => Array ( ) )


Solution

  • It does not have to be done in an recursive way. You can use Xpath. Xpath uses location paths as part of an expression. The paths use different axes - one of them is descendant. It "ignores" the nesting. Xpath allows you to use conditions.

    • Get any element node in the document
      //*
    • That has a text node as an child
      //*[./text()]
    • with the text node containing the string "Target Value"
      //*[./text()[contains(., "Target Value")]]

    Put together it is a fairly small piece of code:

    $administration = new SimpleXMLElement($xml);
    $nodes = $administration->xpath('//*[./text()[contains(., "Target Value")]]');
    
    foreach ($nodes as $node) {
      var_dump($node->getName(), (string)$node);
    }
    

    Output:

    string(9) "reference"
    string(16) "Target Value - 1"
    string(4) "code"
    string(16) "Target Value - 2"
    string(4) "name"
    string(16) "Target Value - 3"
    string(11) "description"
    string(16) "Target Value - 4"
    string(3) "tag"
    string(16) "Target Value - 5"
    string(5) "title"
    string(16) "Target Value - 6"
    string(11) "description"
    string(16) "Target Value - 7"
    

    And with DOM it would not look much different:

    $document = new DOMDocument();
    $document->loadXml($xml);
    $xpath = new DOMXpath($document);
    
    $nodes = $xpath->evaluate('//*[./text()[contains(., "Target Value")]]');
    
    foreach ($nodes as $node) {
      var_dump($node->localName, $node->textContent);
    }