« | »

Sort any XML file in PHP with one XSL

I feel I’ve now earned the particular distinction of having participated in a cage match with an XML file and beaten its bloody face in. Sure, they’re pretty nifty for storing collections of data and laying them out on web sites, but you don’t get the full experience until you have to create a web interface to edit, insert, delete, and sort said XML file. Honestly, it would have been easier just to store the data in a PHP array. But hell, I love a good cage match! My biggest problem was that anytime I would edit or insert an event element, it would appear in the XML file as the last node rather than in date order. I figured out how to sort the XML contents as they were rendered in HTML using an XSLT (EXtensible Stylesheet Language Transformation). This worked fine for one display but sadly, this XML needed to be displayed several different ways in different places and I wasn’t thrilled with the thought of having to create an XSL file for every variation. All I really needed was one XSL file to sort the XML data each time it was updated. So I wrapped rope around my hands, coated them with resin, dipped them in broken glass, and stepped into the cage.

There is a dearth of good documentation with examples on just how to do this. I spent hours running Google searches and browsing forums gaining a small bit of relevant info each time, but never enough for the whole picture. Most of this had to be figured out through good old fashioned trial and error. And damn there were a lot of errors. Quite often I would destroy the entire XML file and would have to copy it again from backup. I felt quite jubilant when I finally created an XSL that would take my unsorted XML file and return the sorted one. But being the glutton for punishment that I am, I decided I didn’t want to stop there. I knew you could pass variables to XSL files so why couldn’t I create an XSL file that could sort any XML file I pass to it on whatever field I wanted?

And damned if this wasn’t way harder than I could have ever imagined. I had to find a way to remove everything in my XSL that was specific to my event XML date sort and pass a variable for it instead. Turns out there are many element attributes in XSL that refuse to use a variable or require special formatting for their variables. Anyhow, after lots of blood, sweat, tears, and retrieved XML files from backup, I finally nailed the sucker down. First I’m going to show a generic bit of XML to use as an example, then show the XSL file, and then the PHP function I used to perform the sort. Here’s a pretty simple XML sample document called events.xml:

<?xml version="1.0" encoding="iso-8859-1"?>
<events>
	<event>
		<name>Easter</name>
		<date>20100404</date>
	</event>
	<event>
		<name>Christmas</name>
		<date>20101225</date>
	</event>
	<event>
		<name>Halloween</name>
		<date>20101031</date>
	</event>
</events>

And here is my blood stained XSL document called sortxml.xsl:

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" version="1.0" encoding="iso-8859-1" indent="yes"/>
<xsl:template match="*|text()|@*">
  <xsl:copy>
    <xsl:apply-templates select="*|text()|@*"/>
  </xsl:copy>
</xsl:template>
<xsl:template match="/*">
  <xsl:copy>
     <xsl:apply-templates select="@*"/>
     <xsl:apply-templates select="*[name() = $child]">
       <xsl:sort select="*[name() = $sortby]" data-type="{$type}" order="{$order}" />
     </xsl:apply-templates>
  </xsl:copy>
</xsl:template>
</xsl:stylesheet>

And finally, here is the PHP function which makes it all happen:

function sortxml( $xmlfile, $sortby, $type = 'number', $order = 'ascending', $child ) {
	// $type: number|text|qname
	// $order: ascending|descending
	$xp = new XSLTProcessor();
	$xsl = new DomDocument;
	$xsl->load('sortxml.xsl');
	$xp->importStylesheet($xsl);
	$xml_doc = new DomDocument;
	$xml_doc->load($xmlfile);
	$xp->setParameter('', 'sortby', $sortby);
	$xp->setParameter('', 'type', $type);
	$xp->setParameter('', 'order', $order);
	$xp->setParameter('', 'child', $child);
	$sorted = $xp->transformToXML($xml_doc);
	$fh = fopen($xmlfile, 'w') or die("can't open file");
	fwrite($fh, $sorted);
	fclose($fh);
}

To sort an XML file, just call the sortxml() function with the arguments you need to get the desired result. For example, to sort my events.xml chronologically (ascending) by date I would call the function as follows:

sortxml('events.xml', 'date', 'number', 'ascending', 'event' );

The first argument is the location of the XML file. The second specifies the node containing the value to be sorted on. The third establishes the data-type (text or number) of that value. The fourth sets the sort-order (ascending or descending). The last identifies which nodes are to be sorted. After calling my sortxml() function, my events.xml file now looks like this:

<?xml version="1.0" encoding="iso-8859-1"?>
<events>
	<event>
		<name>Easter</name>
		<date>20100404</date>
	</event>
	<event>
		<name>Halloween</name>
		<date>20101031</date>
	</event>
	<event>
		<name>Christmas</name>
		<date>20101225</date>
	</event>
</events>

If I wanted to sort the file alphabetically by event name, I would call the function this way:

sortxml('events.xml', 'name', 'text', 'ascending', 'event' );

And my events.xml file would then look like this:

<?xml version="1.0" encoding="iso-8859-1"?>
<events>
	<event>
		<name>Christmas</name>
		<date>20101225</date>
	</event>
	<event>
		<name>Easter</name>
		<date>20100404</date>
	</event>
	<event>
		<name>Halloween</name>
		<date>20101031</date>
	</event>
</events>

And there you have it. I wish I could have run into a blog post like this one a few weeks ago. It would have made my life a lot easier – at least for my particular problem. I haven’t tested this on any other XML files and I can’t guarantee it will work for you like it works for me. You’ll have to fight your own cage match mate! But if you have any questions, feel free to comment below. Cheers.

This entry was posted on Friday, September 3rd, 2010 at 1:42 pm and is filed under PHP Scripts. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

11 Responses to Sort any XML file in PHP with one XSL

  • Rahul Desai says: January 26, 2011 at 5:05 pm

    THANK YOU THANK YOU THANK YOU SO MUCH!!! This was EXACTLY what I needed!!! With your permission, I'd like to use this code in my project.

  • Πολιορκητικα says: January 26, 2011 at 5:15 pm

    Knock yourself out. I am very pleased that this is the most popular post of my humble little blog.

  • Bill says: June 29, 2011 at 7:22 pm

    You're so popular you got ripped off by this site... http://aiminstitute.org/blog/2011/04/sort-any-xml-file-in-php-with-this-xsl/

  • Πολιορκητικα says: June 29, 2011 at 7:53 pm

    Thanks for the heads up. I really appreciate it. However, the AIM Institute used this content with my full consent. I just hope it can help out someone else struggling with XML sorting.

  • Bill says: June 29, 2011 at 9:47 pm

    Ah, well they should credit you for this wonderful help. I spent the past 2 hours trying to figure out exactly what I did wrong with the script to find out that my server didn't have php_xls enabled, as soon as I turned that on, it worked like a charm. Thank you VERY much for this.

  • Luke says: September 1, 2011 at 8:18 am

    Hi, this looks like a great script and is exactly what im looking for, however it doesnt work with my large xml file says out of memory on line 633732 which is a real shame, has anyone else ran into this problem? is there a solution apart from reducing the size of my xml? again great work just hope to be able to use it one day :) keep up the good work!!!

  • Elano Garcez says: July 5, 2012 at 2:47 pm

    I could not make it work with this xml:
    http://steamcommunity.com/id/ice_haku/games?tab=all&xml=1&l=english

    here is my code:
    load('sortxml.xsl');
    $xp->importStylesheet($xsl);
    $xml_doc = new DomDocument;
    $xml_doc->load($xmlfile);
    $xp->setParameter('', 'sortby', $sortby);
    $xp->setParameter('', 'type', $type);
    $xp->setParameter('', 'order', $order);
    $xp->setParameter('', 'child', $child);
    $sorted = $xp->transformToXML($xml_doc);
    $fh = fopen($xmlfile, 'w') or die("can't open file");
    fwrite($fh, $sorted);
    fclose($fh);
    }
    sortxml("games.xml", 'appID', 'number', 'ascending', 'game' );
    ?>
    any hint =/ ?

  • Πολιορκητικα says: July 6, 2012 at 11:29 am

    It's been a long time since I've worked with this script, however from what you've shown here, I don't see where you define the sortxml() function. It was declared as a function so it could be called frequently to run different sorts. If you want to run it inline, instead of calling the function sortxml() at the end with the variables, just put them into the various parameters.

  • Hal Thompson says: June 25, 2014 at 5:43 pm

    I just wanted to say thanks for wrestling with this little chunk of code. I was looking for a nice, easy to implement, solution to this problem and I found your blog, the code worked perfectly.

  • Πολιορκητικα says: June 27, 2014 at 6:39 am

    I'm glad to hear people are still finding it helpful after four years!

  • Sylvain says: March 17, 2016 at 6:49 pm

    Thank you for your code, it made my ideas clearer !



  • Leave a Reply