PHP sorting issue with simpleXML

2019-01-20 20:28发布

test.xml:

<?xml version="1.0"?>
<props>
<prop>
<state statename="Mississippi">
    <info>
        <code>a1</code>
        <location>Jackson</location>
    </info>
    <info>
        <code>d2</code>
        <location>Gulfport</location>
    </info>
    <info>
        <code>g6</code>
        <location>Hattiesburg</location>
    </info>
</state>
<state statename="Texas">
    <info>
        <code>i9</code>
        <location>Dallas</location>
    </info>
    <info>
        <code>a7</code>
        <location>Austin</location>
    </info>
</state>
<state statename="Maryland">
    <info>
        <code>s5</code>
        <location>Mount Laurel</location>
    </info>
    <info>
        <code>f0</code>
        <location>Baltimore</location>
    </info>
    <info>
        <code>h4</code>
        <location>Annapolis</location>
    </info>
</state>
</prop>
</props>

test.php

// start the sortCities
function sortCities($a, $b){
    return strcmp($a->location, $b->location);
}
// start the sortStates
function sortStates($t1, $t2) {
    return strcmp($t1['statename'], $t2['statename']);
}


$props = simplexml_load_file('test.xml');
foreach ($props->prop as $prop) {
    $sortedStates = array();
    foreach($prop->state as $states) {
        $sortedStates[] = $states;
        }
    usort($sortedStates, "sortStates"); // finish the sortStates
    /* --- */
    echo '<pre>'."\n";
    print_r($sortedStates);
    echo '</pre>'."\n"; 
    /* --- */
    foreach ($prop->children() as $stateattr) { // this doesn't do it
    //foreach($sortedStates as $hotel => @attributes){ // blargh!
        if(isset($stateattr->info)) {
            $statearr = $stateattr->attributes();
            echo '<optgroup label="'.$statearr['statename'].'">'."\n";
            $options = array();
            foreach($stateattr->info as $info) {
                $options[] = $info;                            
            }
            usort($options, "sortCities"); // finish the sortCities  
            foreach($options as $stateattr => $info){
                echo '<option value="'.$info->code.'">'.$info->location.'</option>'."\n";
            }
            echo '</optgroup>'."\n";
            } else {
                //empty nodes don't do squat
            }
    }
}  
?>

This is the array that:

print_r($sortedStates);

prints out:

Array
(
    [0] => SimpleXMLElement Object
        (
            [@attributes] => Array
                (
                    [statename] => Maryland
                )

            [info] => Array
                (
                    [0] => SimpleXMLElement Object
                        (
                            [code] => s5
                            [location] => Mount Laurel
                        )

                    [1] => SimpleXMLElement Object
                        (
                            [code] => f0
                            [location] => Baltimore
                        )

                    [2] => SimpleXMLElement Object
                        (
                            [code] => h4
                            [location] => Annapolis
                        )

                )

        )

    [1] => SimpleXMLElement Object
        (
            [@attributes] => Array
                (
                    [statename] => Mississippi
                )

            [info] => Array
                (
                    [0] => SimpleXMLElement Object
                        (
                            [code] => a1
                            [location] => Jackson
                        )

                    [1] => SimpleXMLElement Object
                        (
                            [code] => d2
                            [location] => Gulfport
                        )

                    [2] => SimpleXMLElement Object
                        (
                            [code] => g6
                            [location] => Hattiesburg
                        )

                )

        )

    [2] => SimpleXMLElement Object
        (
            [@attributes] => Array
                (
                    [statename] => Texas
                )

            [info] => Array
                (
                    [0] => SimpleXMLElement Object
                        (
                            [code] => i9
                            [location] => Dallas
                        )

                    [1] => SimpleXMLElement Object
                        (
                            [code] => a7
                            [location] => Austin
                        )

                )

        )

)

this:

// start the sortCities
function sortCities($a, $b){
    return strcmp($a->location, $b->location);
}

plus this part of code:

    $options = array();
    foreach($stateattr->info as $info) {
        $options[] = $info;                            
    }
    usort($options, "sortCities"); // finish the sortCities  
    foreach($options as $stateattr => $info){
        echo '<option value="'.$info->code.'">'.$info->location.'</option>'."\n";
    }

is doing a fine job of sorting by the 'location' node within each optgroup.

You can see that in the array I can make it sort by the attribute 'statename'. What I am having trouble with is echoing out and combining the two functions in order to have it auto sort both the states and the cities within and forming the needed optgroups.

I tried copying the lines for the cities and changing the names called several ways to no avail.

So using the XML structure above, I am trying to get it to look like:

<optgroup label="Maryland">
<option value="h4">Annapolis</option>
<option value="f0">Baltimore</option>
<option value="s5">Mount Laurel</option>
</optgroup>
<optgroup label="Mississippi">
<option value="d2">Gulfport</option>
<option value="g6">Hattiesburg</option>
<option value="a1">Jackson</option>
</optgroup>
<optgroup label="Texas">
<option value="a7">Austin</option>
<option value="i9">Dallas</option>
</optgroup>

So as to no matter what order the states are ordered in the XML and no matter how the Locations are ordered in the node within the states, they always order alphabetically upon the creating of the optgroup.


Artefacto-

the last 2 code blocks show the function for sorting the nodes by name (the location nodes).


// start the sortCities
function sortCities($a, $b){
    return strcmp($a->location, $b->location);
}
// start the sortStates
function sortStates($t1, $t2) {
    return strcmp($t1['statename'], $t2['statename']);
}

the second function does sort by attribute (statename) in the array but, combing the two function or rather nesting them so that the states and the cities get sorted alphabetically has got me stumped.


@Artefacto,

Thanks for the reply. Seems to make sense the way it's nested. Issue is, none of my servers run PHP 5.3. So the generic functions are tossing errors. I should have mentioned this but didn't think about it. They are running 5.2. I have been trying to revert the script back and have gotten stuck with a section.

<?php
$doc = simplexml_load_file('test.xml');
$states =  get_object_vars($doc->prop->children());
$states = $states["state"];
function sortStates($t1, $t2) { 
    return strcmp($t1['statename'], $t2['statename']); 
};
usort($states, "sortStates");

/* this is just here for testing */
echo '<pre>';
print_r($states);
echo '</pre>';
/* end testing */

/*
array_walk($states,
    function (&$state) {
        $state = get_object_vars($state);
        array_walk($state["info"],
            function (&$el) {
                $el = get_object_vars($el);
            }
        );
        usort($state["info"],
            function($a, $b) { return strcmp($a["location"], $b["location"]); }
        );
    }
);
*/
?>

The commented out section starting with the array_walk. I can't figure out how to rewrite the 'function (&$state)' with out the next line dying.

标签: php simplexml
3条回答
男人必须洒脱
2楼-- · 2019-01-20 20:37

My approach was to convert the SimpleXMLElement objects into arrays:

$doc = new SimpleXMLElement($xml);
$states =  get_object_vars($doc->prop->children());
$states = $states["state"];
usort($states, function($t1, $t2) { return strcmp($t1['statename'], $t2['statename']); });
array_walk($states,
    function (&$state) {
        $state = get_object_vars($state);
        array_walk($state["info"],
            function (&$el) {
                $el = get_object_vars($el);
            }
        );
        usort($state["info"],
            function($a, $b) { return strcmp($a["location"], $b["location"]); }
        );
    }
);
查看更多
劳资没心,怎么记你
3楼-- · 2019-01-20 20:40

changing this

foreach ($prop->children() as $stateattr)

to this

foreach ($sortedStates as $stateattr)

did it.

could have sworn I tried that already.

查看更多
Fickle 薄情
4楼-- · 2019-01-20 20:55

You want to sort a list of SimpleXMLElements based on some attribute or element value. You normally do this by turning the list into an array and then apply the sorting on that array.

A function helpful for that in PHP is iterator_to_array. It does the job to turn something that is so magic in simplexml into a more "fixed" array.

Let's say your tour elements in $result->tour. By default simplexml returns an iterator here, something you can not sort out of the box. Let's convert it into an array:

$tours = iterator_to_array($result->tour, false);

Now the $tours variable is an array containing each <tour> element as a SimplXMLElement. You can now sort that array with an array function. Those array sorting functions are outlined in the PHP manual and I normally suggest How do I sort a multidimensional array in php as a good starting point:

$tourDates = array_map(
    function($tour) {return trim($tour->next_bookable_date); },
    $tours
);

// sort $tours array by dates, highest to lowest
$success = array_multisort($tourDates, SORT_DESC, $tours);

And that's already it. The whole code at a glance:

$tours = iterator_to_array($result->tour, false);

$tourDates = array_map(
    function($tour) {return trim($tour->next_bookable_date); },
    $tours
);

// sort $tours array by dates, highest to lowest
$success = array_multisort($tourDates, SORT_DESC, $tours);

foreach ($tours as $tour) {
    ...
}
查看更多
登录 后发表回答