xsltxslt-2.0xslt-grouping

XSLT : Apply an xml tranformation followed by another text tranformation


Even though there are several threads that explain the use of multiple templates in the same xslt file, I can't find a solution to address my issue.

I would like to apply two transformations to my xml file. First, I want to do a grouping by ID. Then, I would like to do some processing to remove special characters

Separately, the templates work fine, but when I try to put them together, I have an xml and text output. My goal is to have only the text part in my output and keep the xml part as an intermediate step only.

Could you please guide me on how to properly use the mode attribute? I'd like to do something similar to this

   <xsl:template match="">
         <xsl:apply-templates select="XML_Part"/> ---> hide from the final output
         <xsl:apply-templates select="Text_Part"/>
    </xsl:template>
    
    <xsl:template match="" mode="XML_Part">
    </xsl:template>
    
    <xsl:template match="" mode="Text_Part">
    </xsl:template>

XML File (input)

<?xml version="1.0" encoding="UTF-8"?>
<wd:Report_Data xmlns:wd="http://www.workday.org/">
  <wd:Report_Entry>
    <wd:id>1000</wd:id>
    <wd:name>Call-of-Duty</wd:name>
    <wd:price>50.12</wd:price>
    <wd:copy>0</wd:copy>
  </wd:Report_Entry>
  <wd:Report_Entry>
    <wd:id>1000</wd:id>
    <wd:name>Call-of-Duty</wd:name>
    <wd:price>60.0</wd:price>
    <wd:copy>1</wd:copy>
  </wd:Report_Entry>
  <wd:Report_Entry>
    <wd:id>2000</wd:id>
    <wd:name>Metal Slug</wd:name>
    <wd:price>75.00</wd:price>
    <wd:copy>0</wd:copy>
  </wd:Report_Entry>
  <wd:Report_Entry>
    <wd:id>3000</wd:id>
    <wd:name>Need For Speed</wd:name>
    <wd:price>40.00</wd:price>
    <wd:copy>0</wd:copy>
  </wd:Report_Entry>
</wd:Report_Data>

XSLT Template for Grouping

<xsl:template match="/wd:Report_Data">
    <xsl:copy>
        <xsl:for-each-group select="wd:Report_Entry" group-by="wd:id">
            <xsl:copy>
                <xsl:copy-of select="wd:id"/>
                <xsl:copy-of select="wd:name"/>
                    <wd:price>
                        <xsl:value-of select="sum(current-group()/wd:price)" />
                    </wd:price>                 
                <xsl:copy-of select="wd:copy"/>
            </xsl:copy>
        </xsl:for-each-group>
    </xsl:copy>
</xsl:template>

XSLT to remove special characters and desplay text part

<xsl:template match="wd:Report_Data/wd:Report_Entry">
    
    <xsl:value-of select="wd:id"/>
    <xsl:text>|</xsl:text>
    <xsl:value-of select="translate(wd:name,'-',' ')"/>
    <xsl:text>|</xsl:text>
    <xsl:value-of select="wd:price"/>
    <xsl:text>|</xsl:text>
    <xsl:value-of select="wd:copy"/>
  
</xsl:template>

Current output

<wd:Report_Data xmlns:wd="http://www.workday.org/">
   <wd:Report_Entry>
      <wd:id>1000</wd:id>
      <wd:name>Call of Duty</wd:name>
      <wd:price>110.12</wd:price>
      <wd:copy>0</wd:copy>
   </wd:Report_Entry>
   <wd:Report_Entry>
      <wd:id>2000</wd:id>
      <wd:name>Metal Slug</wd:name>
      <wd:price>75</wd:price>
      <wd:copy>0</wd:copy>
   </wd:Report_Entry>
   <wd:Report_Entry>
      <wd:id>3000</wd:id>
      <wd:name>Need For Speed</wd:name>
      <wd:price>40</wd:price>
      <wd:copy>0</wd:copy>
   </wd:Report_Entry>
</wd:Report_Data>
1000|Call of Duty|110.12|0
2000|Metal Slug|75.00|0
3000|Need For Speed|40.00|0

Expected output

1000|Call of Duty|110.12|0
2000|Metal Slug|75.00|0
3000|Need For Speed|40.00|0

Solution

  • There is no need to chain transformations. You can do it all at once simply by:

    <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:wd="http://www.workday.org/" >
    <xsl:output method="text"/>
    
    <xsl:template match="/wd:Report_Data">
        <xsl:for-each-group select="wd:Report_Entry" group-by="wd:id">
            <xsl:value-of select="wd:id, translate(wd:name, '-', ' '), sum(current-group()/wd:price), wd:copy" separator="|"/>
            <xsl:text>&#10;</xsl:text>
        </xsl:for-each-group>
    </xsl:template>
    
    </xsl:stylesheet>
    

    If you need the price to be formatted as shown, change :

    sum(current-group()/wd:price)
    

    to:

    format-number(sum(current-group()/wd:price), '0.00')