I am trying to group sibling data in an XML file.
Given :
Here is my attempt. One assumption I have made which simplifies things is that timeline elements with a specific text value are already unique.
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" />
<xsl:template match="/data">
<data>
<xsl:apply-templates select="competition" />
</data>
</xsl:template>
<xsl:template match="competition">
<xsl:for-each select="timeline">
<timeline time="{text()}">
<xsl:copy-of
select="./following-sibling::fixture[count(preceding-sibling::timeline[1] | current()) = 1]" />
</timeline>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The above is edited to use current() instead of a variable as per Tomalak's suggestion.
With help from g andrieu I had to make it only get the next item and not the list following:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="competition" >
<xsl:apply-templates select="timeline" />
</xsl:template>
<xsl:template match="timeline">
<timeline>
<xsl:attribute name="time" >
<xsl:value-of select="." />
</xsl:attribute>
<xsl:apply-templates select="following-sibling::*[1]" mode="copy"/>
</timeline>
</xsl:template>
<xsl:template match="fixture" mode="copy">
<fixture>
<xsl:value-of select="." />
</fixture>
<xsl:apply-templates select="following-sibling::*[1]" mode="copy"/>
</xsl:template>
<xsl:template match="timeline" mode="copy" />
</xsl:stylesheet>
The following xslt will work even if same timelines are scattered in multiple places. For e.g. in the foll xml there are 2 entries for timeline 10:00
<?xml version="1.0" encoding="UTF-8"?>
<data>
<competition>
<timeline>10:00</timeline>
<fixture>team a v team b</fixture>
<fixture>team c v team d</fixture>
<timeline>12:00</timeline>
<fixture>team e v team f</fixture>
<timeline>16:00</timeline>
<fixture>team g v team h</fixture>
<fixture>team i v team j</fixture>
<fixture>team k v team l</fixture>
<timeline>10:00</timeline>
<fixture>team a v team b new</fixture>
<fixture>team c v team d new</fixture>
</competition>
</data>
Xslt:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:key name="TimelineDistint" match="timeline" use="."/>
<xsl:template match="data">
<xsl:apply-templates select="competition"/>
</xsl:template>
<xsl:template match="competition">
<data>
<competition>
<xsl:for-each select="timeline[generate-id() = generate-id(key('TimelineDistint', .)[1])]">
<timeline>
<xsl:variable name="varTimeline" select="."/>
<xsl:attribute name="time"><xsl:value-of select="normalize-space(.)"/></xsl:attribute>
<xsl:for-each select="../fixture[preceding::timeline[1] = $varTimeline]">
<fixture>
<xsl:value-of select="normalize-space(.)"/>
</fixture>
</xsl:for-each>
</timeline>
</xsl:for-each>
</competition>
</data>
</xsl:template>
</xsl:stylesheet>
Output:
<?xml version="1.0" encoding="UTF-8"?>
<data>
<competition>
<timeline time="10:00">
<fixture>team a v team b</fixture>
<fixture>team c v team d</fixture>
<fixture>team a v team b new</fixture>
<fixture>team c v team d new</fixture>
</timeline>
<timeline time="12:00">
<fixture>team e v team f</fixture>
</timeline>
<timeline time="16:00">
<fixture>team g v team h</fixture>
<fixture>team i v team j</fixture>
<fixture>team k v team l</fixture>
</timeline>
</competition>
</data>
This is easy to do when the following is true (which I assume it is):
<timeline>
s within a <competition>
are unique<fixture>
s right after a given <timeline>
belong to it<fixture>
without a <timeline>
element before itThis XSLT 1.0 solution:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:key name="kFixture"
match="fixture"
use="generate-id(preceding-sibling::timeline[1])"
/>
<xsl:template match="data">
<xsl:copy>
<xsl:apply-templates select="competition" />
</xsl:copy>
</xsl:template>
<xsl:template match="competition">
<xsl:copy>
<xsl:apply-templates select="timeline" />
</xsl:copy>
</xsl:template>
<xsl:template match="timeline">
<xsl:copy>
<xsl:attribute name="time">
<xsl:value-of select="." />
</xsl:attribute>
<xsl:copy-of select="key('kFixture', generate-id())" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
produces:
<data>
<competition>
<timeline time="10:00">
<fixture>team a v team b</fixture>
<fixture>team c v team d</fixture>
</timeline>
<timeline time="12:00">
<fixture>team e v team f</fixture>
</timeline>
<timeline time="16:00">
<fixture>team g v team h</fixture>
<fixture>team i v team j</fixture>
<fixture>team k v team l</fixture>
</timeline>
</competition>
</data>
Note the use of an <xsl:key>
to match all <fixture>
s that belong to ("are preceded by") a given <timeline>
.
A slightly shorter but less obvious solution would be a modified identity transform:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:key name="kFixture"
match="fixture"
use="generate-id(preceding-sibling::timeline[1])"
/>
<xsl:template match="* | @*">
<xsl:copy>
<xsl:apply-templates select="*[not(self::fixture)] | @*" />
</xsl:copy>
</xsl:template>
<xsl:template match="timeline">
<xsl:copy>
<xsl:attribute name="time">
<xsl:value-of select="." />
</xsl:attribute>
<xsl:copy-of select="key('kFixture', generate-id())" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Try something like that :
<xsl:template match="timeline">
<timeline>
<xsl:attribute name="time" >
<xsl:value-of select="." />
</xsl:attribute>
<xsl:apply-templates select="following-sibling::*[name()=fixture][1]" />
</timeline>
</xsl:template>
<xsl:template match="fixture">
<fixture>
<xsl:value-of select="." />
</fixture>
<xsl:apply-templates select="following-sibling::*[name()=fixture][1]" />
</xsl:template>
G Andrieu's solution doesn't work, as there is no such axes as 'next-sibling' unfortunately.
And alternative solution would be the following:
<xsl:template match="timeline">
<timeline>
<xsl:attribute name="time" >
<xsl:value-of select="." />
</xsl:attribute>
<xsl:apply-templates select="following-sibling::*[local-name()='fixture' and position()=1]" />
</timeline>
</xsl:template>
<xsl:template match="fixture">
<fixture>
<xsl:value-of select="." />
</fixture>
<xsl:apply-templates select="following-sibling::*[local-name()='fixture' and position()=1]" />
</xsl:template>