I am unable to find out what I should be doing to get desired output as below.
Can anyone help me please? Thank you!
Source XML
<?xml version="1.0" encoding="UTF-8"?>
<Workers>
<Worker>
<empID>12345</empID>
<Location>NY</Location>
<Remote/>
<Amount>1450</Amount>
</Worker>
<Worker>
<empID>23456</empID>
<Local/>
<Location>NY</Location>
<City>NYC</City>
<CityAllowance>100</CityAllowance>
<Remote>Y</Remote>
<Amount>1450</Amount>
</Worker>
<Worker>
<empID>23456</empID>
<Local>Y</Local>
<Location>NY</Location>
<City>Syracuse</City>
<CityAllowance>150</CityAllowance>
<Remote>Y</Remote>
<Amount>1450</Amount>
</Worker>
<Worker>
<empID>23456</empID>
<Local>Y</Local>
<Location>NY</Location>
<City>Ithaca</City>
<CityAllowance>250</CityAllowance>
<Remote>Y</Remote>
<Amount>1450</Amount>
</Worker>
<Worker>
<empID>88001</empID>
<Local/>
<Location>CA</Location>
<City>San Franscisco</City>
<CityAllowance>200</CityAllowance>
<Remote>N</Remote>
<Amount>1450</Amount>
</Worker>
<Worker>
<empID>88001</empID>
<Local>Y</Local>
<Location>CA</Location>
<City>San Jose</City>
<CityAllowance>190</CityAllowance>
<Remote>N</Remote>
<Amount>9450</Amount>
</Worker>
<Worker>
<empID>88001</empID>
<Local>Y</Local>
<Location>CA</Location>
<City>Oakland</City>
<CityAllowance>600</CityAllowance>
<Remote>N</Remote>
<Amount>4500</Amount>
</Worker>
</Workers>
My XSLT is
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="2.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Workers">
<Workers>
<xsl:for-each-group select="Worker[Remote = 'Y' or Remote = 'N']" group-by="empID">
<Worker>
<empID>
<xsl:value-of select="current-grouping-key()"/>
</empID>
</Worker>
</xsl:for-each-group>
</Workers>
</xsl:template>
</xsl:stylesheet>
Expected Output
<?xml version="1.0" encoding="UTF-8"?>
<Workers>
<Worker>
<empID>12345</empID>
<Location>NY</Location>
<Remote/>
<Amount>1450</Amount>
</Worker>
<Worker>
<empID>23456</empID>
</Worker>
<Worker>
<empID>88001</empID>
</Worker>
</Workers>
Current Output
<?xml version="1.0" encoding="UTF-8"?>
<Workers>
<Worker>
<empID>23456</empID>
</Worker>
<Worker>
<empID>88001</empID>
</Worker>
</Workers>
I wanted to copy all nodes exactly as they appear in Source XML if
value of <Local>
is either Y
or N
. However, this is not getting copied down with my current XSLT
For-each-group statement <xsl:for-each-group select="Worker[Remote = 'Y' or Remote = 'N']" group-by="empID">
was written to handle records that have <Local>
value is either set to Y
or N
My application supports both XSLT 3.0
and XSLT 2.0
One way is to use a key and add empty templates for the elements you don't want to output:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
expand-text="yes">
<xsl:strip-space elements="*"/>
<xsl:output method="xml" indent="yes"/>
<xsl:key name="group" match="Worker[Remote = 'Y' or Remote = 'N']" use="empID"/>
<xsl:template match="Worker[Remote = 'Y' or Remote = 'N'][not(. is key('group', empID)[1])]"/>
<xsl:template match="Worker[Remote = 'Y' or Remote = 'N'][. is key('group', empID)[1]]/*[not(self::empID)]"/>
<xsl:mode on-no-match="shallow-copy"/>
</xsl:stylesheet>
If you want to group some elements but process all with for-each-group
one way would be to use a variable:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
expand-text="yes">
<xsl:strip-space elements="*"/>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="Workers">
<xsl:copy>
<xsl:variable name="groups" as="element(Worker)*">
<xsl:for-each-group select="Worker" composite="yes" group-by="Remote = 'Y' or Remote = 'N', empID">
<xsl:choose>
<xsl:when test="current-grouping-key()[1]">
<xsl:sequence select="."/>
</xsl:when>
<xsl:otherwise>
<xsl:sequence select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:variable>
<xsl:apply-templates select="$groups/."/>
</xsl:copy>
</xsl:template>
<xsl:template match="Worker[Remote = 'Y' or Remote = 'N']/*[not(self::empID)]"/>
<xsl:mode on-no-match="shallow-copy"/>
</xsl:stylesheet>
Perhaps it is better to move the grouping code to a function, so that the template body for Workers
remains compact:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
xmlns:mf="http://example.com/mf"
expand-text="yes">
<xsl:function name="mf:group" as="element(Worker)*">
<xsl:param name="workers" as="element(Worker)*"/>
<xsl:for-each-group select="$workers" composite="yes" group-by="Remote = 'Y' or Remote = 'N', empID">
<xsl:choose>
<xsl:when test="current-grouping-key()[1]">
<xsl:sequence select="."/>
</xsl:when>
<xsl:otherwise>
<xsl:sequence select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:function>
<xsl:strip-space elements="*"/>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="Workers">
<xsl:copy>
<xsl:apply-templates select="mf:group(Worker)/."/>
</xsl:copy>
</xsl:template>
<xsl:template match="Worker[Remote = 'Y' or Remote = 'N']/*[not(self::empID)]"/>
<xsl:mode on-no-match="shallow-copy"/>
</xsl:stylesheet>