Search code examples
phpxmlgroup-bysimplexml

php simplexml grouping objects


Before I start I would like to say I am a noob at reading XML into PHP but I have so far managed to load XML and display the data onto a PHP page. My next test is to group items.

Here is an XML snippet:

    <MailboxDatabases>
      <MailboxDatabase>
        <DatabaseName>DB01</DatabaseName>
        <Status>Healthy</Status>
        <MailboxServer>MB08</MailboxServer>
        <ActiveDatabaseCopy>mb07</ActiveDatabaseCopy>
        <ActivationSuspended>False</ActivationSuspended>
        <SinglePageRestore>0</SinglePageRestore>
        <ContentIndexState>Healthy</ContentIndexState>
        <Active>false</Active>
      </MailboxDatabase>
      <MailboxDatabase>
        <DatabaseName>DB01</DatabaseName>
        <Status>Healthy</Status>
        <MailboxServer>MB07</MailboxServer>
        <ActiveDatabaseCopy>mb07</ActiveDatabaseCopy>
        <ActivationSuspended>False</ActivationSuspended>
        <SinglePageRestore>0</SinglePageRestore>
        <ContentIndexState>Healthy</ContentIndexState>
        <Active>true</Active>
      </MailboxDatabase>
    <MailboxDatabases>
      <MailboxDatabase>
        <DatabaseName>DB02</DatabaseName>
        <Status>Healthy</Status>
        <MailboxServer>MB08</MailboxServer>
        <ActiveDatabaseCopy>mb07</ActiveDatabaseCopy>
        <ActivationSuspended>False</ActivationSuspended>
        <SinglePageRestore>0</SinglePageRestore>
        <ContentIndexState>Healthy</ContentIndexState>
        <Active>true</Active>
      </MailboxDatabase>
      <MailboxDatabase>
        <DatabaseName>DB02</DatabaseName>
        <Status>Healthy</Status>
        <MailboxServer>MB07</MailboxServer>
        <ActiveDatabaseCopy>mb07</ActiveDatabaseCopy>
        <ActivationSuspended>False</ActivationSuspended>
        <SinglePageRestore>0</SinglePageRestore>
        <ContentIndexState>Healthy</ContentIndexState>
        <Active>false</Active>
      </MailboxDatabase>
    </MailboxDatabases>

As you can see, "DatabaseName" is the same in two items but "Active" is different. What I want to do is display the above xml in php like

DB01 - MB08 - false | DB01 - MB07 - true

DB02 - MB08 - true | DB01 - MB07 - false

using the following elements (database) - (Mailboxserver) - (active) | ...

Please could someone give me a hand and also please try to explain the code.


Solution

  • Overall strategy:

    1. create a list of unique groups, that is <DatabaseName>
    2. select all <MailboxDatabase> that belong to a group and echo their <MailboxServer> and <Active>
    3. proceed to the next group and go to step 2 until you iterated over all groups.

    It's time to learn about selecting certain nodes from an XML, and this is best done with xpath. In short, xpath is for XML what SQL is for databases.

    Let's say you have a SimpleXML object ready:

    $xml = simplexml_load_string($x); // assume XML in $x
    

    create a list of unique groups

    Get all <DatabaseName> in an array:

    $groups = $xml->xpath("/MailboxDatabases/MailboxDatabase/DatabaseName");
    

    $groups contains an array of SimpleXML elements now, so let's do some array-magic to transform those into strings:

    $groups = array_map("strval", $groups);
    

    Result (var_dump):

    array(4) {
      [0]=>
      string(4) "DB01"
      [1]=>
      string(4) "DB01"
      [2]=>
      string(4) "DB02"
      [3]=>
      string(4) "DB02"
    }
    

    Some more array-magic: make it a unique list by flipping keys and values, keys must be unique, so duplicates are killed. then flip again:

    $groups = array_flip(array_flip($groups));
    

    Result:

    array(2) {
      [1]=>
      string(4) "DB01"
      [3]=>
      string(4) "DB02"
    }
    

    This is it. Write it as one line:

    $groups = array_flip(array_flip(array_map("strval", $xml->xpath("//MailboxDatabase/DatabaseName"));
    

    BTW: // at the beginning of that xpath statement is a wildcard

    select nodes group-wise

    Again xpath, this time with a condition to select only those <MailboxDatabase> that share the same <DatabaseName>. Note the condition in []:

    $elements = $xml->xpath("/MailboxDatabases/MailboxDatabase[DatabaseName = '$group']");
    

    Now iterating over $groups:

    foreach ($groups as $group) {
    
        echo "group $group:" . PHP_EOL;
        $elements = $xml->xpath("//MailboxDatabase[DatabaseName = '$group']");
    
        foreach ($elements as $e)
            echo "  " . $e->MailboxServer . ": " . $e->Active . PHP_EOL;
    
        echo PHP_EOL;
    } 
    

    Result:

    group DB01:
      MB08: false
      MB07: true
    
    group DB02:
      MB08: true
      MB07: false
    

    see it working: https://eval.in/321935