3

I am currently trying to access the data from multiple XML files. I've easily accessed data from the first known as Rainfall.xml but have been unable to retrieve any data from the next file in my list Max_temp.xml.

The overall objective is to combine 4-5 XML files together to include all of the data about various weather events and also the station which these events were recorded at.

The example code is below:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method="xml"/>

    <!-- TODO customize transformation rules 
         syntax recommendation http://www.w3.org/TR/xslt 
    -->
<xsl:variable name="maxTemp" select="document('Max_temp.xml')" />  

<xsl:template match="rainfall">
&lt;weather&gt;
      <xsl:apply-templates select="measurement" />
&lt;/weather&gt;

</xsl:template>


<xsl:template match="measurement">
    &lt;measurement&gt;
        &lt;StationNum&gt;<xsl:value-of select="StationNum"/>&lt;/StationNum&gt;
        &lt;Date&gt;<xsl:value-of select="concat(Day,'/',Month,'/',Year)"/>&lt;/Date&gt;
        <xsl:variable name="Date" select="concat(Day,'/',Month'/',Year)"/>
        &lt;Rainfall&gt;<xsl:value-of select="Volume"/>&lt;/Rainfall&gt;
        &lt;MaxTemp&gt;<xsl:value-of select="$MaxTemp/maxTemp/measurement[concat(Day,'/',Month'/',Year)].equals(Date)"/>&lt;/MaxTemp&gt;
    &lt;/measurement&gt;
</xsl:template>

</xsl:stylesheet>

The structure of the XML files being used is as follows:

<typeOfFile(Rainfall, Temp, Solar Radiation etc)>
    <measurment>
        <Code>...</Code>
        <Station>...</Station>
        <Day>...</Day>
        <Month>...</Month>
        <Year>...</Year>
        <Volume>...</Volume>
    </measurement>
</typeOfFile>

I am currently getting a non-response from the browser when trying to load the corresponding Rainfall.xml file which this XSL sheet styles.

Can someone please point me in the right direction? Also if anyone can refer me to some information about using an XSL sheet to create and format an XML file it'd be much appreciated.

Rory Perry
  • 97
  • 1
  • 10
  • What is the purpose of `<weather>`, `<measurement>` and so on? To output tags?? More use the variable `$MaxTemp`, but you declared *maxTemp* - XSLT is case-sensitive. – potame Sep 25 '15 at 07:03

1 Answers1

4

The following approach would work (XSLT 1.0):

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method="xml"/>

    <!-- select the <measurement> elements of all the various input files -->
    <xsl:variable name="maxTemp" select="document('Max_temp.xml')/*" />
    <xsl:variable name="rainfall" select="document('rainfall.xml')/*" />
    <xsl:variable name="solarRadiation" select="document('solar_radiation.xml')/*" />

    <!-- index <measurement> elements by their station and date -->
    <xsl:key name="kMeasurement" match="measurement"
        use="concat(Station, '/', Day, '/', Month, '/', Year)"
    />

    <xsl:template match="/">
        <weather>
            <xsl:apply-templates select="$maxTemp/measurement" />
        </weather>
    </xsl:template>

    <xsl:template match="measurement">
        <xsl:variable name="currentKey" select="concat(Station, '/', Day, '/', Month, '/', Year)" />

        <measurement>
            <StationNum><xsl:value-of select="Station"/></StationNum>
            <Date><xsl:value-of select="concat(Day, '/', Month, '/', Year)"/></Date>

            <!-- since we are handling maxTemp measurements here, we can output that directly -->
            <MaxTemp><xsl:value-of select="Value"/></MaxTemp>

            <!-- to access the others we need a context switch and a key lookup -->
            <xsl:for-each select="$rainfall">
                <Rainfall><xsl:value-of select="key('kMeasurement', $currentKey)/Volume"/></Rainfall>
            </xsl:for-each>
            <xsl:for-each select="$solarRadiation">
                <SolarRadiation><xsl:value-of select="key('kMeasurement', $currentKey)/Watt"/></SolarRadiation>
            </xsl:for-each>
            <!-- and so on -->
        </measurement>
    </xsl:template>
</xsl:stylesheet>

You would apply it against an empty dummy input document (something like a simple <xml />).

It then loads all the actual documents into variables and follows the entries in one of them to create the output. I chose the max temp measurements, but if all files contain data points for the same dates it won't matter which one.

Each of those max temp data points generates one output <measurement> element.

For this it uses an <xsl:key> to pull the correct measurement out of the related documents. The <xsl:key> is a key/value store (i.e. dictionary, hashtable): it indexes the nodes by a certain key string, in our case the combination of station ID and date.

But it returns only those nodes that are in the same document as the current node. Therefore, to output anything outside of Max_temp.xml, we must switch the context to another document. The <xsl:for-each> does that, so we use it here to set the scope for our call to key() ($rainfall and $solarRadiation only hold a single element anyway).

Note that since I had to guess at your actual input document structure, a few XPaths might be off. Adapt them accordingly.

Tomalak
  • 332,285
  • 67
  • 532
  • 628
  • Thanks a bunch that helps a ton, I've it doing what I want it to now. Getting toward the next part how would I go about writing this to a new file known as "weather.xml" I'm assuming it would have something to do with the document() function? – Rory Perry Sep 25 '15 at 12:26
  • `document()` is purely for reading documents. XSLT is a *transformation* process. It transforms a single input document (in the above case, an empty one) into a single output document. You can do whatever you like with that resulting output, including saving it to disk, but that part is outside of the XSLT processor's domain. It's the job of your host programming language or -tool to do that. – Tomalak Sep 25 '15 at 12:39
  • 1
    Aha! I've got it now, thanks for that. Didn't quite understand that I had to save it to see it outputed as XML. Now to just format it :) – Rory Perry Sep 25 '15 at 12:54
  • Wondering if you can help me work out how to make a key where files share separate structures? The next file I have is a station file that looks as follows: 1000 Karunjie 16.29 127.2 WA I need to pull the data down for a specific station and make it an attribute of my element. – Rory Perry Sep 29 '15 at 05:28
  • @RoryPerry What have you got so far? (If you are unsure how keys work in the first place, read the lower part of [an older answer of mine](http://stackoverflow.com/a/955527/18771), where I take a detour to explain them.) BTW, you don't necessarily need a key to get information, regular XPath might work just as well. – Tomalak Sep 29 '15 at 08:44
  • So basically I need to obtain the station data for a specific station/site (Has a 6 digit id number) none of the other stations are relevant for now. – Rory Perry Sep 29 '15 at 09:10
  • If all stations are in one file, "station data for a specific site" is a very trivial XPath expression, if you think about it for a minute. – Tomalak Sep 29 '15 at 09:27
  • Sort of looking at it more like this: matching it using a predicate pretty much like .station[site = '081123'] – Rory Perry Sep 29 '15 at 09:55
  • Yes, something along the lines of this. Note that there is the `current()` XSLT function that helps you reference the current node inside an XPath predicate. – Tomalak Sep 29 '15 at 10:48