Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XSLT Grouping Siblings

Tags:

xml

xslt

I am trying to group sibling data in an XML file.

Given :

<?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>
    </competition>
</data>

I am trying to produce :

<?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>
        </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>

I am using the following 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: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::*" mode="copy"/>

        </timeline>
    </xsl:template>

    <xsl:template match="fixture" mode="copy">
        <fixture>
            <xsl:value-of select="." />
        </fixture>
    </xsl:template>

    <xsl:template match="timeline" mode="copy">
        <xsl:apply-templates select="following-sibling::*" mode="null" />
    </xsl:template>

    <xsl:template match="*" mode="null">
    </xsl:template>
</xsl:stylesheet>

My problem is that it is not stopping processing fixture nodes when it gets to the next timeline

like image 789
Xetius Avatar asked Jun 03 '09 08:06

Xetius


People also ask

What is following sibling in XSLT?

The following-sibling axis indicates all the nodes that have the same parent as the context node and appear after the context node in the source document.

What is current group () in XSLT?

Returns the contents of the current group selected by xsl:for-each-group. Available in XSLT 2.0 and later versions. Available in all Saxon editions. current-group() ➔ item()*

What is Number () in XSLT?

Specifies the format pattern. Here are some of the characters used in the formatting pattern: 0 (Digit) # (Digit, zero shows as absent)


2 Answers

This is easy to do when the following is true (which I assume it is):

  • all <timeline>s within a <competition> are unique
  • only the <fixture>s right after a given <timeline> belong to it
  • there is no <fixture> without a <timeline> element before it

This 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>
like image 121
Tomalak Avatar answered Sep 20 '22 05:09

Tomalak


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.

like image 31
AnthonyWJones Avatar answered Sep 22 '22 05:09

AnthonyWJones