Home > Back-end >  Only print when last occurred in XML using XSLT 1.0
Only print when last occurred in XML using XSLT 1.0

Time:08-25

I have a similar XSLT like below:

<Records>
 <Record>
  <Name>James</Name>
  <Subject>Maths</Subject>
  <Marks>75</Marks>
</Record>
<Record>
 <Name>James</Name>
 <Subject>Physics</Subject>
 <Marks>68</Marks>
</Record>
<Record>
 <Name>Alex</Name>
 <Subject>Maths</Subject>
 <Marks>89</Marks>
</Record>
<Record>
 <Name>Alex</Name>
 <Subject>Chemistry</Subject>
 <Marks>73</Marks>
</Record>

Now I want to print subtotal of each Name only at the last occurrence, For example, I want to print 143 as subtotal for James at the last occurrence i.e., when subject is Physics. something like below:

<Records>
 <Record>
  <Name>James</Name>
  <Subject>Maths</Subject>
  <Marks>75</Marks>
  <SubTotal />
 </Record>
 <Record>
  <Name>James</Name>
  <Subject>Physics</Subject>
  <Marks>68</Marks>
  <SubTotal>143</SubTotal> <!-- Total marks of James -->
 </Record>
 <Record>
  <Name>Alex</Name>
  <Subject>English</Subject>
  <Marks>89</Marks>
  <SubTotal />
 </Record>
 <Record>
  <Name>Alex</Name>
  <Subject>Chemistry</Subject>
  <Marks>73</Marks>
  <SubTotal>162</SubTotal> <!-- Total marks of Alex -->
 </Record>
</Records>

What would be the XSLT logic for this scenario? I'm very new to XSLTs and I have tried everything that I could but couldn't figure it out.

CodePudding user response:

For an XSLT beginner, here is a solution that doesn't use keys (but it would be less efficient than the one with keys:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

  <xsl:template match="node()|@*" name="identity">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match= "Record[not(Name = preceding-sibling::*[1]/Name)][position() > 1]">
    <SubTotal><xsl:value-of select=
    "sum(preceding-sibling::*[Name = current()/preceding-sibling::*[1]/Name]/Marks)"/></SubTotal>
    <xsl:call-template name="identity"/>
  </xsl:template>
  
  <xsl:template match= "Record[position() = last()]">
    <xsl:call-template name="identity"/>
    <SubTotal><xsl:value-of select=
    "sum(preceding-sibling::*[Name = current()/Name]/Marks)   Marks"/></SubTotal>
  </xsl:template>
</xsl:stylesheet>

When this transformation is applied on the provided XML document:

<Records>
    <Record>
        <Name>James</Name>
        <Subject>Maths</Subject>
        <Marks>75</Marks>
    </Record>
    <Record>
        <Name>James</Name>
        <Subject>Physics</Subject>
        <Marks>68</Marks>
    </Record>
    <Record>
        <Name>Alex</Name>
        <Subject>Maths</Subject>
        <Marks>89</Marks>
    </Record>
    <Record>
        <Name>Alex</Name>
        <Subject>Chemistry</Subject>
        <Marks>73</Marks>
    </Record>
</Records>

The wanted, correct result is produced:

<Records>
   <Record>
      <Name>James</Name>
      <Subject>Maths</Subject>
      <Marks>75</Marks>
   </Record>
   <Record>
      <Name>James</Name>
      <Subject>Physics</Subject>
      <Marks>68</Marks>
   </Record>
   <SubTotal>143</SubTotal>
   <Record>
      <Name>Alex</Name>
      <Subject>Maths</Subject>
      <Marks>89</Marks>
   </Record>
   <Record>
      <Name>Alex</Name>
      <Subject>Chemistry</Subject>
      <Marks>73</Marks>
   </Record>
   <SubTotal>162</SubTotal>
</Records>

Note: A more advanced non-keys solution that isn't less efficient than a keys-based one will use the "sequential identity" transformation and pass the current running total as a parameter.Here is the code of this not-so-well-known identity transformation:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

<xsl:template match="node()|@*">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()[1]"/>
    </xsl:copy>
    <xsl:apply-templates select="following-sibling::node()[1]"/>
  </xsl:template>
  
  <xsl:template match="/node()[not(self::*)]">
    <xsl:copy-of select="."/>
  </xsl:template>
</xsl:stylesheet>
  • Related