Sort xml

Sorry if this shouldn’t be in this section…it’s a bit of a cross section question.

In a recent post I was told that I should use xsl in order to sort my xml feeds… however I am struggling to assemble everything correctly…


$xml = new SimpleXMLElement(
    'http://www.feedsite.asp?section=Weather&Type=B',
    null,
    true
);

foreach($xml->xpath("//subevent[@title='Hottest day of the year']/selection[@name]") as $item) {

    printf('%s = %f<br />', $item['name'], $item['backp1']);

} 

I’d get something like this:

July = 2.2
August = 2.8
September = 2.6

…and this is where I struggle to implement the xsl having been so far unable to customise any examples that I have found. If anyone could shed some light on this it would be greatly appreciated!

_with thanks

any joy cruncher?

I’ve been doing some research and some avenues have come up but I don’t know if they are viable… a promising one is using addChild() :- link.

…would anyone else like to solve this or have any suggestions, experience, etc.

_thanks all

thanks for the response oddz…

I have since tested with various feeds and am now convinced that this error was happening as the feeds coincidentally had the same values - in which case the duplication was removed… I’m dealing with what seems an incredibly temperamental xml source which may not be helping me…but you certainly have!

I really appreciate your response and I’ll let you know of the outcome of extensive testing, etc…

…oh incidentally maybe you can help here - do you know what would be the xPath for the following xml data if I was trying to obtain selection name (July & August) and its backp1 (2.20 and 2.80):


<event name="weather" date="08/11/2010">
  <subevent title="Hottest day of the year" id="97383">
     <selection name="July" id="53" backp1="2.20"/>
     <selection name="August" id="54" backp1="2.80"/>
  </subevent>
  <subevent title="Coldest day of the year" id="97384">
     <selection name="November" id="55" backp1="2.40"/>
     <selection name="December" id="56" backp1="1.90"/>
  </subevent>
</event>

I went for this but am not getting it for some reason:

<xsl:for-each select="//*[@title='Hottest day of the year']/selection">
  <row>
    <album_title><xsl:value-of select="@name"/></album_title>
    <album_artist><xsl:value-of select="@backp1"/></album_artist>
  </row>
</xsl:for-each>

_with thanks

oddz<< wanted to thank you for the post… I had time to look at it and do some playing - seems to be what I’m looking for… just trying to figure out how to style the final merged outcome - I’ve been looking around and can’t seem to get it quite right… would I have to create another xsl file? … or restyle the file ‘final.xsl’?

_thanks again

I heard of importing your xml doc into a dataset, then sort the
dataset and re-populate your xml, but I’m not sure how to code this or
is there another way of sorting a XML file? I not familiar with
Dataset coding and how that would work.
Here is my XML file:
>
?xml version=“1.0”?>
<IndividualListing>
<Individual>
<Name>Aaron, Scott</Name>
<Pager>3122220365@archwireless.net</Pager>
</Individual> by Martin Honnen

on second thoughts… this script doesn’t seem to work for multiple external feeds - does anyone know if this is the correct technique for obtaining, sorting and styling them?

_with thanks

thanks oddz! - seems I had named the first file ‘album.xml’ rather than ‘albums.xml’… quite irritatingly small to put you to the trouble. I’m going to attempt to adapt your workings to merge some external xml feeds - I’m thinking this can be done with your method - if can’t please let me know.

_with thanks

oddz…

… After seeing your post I was eager to test it and try to apply to it what I am doing… however I was unable to get your version working… outputting merge.php simply gave me ‘<rows/>’ on screen and I couldn’t get the results that you indicated (at the bottom of your post) - any idea what I could be doing wrong? - I have kept my file structure the same so I’m lost <<

with thanks_

There is a lot of logic here but it will do exactly what you want for the test data sets provided. The below merges two (though it could be any number) separate data sets, sorts them and even throws out repeats.

So first I’m going to explain the data sets I have created.

I have created two data sets for music albums. Each one differs in structure:

/datasets/albums.xml


<?xml version="1.0"?>
<albums>
	<album>
		<name>7th Symphony</name>
		<artist>Apocalyptica</artist>
	</album>
	<album>
		<name>Blindside</name>
		<artist>The Black Rose EP</artist>
	</album>
</albums>

/datasets/music.xml


<?xml version="1.0"?>
<music>
	<cd>
		<title>7th Symphony</title>
		<band>Apocalyptica</band>
	</cd>
	<cd>
		<title>Avenged Sevenfold</title>
		<band>Avenged Sevenfold</band>
	</cd>
	<cd>
		<title>Disturbed</title>
		<band>Asylum</band>
	</cd>
</music>

Each data set has a xsl file for normalizing its format. This is so that when the two are merged into a single document they have the same structure.

/transforms/albums.xsl


<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
	<xsl:template match="/">
		<rows>
		<xsl:for-each select="/albums/album">
			<row>
				<album_title><xsl:value-of select="name"/></album_title>
				<album_artist><xsl:value-of select="artist"/></album_artist>
			</row>
		</xsl:for-each>
		</rows>
	</xsl:template>
</xsl:stylesheet>

/transforms/music.xsl


<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
	<xsl:template match="/">
		<rows>
		<xsl:for-each select="/music/cd">
			<row>
				<album_title><xsl:value-of select="title"/></album_title>
				<album_artist><xsl:value-of select="band"/></album_artist>
			</row>
		</xsl:for-each>
		</rows>
	</xsl:template>
</xsl:stylesheet>

The last xsl file is the one to be applied to the merged data that sorts it once it has been combined. I also added a a filter for repeats. This is so that given both data sets have the same album it only appears once in the result set ( you’ll see my comments for that).

/final.xsl


<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fn="http://www.w3.org/2005/xpath-functions">
	
	<xsl:key name="parsed-rows" match="row" use="concat(album_title,'-',album_artist)"/>

	<xsl:param name="global_sort_col" select="'artist'"/>
	<xsl:param name="global_sort" select="desc"/>

	<xsl:template match="/">
		<rows>
			<xsl:choose>
				<xsl:when test="$global_sort = 'asc' and $global_sort_col = 'title'">
					<xsl:apply-templates select="/root/rows/row">
						<xsl:sort select="album_title" order="ascending"/>
					</xsl:apply-templates>	
				</xsl:when>
				<xsl:when test="$global_sort = 'desc' and $global_sort_col = 'title'">
					<xsl:apply-templates select="/root/rows/row">
						<xsl:sort select="album_title" order="descending"/>
					</xsl:apply-templates>	
				</xsl:when>
				<xsl:when test="$global_sort = 'asc' and $global_sort_col = 'artist'">
					<xsl:apply-templates select="/root/rows/row">
						<xsl:sort select="album_artist" order="ascending"/>
					</xsl:apply-templates>					
				</xsl:when>
				<xsl:otherwise>
					<xsl:apply-templates select="/root/rows/row">
						<xsl:sort select="album_artist" order="descending"/>
					</xsl:apply-templates>				
				</xsl:otherwise>
			</xsl:choose>
		</rows>
	</xsl:template>
	
	<xsl:template match="row">
		<xsl:comment>
		Throw out repeats based on artist name and album title combinations
		</xsl:comment>
		<xsl:if test="generate-id() = generate-id(key('parsed-rows',concat(album_title,'-',album_artist)))">
			<row>
				<album_title><xsl:value-of select="album_title"/></album_title>
				<album_artist><xsl:value-of select="album_artist"/></album_artist>
			</row>
		</xsl:if>
	</xsl:template>
	
</xsl:stylesheet>

Lastly, there is the PHP file that handles all the logic. You can go ahead and read the comments to get a better idea of what is being done. However, in a nutshell both data sets are normalized, added to a new xml document than the final sorting transformation is applied. This results in the final output. The script may also feed the sort and sort column to the final transformation, as this is dynamic using parameters.

/merge.php


<?php
/*
* Load XML data sets
*/
$albums = new DOMDocument();
$music = new DOMDocument();

$albums->load('datasets/albums.xml');
$music->load('datasets/music.xml');

/*
* Load XSL normalzation transformations
*/
$albums_xsl = new DOMDocument();
$music_xsl = new DOMDocument();

$albums_xsl->load('transforms/albums.xsl');
$music_xsl->load('transforms/music.xsl');

/*
* Normalize data sets
*/
$albums_processor = new XSLTProcessor();
$music_processor = new XSLTProcessor();

$albums_processor->importStyleSheet($albums_xsl);
$music_processor->importStyleSheet($music_xsl); 

$albums_normalized = $albums_processor->transformToDoc($albums);
$music_normalized = $music_processor->transformToDoc($music);

/*
* Merge into new document
*/
$merged = new DOMDocument();
$wrap = $merged->createElement('root');
$merged->appendChild($wrap);

/*
* Add normalized documents as children of wrapper
*/
$wrap->appendChild($merged->importNode($albums_normalized->documentElement,true));
$wrap->appendChild($merged->importNode($music_normalized->documentElement,true));

/*
* Load final stylesheet transform
*/
$final = new DOMDocument();
$final->load('final.xsl');

/*
* Apply XXL sorting translation for final output
*/
$final_processor = new XSLTProcessor();

$final_processor->importStyleSheet($final);

// pass sort column and order
$final_processor->setParameter('','global_sort_col','title');
$final_processor->setParameter('','global_sort','desc');

// apply transform and return dom document
$sorted = $final_processor->transformToDoc($merged);

$sorted->formatOutput = true;
$sorted->preserveWhiteSpace = true;

/*
* Show final XML although you could do anything at this point
*/
header('content-type: text/xml');
echo $sorted->saveXML();
?>

If you create the same file structure as shown you will see the merge in action. However, the result with the supplied files and sorting parameters is shown below.

  • Take note of even though Apocalyptica:7th symphony exists in both data sets there is only one in the result set due to the filter conditional within /final.xsl.

<?xml version="1.0"?>
<rows xmlns:fn="http://www.w3.org/2005/xpath-functions">
  <row>
    <album_title>Disturbed</album_title>
    <album_artist>Asylum</album_artist>
  </row>
  <row>
    <album_title>Blindside</album_title>

    <album_artist>The Black Rose EP</album_artist>
  </row>
  <row>
    <album_title>Avenged Sevenfold</album_title>
    <album_artist>Avenged Sevenfold</album_artist>
  </row>
  <row>

    <album_title>7th Symphony</album_title>
    <album_artist>Apocalyptica</album_artist>
  </row>
</rows>

Its probably a lot to take in, but this seems like the best approach. At least in my eyes.

The below is the file structure I was working with for these files. So if you mimic this you will be able to see it in action on your own environment and play around with it.

File Structure (root relative)

  1. merge.php
  2. final.xsl
  3. datasets
    [list=*]
  4. albums.xml
  5. music.xml
    [/list]
  6. transforms
    [list=*]
  7. albums.xsl
  8. music.xsl
    [/list]

Hope that helps or at least gives you an idea of how to implement it for your situation.

actually with the time since your last post the response was unexpected - thanks kindly when you can!

I have had some unexpected work come up, but I haven’t forgotten about your question. I will post a response as soon as I can.

Perhaps you could post some snippets of the data sets your working with. The process I provided is reliant on the idea that after the transformations have been applied they are all in the same structure. If one is not in the same structure it won’t work. Similar to the concept of an interface, if they look the same they can be treated the same. However, the they must first all look the same. The proper valid and tested normalization transformations must be in place to get this to work. If the transformations aren’t correct or you haven’t actually tested them on an individual basis, than no, its probably not going to work.

Are you wanting it filtered or multiple posts? If you want multiple posts; you should have an XML file that possibly includes something like recordId like you would in a database to distinguish from one post to another. An XSD would also be worthwhile so you could use it in a database or share with others easily.

If you want to filter the posts, you have two options that I know of; you can use Visual Studio which has an XML control and use an XML file as the data source. With that, you can set your sorts and filter; but if you want something that’s platform independent, you should look into XQuery. XQuery is more complicated and I have just started using it, so my skill set is not yet what it needs to be to help you with XQuery itself.

However, here is a free online tutorial on XQuery: http://www.w3schools.com/xquery/default.asp

Ignore this part, if you are not confused:

It is important to note that you need to create an XML document first so I can work you through this example. You don’t have to use real data, just use data that uses the same data types as the document that you want to display.

If you aren’t following along on how to set up the XML, XSL and XSD interactively, I can walk you through it. The first step I would need you to do is create an XML file of your data. Normally, I would say and XSD, but if you are unfamiliar with how these work together, it might be easier to go this route. Here is what we will do once you have an XML file ready:

  1. Create an XSD: so that we have a structure to verify all data entered is valid with integrity.
  2. Create the XML file and then reference the XSD
  3. Enter data into the XML file; the XSD will ensure everything is entered appropriately and required fields aren’t ignored.
  4. Create the XSL structure to determine how to display the information on the web page.
  5. Display a finished product in an HTML page for the sake of simplicity and to have a working example at hand.

thanks alot! … meanwhile I’ll be trying to learn what I can about XLink, etc…

thanks for the detailed response…

I hope you are clear about what I am trying to do - it does sound like it…

… as the multiple (to begin with 2) XML files are feeds from other sites (i.e. not on my server) I believe I am right in saying XSD seems the most viable option…

…an immediate problem seems to be however that I would need to convert a feed file to XSD and not my own XML file… (perhaps you misunderstood me for this) but for the cause of trying to expand my limited knowledge on this new topic (XSD) suppose the XML was:

<subevent title="Hottest day of the year" id="97383">
   <selection name="July" id="53" backp1="2.20"/>
   <selection name="August" id="54" backp1="2.80"/>
</subevent>
<subevent title="Coldest day of the year" id="97384">
   <selection name="November" id="55" backp1="2.40"/>
   <selection name="December" id="56" backp1="1.90"/>
</subevent>

I know of some sites that convert XML to XSD but it is probably better to understand why/how, etc… so if you can walk me through it as you kindly offered I would be grateful but I should also reiterate that the motive of my learning is to solve the original dilemma of sorting multiple EXTERNAL XML feeds.

_with great thanks

We probably need to back up for a moment. XSD is all about data types. From what your saying, the information is from elsewhere. In that case you are wanting to do something a little different. You might want to use something called Hijax, but I need to ensure that is correct.

Another alternative, you might want to look at and if this resolves your issue could be the easiest; try Yahoo Pipes.

Now back to the issue at hand; there may be some need to use XPath, XLink, and XPointer.

When you sorted you used XPath. Now XLink can point to an XML element including external sources. XPointer points to a specific part of a document.

So my question is: Are you trying to acquire information from multiple sites and sort them?

Chris

yes - exactly

Okay, I will work on an example and post it once complete.

thanks oddz - it seems a little clearer now…

$objXML = simplexml_load_file('bev.xml');

… in the example however it was for a file on the server but what if the file was from another site?

_thanks

Getting to grips with it a little now - I can actually get the external feed and sort it (below) but does anyone know how I could incorporate a second external feed - I’ve searched but nothing really seems to fit in with this <<

>> thanks all

// Load the XML source
$xml = new DOMDocument;
$xml->load('http://www.feedsite.asp?section=Weather&Type=B');

$xsl = new DOMDocument;
$xsl->loadXML('<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="/">
  <html>
  <body>
    <h2>Event</h2>
    <table border="1">
      <tr bgcolor="#9acd32">
        <th>Month</th>
        <th>Back</th>
      </tr>
      <xsl:for-each select="temperature/event/subevent/selection">
      <xsl:sort select="@backp1" data-type="number"/>
      <tr>
        <td><xsl:value-of select="@month"/></td>
        <td><xsl:value-of select="@backp1"/></td>
      </tr>
      </xsl:for-each>
    </table>
  </body>
  </html>
</xsl:template>

</xsl:stylesheet>');

// Configure the transformer
$proc = new XSLTProcessor;
$proc->importStyleSheet($xsl); // attach the xsl rules

$sorted = $proc->transformToDoc($xml);
$sorted->formatOutput = true;
$sorted->preserveWhiteSpac