Sure, I'll go through section by section, not sure how much you already know so I'll try to explain quite simply.
<?php
$xml = file_get_contents ( 'http://xfireplus.com/xfireplus/xfirexml/xfire_xml.php?username=w4rp3d1' ); //
$xml = preg_replace ( '/>(n|r|rn| |t)*</','><', $xml ); //remove unnecessary whitespace
$i = 0; //index for $games
$games = array ( ); //array to store info about games
$element = ''; //current element
$gsection = false; //bool to store whether we are currently parsing the <games> section
This part first of all retrieves the XML as one long string and put it in $xml, and then removes any whitespace (i.e. newlines, tabs and spaces) which is between two tags if there is nothing else between them (without this I got stange behaviour). This is achieved using regular expressions, which is a sort of way of matching text to a pattern. You can read about those here.
We then declare and initialise some global variables which will be used to store information which which be required in more than one function. The reason for using global variables is normal (local) variables in a function only exist until the leaving the scope of the fujntion, we can use static variables to maintain the value and only have is accessible to the function it was delcared in, or used global variables which will mean it keeps its value and will be accessible to other functions. You can read more about variable scope here.
$i is used to store the index to use for the array storing the information about each game (it's not really an iterator, not sure why I put that previously) which is stored in $games, which is an array. Arrays (if you don't know) are used to store more than one element of data which is set and retrieved using an index or key, in this case we are using an array of arrays, known as a multidimensional array, you can read more about arrays here. $element is used to store the XML element we are currently on or in, you'll se more of how that is used further down. $gsection is used to indicate if we are in the <games> section (that is between <games> and </games>. This is because there is also <game> elements which aren't used to store information about play time futher down in the XML, which we don't care about.
function start_element ( $parser, $name, $attribute )
{
global $element, $gsection;
$element = $name; //set current element
if ( $name == 'games' ) //if we are entering the <games> section set $gsection to true
{
$gsection = true;
}
}
This is the function that xml_parse will call whenever it encounters a start element in the XML, e.g. <games>, <game>, <servers>, etc. It passes or gives the function the name of the element and its attributes (as an associative array, one that uses a string to identify its elements) to the function. So for instance if the element was <game id="game0"> in $name we would have "game" and in $attribute['id'] we would have "game0".
First, we declare $element and $gsection as global so we have access to those variables. We then set $element to the value of $name, so we can find out anywhere in the program what the last element we encountered was. We check if the element name is "games", if it is it means we have just encountered the <games> element so we set $gsection to true. Read more about control structures here
function end_element ( $parser, $name )
{
global $i, $gsection;
if ( $name == 'game' && $gsection == true ) //increment iterator if we have just finished parsing a game and are in the <games> section
{
$i++;
}
if ( $name == 'games' ) //if we are exiting the <games> section set $gsection to false
{
$gsection = false;
}
}
This is the function that xml_parse will call whenever it encounters a end element in the XML like </games> or </game>. It passes the name of the element but there are no attributes this time, because end elements don't have attributes.
Like before we declare our global variables, in this case $i and $gsection. Then we check if we have reached a </game> element and are within the <games> section. If both of those are true then that means we have just finished storing information about a games, and any new information we store will be about a different game, therefore we increase $i by 1, so when we store more stuff in $games it will be in a different element. Next we check in the end tag we have just reached is </games>, if it is we set $gsection to false because we have just left the section.
function cdata ( $parser, $data )
{
global $element, $i, $games;
if ( $element == 'gamename' ||
$element == 'thisweek' ||
$element == 'alltime' ) //if the current element is one of the three we need the cdata is for add it to the associative array
{
$games[$i][$element] = $data;
}
}
This function is called when we reach some character data. Character data is text which is not part of an element, that is it is before, between and after elements. This includes whitespace, which is why we removed it earlier. For example in:
<username>w4rp3d1</username>
<status>Online</status>
<nickname>W4rp3d</nickname>
<avatar>
http://media.xfire.com/xfire/xf/images/avatars/gallery/default/xfire.gif
</avatar>
"w4rp3d1", "Online", "W4rp3d" and "http://media.xfire.com/xfire/xf/images/avatars/gallery/default/xfire.gif" are all charcter data. The function is passed the character data as a string.
We first (as in the last 2 functions) delcare the global variables we need. We then check if the $element variable is one of the three that contains the information we want, this is the reason we have a variable storing the last element. For example if $element is "nickname" it means we last encounters the element <nickname>, and any character data after that is (hopefully) going to be the nickname. If it is one of those three variables we set the element of the associative array with the key of th XML element name (which is in an element of $games itself) to the character data within it. So if we are on the second game encountered and the last XML element was "alltime" and the character data is "187200" then $games[1]['alltime'] will be set to "187200". The index is 1 for the second game because in PHP (and most other C-derived languages) array indices start at 0.
$parser = xml_parser_create ( );
xml_set_element_handler ( $parser, 'start_element', 'end_element' ); //set handler functions
xml_set_character_data_handler ( $parser, 'cdata' );
xml_parser_set_option ( $parser, XML_OPTION_CASE_FOLDING, FALSE ); //don't pass element names in uppercase
xml_parse ( $parser, $xml ); //parse!
xml_parser_free ( $parser );
Here is when we do the actual parsing of the XML, first we create an XML parser which we will use to parse the XML and store its resource handle in $parser. We then set some options, specifically tell it the names of the handler functions to use (the ones we just created earlier) and tell it not to pass element names as upper case (since in PHP string comparisons are case sensitive). We could have just all the names in upper case and left this out, but there was no point in changing them. We then run the xml_parse function which is the function that actually does the parsing and then free the parser (basically destroys it ).
//can now do whatever with the array of associative arrays that we have
$height = 48 * count ( $games ) + 10;
$image = imagecreate ( 300, $height );
$grey = imagecolorallocate ( $image, 40, 40, 40 );
$black = imagecolorallocate ( $image, 0, 0, 0 );
$white = imagecolorallocate ( $image, 255, 255, 255 );
imagefilledrectangle ( $image, 0, 0, 299, $height, $grey );
imagesetthickness ( $image, 3 );
imagerectangle ( $image, 0, 1, 299, $height - 3, $black );
$ycursor = 5;
Here is where we start making the image. First we work out what height the image should be (based on how many games we have) and store it in $height. We then create the image itself with imagecreate, which we give the width and height of the image (You can read more about the image functions here). Next we set some colours which we will use later these are just some decimal RGB values. We then draw a filled rectangle which covers the entire image and is the grey we just created, this is just there as a background. We then set the thickness (in pixels) of any lines we draw, which is used an the thickness of the lines in the (non-filled) rectangle we draw. This is just a border and is black. $ycursor is set to 5, this is used an a placemarker for how far down the image (in pixels) we should be putting things.
foreach ( $games as $game )
{
$gamename = 'Game: ' . $game['gamename'];
$thisweek = 'Time this week: ' . sprintf ( "%.1f", $game['thisweek'] / 3600 ) . ' hours';
$totaltime = 'Total time: ' . sprintf ( "%.1f", $game['alltime'] / 3600 ) . ' hours';
imagestring ( $image, 4, 5, $ycursor, $gamename, $white );
$ycursor += 16;
imagestring ( $image, 4, 5, $ycursor, $thisweek, $white );
$ycursor += 16;
imagestring ( $image, 4, 5, $ycursor, $totaltime, $white );
$ycursor += 16;
}
Here we cycle through each element of $games, which is put into $game. First we set the string we are going to write to the image, the sprintf is a way of formatting a string, in this case we set it to only show one decimal place (since when we divide by 3600 to get the times into hours we will probably get a very long number). We then put the string on the image, using font number 4 (there are 5 built-in fonts), 5 pixels across, howver many pixels down $ycursor has and in white. We then increase $ycursor by 16 (because in this font a line is 16 pixels high) and write out the next string. When we reach the end the loop will go on to the next game, or if this is the last or only element the loop ends.
header ( 'Content-type: image/png' );
imagepng ( $image );
?>
We output the content type header (so the browser knows it is a PNG image) and then the PNG data itself.
I haven't proof-read this so there are almost certainly mistakes, but hopefully you will get what I mean and if you don't understand something I can try to explain better. There are also a lot of improvements that could be made to this (aside from the aesthetic ones) such as chaching the XML data so it doesn't take so long to load the image which we are waiting for it to got get it.
Cooper has said how to fix this, although I'll just point out you don't specifically need PHP 4.3.x, anything after that should have the GD library as well.