1

Having solved a chain of other issues with this XML parsing project (thanks to a lovely SO user), I'm now stuck at the next hurdle. I'm loading SimpleXML into an array, sorting it and then echoing it into the page with grouping for the data.

I can get my group headings to display, sorted correctly, however when I try to add the sub-group data (which requires xpath to match attributes to the parent group), my output is just the first group heading. Not sure if it's the way I'm mixing array results with xpath or not but hopefully somebody here can spot the issue straight away.

Here is my XML:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE besteplist SYSTEM "example.dtd">
<besteplist>
    <yearlist>
        <year yid="y1">
            <yearname>1995, Season 3</yearname>
        </year>
        <year yid="y2">
            <yearname>1996, Season 4</yearname>
        </year>
        <year yid="y3">
            <yearname>1997, Season 5</yearname>
        </year>
        <year yid="y4">
            <yearname>1994, Season 2</yearname>
        </year>
        <year yid="y5">
            <yearname>1993, Season 1</yearname>
        </year>
    </yearlist>
    <eplist>
        <ep yearid="y1" eid="e1">
            <eptitle>The Third Episode</eptitle>
            <eptnumber>3</eptnumber>
        </ep>
        <ep yearid="y2" eid="e2">
            <eptitle>Bla bla bla</eptitle>
            <eptnumber>21</eptnumber>
        </ep>
        <ep yearid="y2" eid="e3">
            <eptitle>Rar rar rar</eptitle>
            <eptnumber>39</eptnumber>
        </ep>
        <ep yearid="y2" eid="e4">
            <eptitle>Tra la la</eptitle>
            <eptnumber>45</eptnumber>
        </ep>
        <ep yearid="y3" eid="e5">
            <eptitle>Donkey</eptitle>
            <eptnumber>126</eptnumber>
        </ep>
        <ep yearid="y1" eid="e6">
            <eptitle>SHOULD APPPEAR AS FIRST ONCE SORTED</eptitle>
            <eptnumber>1</eptnumber>
        </ep>
    </eplist>
</besteplist>

I am looking to sort the group headers by yearname in ascending order and the subgroup results by eptnumber in ascending order.

Here is my PHP code so far:

<?php
$xml=simplexml_load_file("example.xml") or die("Error: Cannot create object");

function xsort(&$sorted_year, $child_name, $order=SORT_ASC)
{
    $sort_proxy = array();

    foreach ($sorted_year as $k => $node) {
        $sort_proxy[$k] = (string) $node->$child_name;
    }

    array_multisort($sort_proxy, $order, $sorted_year);
}

$sorted_year = $xml->xpath('/besteplist/yearlist/year');
xsort($sorted_year, 'yearname', SORT_ASC);

$sorted_ep = $xml->xpath('/besteplist/eplist/ep');
xsort($sorted_ep, 'eptnumber', SORT_ASC);

foreach ($sorted_year as $season) {
    echo "SEASON: " . $season->yearname . "<br>";
    foreach ($sorted_ep->xpath("//ep[@yearid='$season[yid]']") as $episode) { 
        echo "EPISODE TITLE: " . $episode->eptitle . PHP_EOL . "<br>";
        echo "EPISODE NUMBER: " . $episode->eptnumber . PHP_EOL . "<br>"; 
        echo PHP_EOL . "<br>";
    }
}


?>

This is my output before any sorting:

SEASON: 1995, Season 3
EPISODE TITLE: The Third Episode 
EPISODE NUMBER: 3 

EPISODE TITLE: SHOULD APPPEAR AS FIRST ONCE SORTED 
EPISODE NUMBER: 1 

SEASON: 1996, Season 4
EPISODE TITLE: Bla bla bla 
EPISODE NUMBER: 21 

EPISODE TITLE: Rar rar rar 
EPISODE NUMBER: 39 

EPISODE TITLE: Tra la la 
EPISODE NUMBER: 45 

SEASON: 1997, Season 5
EPISODE TITLE: Donkey 
EPISODE NUMBER: 126 

SEASON: 1994, Season 2
SEASON: 1993, Season 1

This is how it SHOULD appear:

SEASON: 1993, Season 1

SEASON: 1994, Season 2

SEASON: 1995, Season 3
EPISODE TITLE: SHOULD APPPEAR AS FIRST ONCE SORTED 
EPISODE NUMBER: 1 

EPISODE TITLE: The Third Episode 
EPISODE NUMBER: 3 

SEASON: 1996, Season 4
EPISODE TITLE: Bla bla bla 
EPISODE NUMBER: 21 

EPISODE TITLE: Rar rar rar 
EPISODE NUMBER: 39 

EPISODE TITLE: Tra la la 
EPISODE NUMBER: 45 

SEASON: 1997, Season 5
EPISODE TITLE: Donkey 
EPISODE NUMBER: 126 

Many thanks in advance if you can spot the problem or suggest improvements. Obviously this is sample/example data based on the same structure.

Sam

UPDATE

Still haven't managed to get this quite right and unable to spot which point I'm going wrong at. Best guess would be the xpath within the foreach statement, but I'm not familiar enough with this type of code to be able to spot it.

I've switched out xsort for sort_obj_arr as I assume it's a somewhat better function. Unfortunately I'm getting the exact same results as before. Here's my updated code:

<?php
$xml=simplexml_load_file("example.xml") or die("Error: Cannot create object");

function sort_obj_arr(& $arr, $sort_field, $sort_direction) {
    $sort_func = function($obj_1, $obj_2) use ($sort_field, $sort_direction) {
        if ($sort_direction == SORT_ASC) {
            return strnatcasecmp($obj_1->$sort_field, $obj_2->$sort_field);
        } else {
            return strnatcasecmp($obj_2->$sort_field, $obj_1->$sort_field);
        }
    };
    usort($arr, $sort_func);
}

$sorted_year = $xml->xpath('/besteplist/yearlist/year');
$field1 = 'yearname';
$direction1 = SORT_ASC;
sort_obj_arr($sorted_year, $field1, $direction1);

$sorted_ep = $xml->xpath('/besteplist/eplist/ep');
$field2 = 'eptnumber';
$direction2 = SORT_ASC;
sort_obj_arr($sorted_ep, $field2, $direction2);

foreach ($sorted_year as $season) {
    echo "SEASON: " . $season->yearname . "<br>";
    foreach ($sorted_ep->xpath("//ep[@yearid='$season[yid]']") as $episode) { 
        echo "EPISODE TITLE: " . $episode->eptitle . PHP_EOL . "<br>";
        echo "EPISODE NUMBER: " . $episode->eptnumber . PHP_EOL . "<br>"; 
        echo PHP_EOL . "<br>";
    }
}

?>
IAreSam
  • 35
  • 4
  • I'm guessing my xpath referring to the array is incorrect somehow but I'm not really familiar enough with the code to be able to spot errors as I'm sort of winging this. Have updated my question. – IAreSam Nov 12 '15 at 21:32
  • time to learn debugging your code. Your guess is quite right, do you get error messages? see https://eval.in/468492. you sort ep too early, you need to sort them after you select the relevant ones. – michi Nov 13 '15 at 20:01
  • My PHP setup doesn't give me error codes unfortunately - I'll have to start using eval.in for testing purposes. I've tried rearranging but am now getting https://eval.in/468509 – IAreSam Nov 13 '15 at 20:16
  • getting useful error messages in PHP: http://stackoverflow.com/questions/845021/how-to-get-useful-error-messages-in-php – michi Nov 13 '15 at 22:23

1 Answers1

1

Your code

foreach ($sorted_ep->xpath("//ep[@yearid='$season[yid]']") as $episode)

throws an error

Fatal error: Call to a member function xpath() on a non-object (...)

because $sorted_ep is an array, not a SimpleXml-object.

It makes no sense to get a sorted list of all <ep> in your XML. You need to select the episodes for a certain year, and then sort those:

foreach ($sorted_year as $season) {
    echo "SEASON: " . $season->yearname . "<br><br>";

    // first select episodes from a certain year 
    $episodes = $xml->xpath("//ep[@yearid='$season[yid]']");

    // then sort those
    sort_obj_arr($episodes, "eptnumber", SORT_ASC);

    // now iterate and echo
    foreach ($episodes as $episode) { 
        echo "  EPISODE TITLE: " . $episode->eptitle . "<br>";
        echo "  EPISODE NUMBER: " . $episode->eptnumber . "<br><br>"; 
    }
}

see it in action: https://eval.in/468571

michi
  • 6,565
  • 4
  • 33
  • 56
  • I know we're not supposed to thank people in these comments but I feel rude not showing my gratitude. Just want to say thank you for all your wonderful help! :) – IAreSam Nov 14 '15 at 11:03
  • Thank you, I'm glad I can share. – michi Nov 14 '15 at 12:54