Search code examples
xmltextxslt-1.0muenchian-grouping

Transform XML to Text file


I would like to convert the following XML to a text file, so that the type and date of the cars appear only once in the header, but the times are listed below. And the header is created with a daily breakdown. I tried grouping first and without grouping, but so far I can't find the solution. Please advise how I can do this. Thanks.

input XML:

<?xml version="1.0" encoding="UTF-8"?>
<data>
    <item>
        <Date>2023-02-19</Date>
        <Cars>Ford</Cars>
        <SellTarget>A4+</SellTarget>
        <Time>10:40:09</Time>
        <avg>19.3464027998</avg>
    </item>
    <item>
        <Date>2023-02-19</Date>
        <Cars>Ford</Cars>
        <SellTarget>A4+</SellTarget>
        <Time>11:21:56</Time>
        <avg>32.7150023474</avg>
    </item>
    <item>
        <Date>2023-02-19</Date>
        <Cars>Ford</Cars>
        <SellTarget>A4+</SellTarget>
        <Time>19:01:27</Time>
        <avg>554.0289810087</avg>
    </item>
    <item>
        <Date>2023-02-19</Date>
        <Cars>Ford</Cars>
        <SellTarget>A4+</SellTarget>
        <Time>23:15:26</Time>
        <avg>46.1398232343</avg>
    </item>
    <item>
        <Date>2023-02-19</Date>
        <Cars>Opel</Cars>
        <SellTarget>A4+</SellTarget>
        <Time>14:05:51</Time>
        <avg>41.7428144493</avg>
    </item>
    <item>
        <Date>2023-02-19</Date>
        <Cars>Opel</Cars>
        <SellTarget>A4+</SellTarget>
        <Time>15:01:02</Time>
        <avg>65.6303001034</avg>
    </item>
    <item>
        <Date>2023-02-19</Date>
        <Cars>Opel</Cars>
        <SellTarget>A4+</SellTarget>
        <Time>02:00:00</Time>
        <avg>1.2954721559</avg>
    </item>
</data>

XSLT:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method="text" encoding="UTF-8"/>

    <xsl:key name="carsgroup" match="/data/item" use="concat(Cars, '|', Date)" />

    <xsl:variable name="tab" select="'&#09;'"/>
    <xsl:variable name="newLine" select="'&#10;'"/>
    <xsl:variable name="csvSeparator" select="$tab"/>
    
    <xsl:template match="/">
        <xsl:call-template name="headerlines"/>
        <xsl:apply-templates select="data/item[generate-id() = generate-id(key('carsgroup', 
            concat(Cars, '|', Date)))]" mode="dataRows">
        </xsl:apply-templates>
    </xsl:template>
    
    <xsl:template name="headerlines">
        <!-- 1. line is Skipped -->
        <xsl:call-template name="csv">
            <xsl:with-param name="val" select="''"/>
        </xsl:call-template>
        <!-- 2. line is Skipped-->
        <xsl:call-template name="csv">
            <xsl:with-param name="val" select="''"/>
        </xsl:call-template>
        <!-- 3. line is Skipped -->
        <xsl:call-template name="csv">
            <xsl:with-param name="val" select="''"/>
        </xsl:call-template>
        <!-- 4. line is Skipped -->
        <xsl:call-template name="csv">
            <xsl:with-param name="val" select="''"/>
            <xsl:with-param name="isLastCol" select="true()"/>
        </xsl:call-template>
    </xsl:template>

    <xsl:template match="item" mode="dataRows">
        <!-- at 5. line the first 5 char is skipped -->
        <xsl:call-template name="csv">
            <xsl:with-param name="val" select="concat('    ', Cars, ' / ', Date, ' [ 50 Entrants ]')"/>
        </xsl:call-template>
        <!-- sequence number -->
        <xsl:call-template name="csv">
            <xsl:with-param name="val" select="position()"/>
        </xsl:call-template>
        <!-- Time HH:MM:SS -->
        <xsl:call-template name="csv">
            <xsl:with-param name="val" select="Time"/>
        </xsl:call-template>
        <!-- avg -->
        <xsl:call-template name="csv">
            <xsl:with-param name="val" select="avg"/>
            <xsl:with-param name="isLastCol" select="true()"/>
            <xsl:with-param name="isEOF" select="position()=last()"/>
        </xsl:call-template>
    </xsl:template>

    <xsl:template name="csv">
        <xsl:param name="val"/>
        <xsl:param name="isLastCol" select="false()"/>
        <xsl:param name="isEOF" select="false()"/>
        <xsl:value-of select="$val"/>
        <xsl:choose>
            <xsl:when test="$isLastCol and not($isEOF)">
                <xsl:value-of select="$newLine"/>
            </xsl:when>
            <xsl:when test="not($isEOF)">
                <xsl:value-of select="$csvSeparator"/>
            </xsl:when>
        </xsl:choose>
    </xsl:template>

</xsl:stylesheet>

output text file atm:

 Ford / 2023-02-19 [ 50 Entrants ]  1   10:40:09    19.3464027998
 Opel / 2023-02-19 [ 50 Entrants ]  2   14:05:51    41.7428144493

What i want:

     Ford / 2023-02-19 [ 50 Entrants ]
1   10:40:09    19.3464027998
2   11:21:56    32.7150023474
3   19:01:27    554.0289810087
4   23:15:26    46.1398232343
     Opel / 2023-02-19 [ 50 Entrants ]
1   02:00:00    1.2954721559
2   14:05:51    41.7428144493
3   15:01:02    65.6303001034

Solution

  • Muenchian grouping usually works in two stages: first you identify the first item in each group and create a group; then you process the items in this group. The problem with your attempt is that it lacks the second part.

    Try adapting this simplified stylesheet:

    <xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text" encoding="UTF-8"/>
    
    <xsl:key name="carsgroup" match="item" use="concat(Cars, '|', Date)" />
    
    <xsl:template match="/data">
        <xsl:for-each select="item[generate-id() = generate-id(key('carsgroup', concat(Cars, '|', Date))[1])]">
            <!-- group header -->
            <xsl:value-of select="Cars"/>
            <xsl:text>/</xsl:text>
            <xsl:value-of select="Date"/>
            <xsl:text>&#10;</xsl:text>
            <!-- group items -->
            <xsl:for-each select="key('carsgroup', concat(Cars, '|', Date))">
                <xsl:value-of select="position()"/>
                <xsl:text>&#9;</xsl:text>
                <xsl:value-of select="Time"/>
                <xsl:text>&#9;</xsl:text>
                <xsl:value-of select="avg"/>
                <xsl:text>&#10;</xsl:text>
            </xsl:for-each>     
        </xsl:for-each>            
    </xsl:template>
    
    </xsl:stylesheet>