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>