How can I eliminate duplicate nodes based on values of multiple (more than 1) attributes? Also the attribute names are passed as parameters to the stylesheet. Now I am aware of the Muenchian method of grouping that uses a <xsl:key> element. But I came to know that XSLT 1.0 does not allow paramters/variables in <xsl:key>.
Is there another method(s) to achieve duplicate nodes removal? It is fine if it not as efficient as the Munechian method.
Update from previus question:
XML:
<data id = "root">
<record id="1" operator1='xxx' operator2='yyy' operator3='zzz'/>
<record id="2" operator1='abc' operator2='yyy' operator3='zzz'/>
<record id="3" operator1='abc' operator2='yyy' operator3='zzz'/>
<record id="4" operator1='xxx' operator2='yyy' operator3='zzz'/>
<record id="5" operator1='xxx' operator2='lkj' operator3='tyu'/>
<record id="6" operator1='xxx' operator2='yyy' operator3='zzz'/>
<record id="7" operator1='abc' operator2='yyy' operator3='zzz'/>
<record id="8" operator1='abc' operator2='yyy' operator3='zzz'/>
<record id="9" operator1='xxx' operator2='yyy' operator3='zzz'/>
<record id="10" operator1='rrr' operator2='yyy' operator3='zzz'/>
</data>
Other approach for a single transformation in two steps:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
exclude-result-prefixes="msxsl">
<xsl:key name="kItemByLocal" match="record[#local-key]" use="#local-key"/>
<xsl:param name="pAttNames" select="'operator1 operator2 operator3'"/>
<xsl:template match="/">
<xsl:variable name="vFirstRTF">
<xsl:apply-templates/>
</xsl:variable>
<xsl:apply-templates select="msxsl:node-set($vFirstRTF)/node()"/>
</xsl:template>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="record[not(#local-key)]">
<xsl:copy>
<xsl:attribute name="local-key">
<xsl:call-template name="local-key"/>
</xsl:attribute>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="record[#local-key]
[count(.|key('kItemByLocal',#local-key)[1])
!= 1]|#local-key"/>
<xsl:template name="local-key">
<xsl:param name="pAttributes" select="concat($pAttNames,' ')"/>
<xsl:if test="normalize-space($pAttributes)">
<xsl:variable name="vName"
select="substring-before($pAttributes,' ')"/>
<xsl:variable name="vAttribute" select="#*[name()=$vName]"/>
<xsl:value-of select="concat($vName,'+',$vAttribute,'+')"/>
<xsl:call-template name="local-key">
<xsl:with-param name="pAttributes"
select="substring-after($pAttributes,' ')"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Output:
<data id="root">
<record id="1" operator1="xxx" operator2="yyy" operator3="zzz"></record>
<record id="2" operator1="abc" operator2="yyy" operator3="zzz"></record>
<record id="5" operator1="xxx" operator2="lkj" operator3="tyu"></record>
<record id="10" operator1="rrr" operator2="yyy" operator3="zzz"></record>
</data>
Edit: Also without named template for #local-key generation
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
exclude-result-prefixes="msxsl">
<xsl:key name="kItemByLocal" match="record[#local-key]" use="#local-key"/>
<xsl:param name="pAttNames" select="'operator1 operator2 operator3'"/>
<xsl:template match="/">
<xsl:variable name="vFirstRTF">
<xsl:apply-templates/>
</xsl:variable>
<xsl:apply-templates select="msxsl:node-set($vFirstRTF)/node()"/>
</xsl:template>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="record[not(#local-key)]">
<xsl:variable name="vAttNames"
select="concat(' ',$pAttNames,' ')"/>
<xsl:copy>
<xsl:attribute name="local-key">
<xsl:for-each select="#*[contains(
$vAttNames,
concat(' ',name(),' ')
)]">
<xsl:sort select="substring-before(
$vAttNames,
concat(' ',name(),' ')
)"/>
<xsl:value-of select="concat(name(),'++',.,'++')"/>
</xsl:for-each>
</xsl:attribute>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="record[#local-key]
[count(.|key('kItemByLocal',#local-key)[1])
!= 1]|#local-key"/>
</xsl:stylesheet>
Note: If you are positive sure that attributes order is the same for all elements, then you could remove the sorting.
Use this transformation (simple and no need to generate a new stylesheet):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pAttribs">
<name>operator1</name>
<name>operator2</name>
<name>operator3</name>
</xsl:param>
<xsl:variable name="vAttribs" select=
"document('')/*/xsl:param[#name='pAttribs']"/>
<xsl:key name="kRecByAtts" match="record"
use="#___g_key"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<xsl:variable name="vrtdPass1">
<xsl:apply-templates/>
</xsl:variable>
<xsl:variable name="vPass1" select=
"ext:node-set($vrtdPass1)/*"/>
<xsl:apply-templates select="$vPass1"/>
</xsl:template>
<xsl:template match="record[not(#___g_key)]">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:attribute name="___g_key">
<xsl:for-each select="#*[name()=$vAttribs/name]">
<xsl:sort select="name()"/>
<xsl:value-of select=
"concat('___Attrib___',name(),'___Value___',.,'+++')"/>
</xsl:for-each>
</xsl:attribute>
</xsl:copy>
</xsl:template>
<xsl:template match=
"record[#___g_key]
[not(generate-id()
=
generate-id(key('kRecByAtts', #___g_key)[1])
)
]
"/>
<xsl:template match="#___g_key"/>
</xsl:stylesheet>
When applied to the XML document of your previous question:
<data id = "root">
<record id="1" operator1='xxx' operator2='yyy' operator3='zzz'/>
<record id="2" operator1='abc' operator2='yyy' operator3='zzz'/>
<record id="3" operator1='abc' operator2='yyy' operator3='zzz'/>
<record id="4" operator1='xxx' operator2='yyy' operator3='zzz'/>
<record id="5" operator1='xxx' operator2='lkj' operator3='tyu'/>
<record id="6" operator1='xxx' operator2='yyy' operator3='zzz'/>
<record id="7" operator1='abc' operator2='yyy' operator3='zzz'/>
<record id="8" operator1='abc' operator2='yyy' operator3='zzz'/>
<record id="9" operator1='xxx' operator2='yyy' operator3='zzz'/>
<record id="10" operator1='rrr' operator2='yyy' operator3='zzz'/>
</data>
The wanted, correct result is produced:
<data id="root">
<record id="1" operator1="xxx" operator2="yyy" operator3="zzz"/>
<record id="2" operator1="abc" operator2="yyy" operator3="zzz"/>
<record id="5" operator1="xxx" operator2="lkj" operator3="tyu"/>
<record id="10" operator1="rrr" operator2="yyy" operator3="zzz"/>
</data>
If you want to pass in the attribute names as a parameter then one approach could be a two step transformation where the first step takes any XML input and simply the attribute names and the element names as parameters to generate a second stylesheet that then eliminates the duplicates.
Here is an example first stylesheet:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
xmlns:axsl="http://www.w3.org/1999/XSL/TransformAlias"
exclude-result-prefixes="axsl exsl"
version="1.0">
<xsl:param name="parent-name" select="'items'"/>
<xsl:param name="element-name" select="'item'"/>
<xsl:param name="att-names" select="'att1,att2'"/>
<xsl:param name="sep" select="'|'"/>
<xsl:namespace-alias stylesheet-prefix="axsl" result-prefix="xsl"/>
<xsl:output method="xml" indent="yes"/>
<xsl:variable name="key-value">
<xsl:text>concat(</xsl:text>
<xsl:call-template name="define-values">
<xsl:with-param name="att-names" select="$att-names"/>
</xsl:call-template>
<xsl:text>)</xsl:text>
</xsl:variable>
<xsl:template name="define-values">
<xsl:param name="att-names"/>
<xsl:choose>
<xsl:when test="contains($att-names, ',')">
<xsl:value-of select="concat('#', substring-before($att-names, ','), ',"', $sep, '",')"/>
<xsl:call-template name="define-values">
<xsl:with-param name="att-names" select="substring-after($att-names, ',')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat('#', $att-names)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="/">
<axsl:stylesheet version="1.0">
<axsl:output indent="yes"/>
<axsl:key name="k1" match="{$parent-name}/{$element-name}" use="{$key-value}"/>
<axsl:template match="#* | node()">
<axsl:copy>
<axsl:apply-templates select="#* | node()"/>
</axsl:copy>
</axsl:template>
<axsl:template match="{$parent-name}">
<axsl:copy>
<axsl:apply-templates select="#*"/>
<axsl:apply-templates select="{$element-name}[generate-id() = generate-id(key('k1', {$key-value})[1])]"/>
</axsl:copy>
</axsl:template>
</axsl:stylesheet>
</xsl:template>
</xsl:stylesheet>
It takes four parameters:
parent-name: the name of the element containing those elements of which you want to eliminate duplicates
element-name: the name of those elements of which you want to eliminate duplicates
att-names: a comma separated list of attribute names
sep: a separator character that should not occur in attribute values in the input XML
The stylesheet then generates a second stylesheet that applies Muenchian grouping to eliminate duplicates. For instance with the default parameters given in the stylesheet Saxon 6.5.5 generates the following stylesheet:
<axsl:stylesheet xmlns:axsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<axsl:output indent="yes"/>
<axsl:key name="k1" match="items/item" use="concat(#att1,"|",#att2)"/>
<axsl:template match="#* | node()">
<axsl:copy>
<axsl:apply-templates select="#* | node()"/>
</axsl:copy>
</axsl:template>
<axsl:template match="items">
<axsl:copy>
<axsl:apply-templates select="#*"/>
<axsl:apply-templates select="item[generate-id() = generate-id(key('k1', concat(#att1,"|",#att2))[1])]"/>
</axsl:copy>
</axsl:template>
</axsl:stylesheet>
This can the be applied to an XML document like
<items>
<item att1="a" att2="1" att3="A"/>
<item att1="b" att2="1" att3="A"/>
<item att1="a" att2="1" att3="B"/>
<item att1="c" att2="2" att3="A"/>
<item att1="d" att2="3" att3="C"/>
</items>
and the output is
<items>
<item att1="a" att2="1" att3="A"/>
<item att1="b" att2="1" att3="A"/>
<item att1="c" att2="2" att3="A"/>
<item att1="d" att2="3" att3="C"/>
</items>
Related
I am trying to re-structure xml data into groups and sub-group. I was able to get it to work, but my code has to include something, that looks (at least to me) like a workaround. Here are my sample files:
Data.xml:
<data>
<record Group="g1" SubGroup="sg1">Record 1</record>
<record Group="g2" SubGroup="sg1">Record 2</record>
<record Group="g1" SubGroup="sg1">Record 3</record>
<record Group="g2" SubGroup="sg1">Record 4</record>
<record Group="g2" SubGroup="sg2">Record 5</record>
<record Group="g1" SubGroup="sg2">Record 6</record>
</data>
Stylesheet.xsl:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" version="1.0" indent="yes" encoding="UTF-8"/>
<xsl:key name="Group" match="record" use="#Group" />
<xsl:key name="SubGroup" match="record" use="#SubGroup" />
<xsl:template match="/data">
<xsl:variable name="Records" select="record"/>
<data>
<xsl:for-each select="$Records[generate-id(.)=generate-id(key('Group',#Group)[1])]">
<xsl:sort select="#Group"/>
<xsl:variable name="Group" select="#Group"/>
<xsl:call-template name="Group">
<xsl:with-param name="Records" select="$Records[#Group = $Group]"/>
<xsl:with-param name="Group" select="$Group"/>
</xsl:call-template>
</xsl:for-each>
</data>
</xsl:template>
<xsl:template name="Group">
<xsl:param name="Records"/>
<xsl:param name="Group"/>
<group name="{$Group}">
<xsl:for-each select="$Records[generate-id(.)=generate-id(key('SubGroup',#SubGroup)[1])]">
<!-- this works: <xsl:for-each select="$Records[generate-id(.)=generate-id(key('SubGroup',#SubGroup)[#Group = $Group][1])]"> -->
<xsl:sort select="#SubGroup"/>
<xsl:variable name="SubGroup" select="#SubGroup"/>
<xsl:call-template name="SubGroup">
<xsl:with-param name="Records" select="$Records[#SubGroup = $SubGroup]"/>
<xsl:with-param name="Group" select="$Group"/>
<xsl:with-param name="SubGroup" select="$SubGroup"/>
</xsl:call-template>
</xsl:for-each>
</group>
</xsl:template>
<xsl:template name="SubGroup">
<xsl:param name="Records"/>
<xsl:param name="Group"/>
<xsl:param name="SubGroup"/>
<subgroup name="{$SubGroup}">
<xsl:for-each select="$Records">
<xsl:copy-of select="."/>
</xsl:for-each>
</subgroup>
</xsl:template>
</xsl:stylesheet>
This is the output generated:
<?xml version="1.0" encoding="UTF-8"?>
<data>
<group name="g1">
<subgroup name="sg1">
<record Group="g1" SubGroup="sg1">Record 1</record>
<record Group="g1" SubGroup="sg1">Record 3</record>
</subgroup>
</group>
<group name="g2">
<subgroup name="sg2">
<record Group="g2" SubGroup="sg2">Record 5</record>
</subgroup>
</group>
</data>
but this is the output, I want to have:
<?xml version="1.0" encoding="UTF-8"?>
<data>
<group name="g1">
<subgroup name="sg1">
<record Group="g1" SubGroup="sg1">Record 1</record>
<record Group="g1" SubGroup="sg1">Record 3</record>
</subgroup>
<subgroup name="sg2">
<record Group="g1" SubGroup="sg2">Record 6</record>
</subgroup>
</group>
<group name="g2">
<subgroup name="sg1">
<record Group="g2" SubGroup="sg1">Record 2</record>
<record Group="g2" SubGroup="sg1">Record 4</record>
</subgroup>
<subgroup name="sg2">
<record Group="g2" SubGroup="sg2">Record 5</record>
</subgroup>
</group>
</data>
The problem is the for-each loop in the tempalte named "Group". It seems, that the key()-function is not working on the nodes contained in $Records but on the entire input XML file.
I get identical results with xsltproc and with saxon, so I do not think, it is a bug in my xslt processor. It seems, that I did not completely understand, how key() works.
If I add an additional selector [#Group = $Group] to key()'s output, I get the expected result.
Can somebody explain what is going on and why the additional selector [#Group = $Group] is needed.
Mario
When you want to do sub-grouping, you need to use a concatenated key of both the main group and the sub-group
<xsl:key name="SubGroup" match="record" use="concat(#Group,'|', #SubGroup)" />
Then, just use it in the same way as before, with the concatenation
<xsl:for-each select="$Records[generate-id(.)=generate-id(key('SubGroup',concat(#Group,'|', #SubGroup))[1])]">
Try this XSLT (which I have also simplified to utilise the key when calling your named templates with the records)
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" version="1.0" indent="yes" encoding="UTF-8"/>
<xsl:key name="Group" match="record" use="#Group" />
<xsl:key name="SubGroup" match="record" use="concat(#Group,'|', #SubGroup)" />
<xsl:template match="/data">
<data>
<xsl:for-each select="record[generate-id(.)=generate-id(key('Group',#Group)[1])]">
<xsl:sort select="#Group"/>
<xsl:call-template name="Group">
<xsl:with-param name="Records" select="key('Group',#Group)"/>
</xsl:call-template>
</xsl:for-each>
</data>
</xsl:template>
<xsl:template name="Group">
<xsl:param name="Records"/>
<group name="{#Group}">
<xsl:for-each select="$Records[generate-id(.)=generate-id(key('SubGroup',concat(#Group,'|', #SubGroup))[1])]">
<xsl:sort select="#SubGroup"/>
<xsl:call-template name="SubGroup">
<xsl:with-param name="Records" select="key('SubGroup',concat(#Group,'|', #SubGroup))"/>
</xsl:call-template>
</xsl:for-each>
</group>
</xsl:template>
<xsl:template name="SubGroup">
<xsl:param name="Records"/>
<subgroup name="{#SubGroup}">
<xsl:for-each select="$Records">
<xsl:copy-of select="."/>
</xsl:for-each>
</subgroup>
</xsl:template>
</xsl:stylesheet>
I'm trying to transform a XML file with XSLT 1.0 but I'm having troubles with this.
Input:
<task_order>
<Q>
<record id="1">
<column name="task_externalId">SPLIT4_0</column>
</record>
<record id="2">
<column name="task_externalId">SPLIT4_1</column>
</record>
</Q>
<task>
<id>SPLIT4</id>
<name>test</name>
</task>
</task_order>
Wanted result:
For each task_order element: When there is more than 1 record-element (SPLIT4 and SPLIT4_1) I need to duplicate the task element and change the orginal task-id with the id from record elements.
<task_order>
<Q>
<record id="1">
<column name="task_externalId">SPLIT4_0</column>
</record>
<record id="2">
<column name="task_externalId">SPLIT4_1</column>
</record>
</Q>
<task>
<id>SPLIT4_0</id>
<name>test</name>
</task>
<task>
<id>SPLIT4_1</id>
<name>test</name>
</task>
</task_order>
Any suggestions?
Thank you
First start off with the Identity Template which will handle copying across all existing nodes
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
Next, to make looking up the columns slightly easier, consider using an xsl:key
<xsl:key name="column" match="column" use="substring-before(., '_')" />
Then, you have a template matching task where you can look up all matching column elements using the key, and create a new task element for each
<xsl:template match="task">
<xsl:variable name="task" select="." />
<xsl:for-each select="key('column', id)">
<!-- Create new task -->
</xsl:for-each>
</xsl:template>
Try this XSTL
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:key name="column" match="column" use="substring-before(., '_')" />
<xsl:template match="task">
<xsl:variable name="task" select="." />
<xsl:for-each select="key('column', id)">
<task>
<id><xsl:value-of select="." /></id>
<xsl:apply-templates select="$task/*[not(self::id)]" />
</task>
</xsl:for-each>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Thanks in Advance & Sorry as it might take a while for you to understand my logic (i'm not a pro in using XSLT).
Could anyone help in eliminating duplication of tag "PaySlip_Val" and group all "ListOfPaySlipData" childs under one parent "ListOfPaySlipMonth".
Below is XML hierarchy,
<?xml version="1.0" encoding="UTF-8"?>
<Data>
<P>
<FstName>F1</FstName>
<LstName>L1</LstName>
<Type>
<Payslip>y</Payslip>
<Details>
<Year>2016</Year>
<Month>Jan</Month>
<Amount>$$$</Amount>
</Details>
<Details>
<Year>2016</Year>
<Month>Feb</Month>
<Amount>$$$</Amount>
</Details>
</Type>
<Type>
<Payslip>yes</Payslip>
<Details>
<Year>2016</Year>
<Month>Mar</Month>
<Amount>$$$</Amount>
</Details>
<Details>
<Year>2016</Year>
<Month>Apr</Month>
<Amount>$$$</Amount>
</Details>
</Type>
<Type>
<Payslip>n</Payslip>
<Details>
<Year>2016</Year>
<Month>May</Month>
<Amount>$$$</Amount>
</Details>
</Type>
</P>
<P>
<FstName>F2</FstName>
<LstName>L2</LstName>
<Type>
<Payslip>n</Payslip>
<Details>
<Year>2016</Year>
<Month>Feb</Month>
<Leaves>4</Leaves>
</Details>
</Type>
</P>
</Data>
I have applied below XSLT,
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:variable name="smallcase" select="'abcdefghijklmnopqrstuvwxyz'" />
<xsl:variable name="uppercase" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'" />
<xsl:template match="/">
<xsl:call-template name="Pay_Slip" />
</xsl:template>
<xsl:template name="Pay_Slip">
<xsl:element name="ListOfData">
<xsl:for-each select="Data/P">
<xsl:element name="First_Name">
<xsl:value-of select="FstName" />
</xsl:element>
<xsl:element name="Last_Name">
<xsl:value-of select="LstName" />
</xsl:element>
<xsl:element name="PaySlip">
<xsl:for-each select="Type">
<xsl:variable name="Type" select="Payslip" />
<xsl:if test="$Type ='y' or $Type ='yes'">
<xsl:element name="PaySlip_Val">
<xsl:value-of select="$Type" />
</xsl:element>
<xsl:element name="ListOfPaySlipMonth">
<xsl:for-each select="Details">
<xsl:element name="ListOfPaySlipData">
<xsl:element name="Month">
<xsl:value-of select="Month" />
</xsl:element>
<xsl:element name="Salary">
<xsl:value-of select="Amount" />
</xsl:element>
<xsl:element name="Year">
<xsl:value-of select="Year" />
</xsl:element>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:if>
</xsl:for-each>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Result is :
<?xml version="1.0" encoding="UTF-8"?>
<ListOfData>
<First_Name>F1</First_Name>
<Last_Name>L1</Last_Name>
<PaySlip>
<PaySlip_Val>y</PaySlip_Val>
<ListOfPaySlipMonth>
<ListOfPaySlipData>
<Month>Jan</Month>
<Salary>$$$</Salary>
<Year>2016</Year>
</ListOfPaySlipData>
<ListOfPaySlipData>
<Month>Feb</Month>
<Salary>$$$</Salary>
<Year>2016</Year>
</ListOfPaySlipData>
</ListOfPaySlipMonth>
<PaySlip_Val>yes</PaySlip_Val>
<ListOfPaySlipMonth>
<ListOfPaySlipData>
<Month>Mar</Month>
<Salary>$$$</Salary>
<Year>2016</Year>
</ListOfPaySlipData>
<ListOfPaySlipData>
<Month>Apr</Month>
<Salary>$$$</Salary>
<Year>2016</Year>
</ListOfPaySlipData>
</ListOfPaySlipMonth>
</PaySlip>
<First_Name>F2</First_Name>
<Last_Name>L2</Last_Name>
<PaySlip/>
</ListOfData>
Desired Result :
<ListOfData>
<ListOfP>
<First_Name>F1</First_Name>
<Last_Name>L1</Last_Name>
<PaySlip>
<PaySlip_Val>y</PaySlip_Val>
<ListOfPaySlipMonth>
<ListOfPaySlipData>
<Month>Jan</Month>
<Salary>$$$</Salary>
<Year>2016</Year>
</ListOfPaySlipData>
<ListOfPaySlipData>
<Month>Feb</Month>
<Salary>$$$</Salary>
<Year>2016</Year>
</ListOfPaySlipData>
<ListOfPaySlipData>
<Month>Mar</Month>
<Salary>$$$</Salary>
<Year>2016</Year>
</ListOfPaySlipData>
<ListOfPaySlipData>
<Month>Apr</Month>
<Salary>$$$</Salary>
<Year>2016</Year>
</ListOfPaySlipData>
</ListOfPaySlipMonth>
</PaySlip>
</ListOfP>
</ListOfData>
AFAICT, this returns the expected result:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/Data">
<ListOfData>
<xsl:apply-templates/>
</ListOfData>
</xsl:template>
<xsl:template match="P">
<First_Name><xsl:value-of select="FstName"/></First_Name>
<Last_Name><xsl:value-of select="LstName"/></Last_Name>
<xsl:variable name="payslips" select="Type[Payslip='yes' or Payslip='y']" />
<PaySlip>
<xsl:if test="$payslips">
<PaySlip_Val>y</PaySlip_Val>
<ListOfPaySlipMonth>
<xsl:apply-templates select="Type[Payslip='yes' or Payslip='y']/Details"/>
</ListOfPaySlipMonth>
</xsl:if>
</PaySlip>
</xsl:template>
<xsl:template match="Details">
<ListOfPaySlipData>
<xsl:copy-of select="Month"/>
<Salary><xsl:value-of select="Amount"/></Salary>
<xsl:copy-of select="Year"/>
</ListOfPaySlipData>
</xsl:template>
</xsl:stylesheet>
Edit:
I have updated the desired result. The condition is that I need to get
the First Name & Last name only if the "Payslip" tag has y or yes. In
other cases, it should not fetch the data (F2 & L2 as per example).
Try it this way, then:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/Data">
<ListOfData>
<xsl:apply-templates select="P[Type/Payslip='yes' or Type/Payslip='y']"/>
</ListOfData>
</xsl:template>
<xsl:template match="P">
<First_Name><xsl:value-of select="FstName"/></First_Name>
<Last_Name><xsl:value-of select="LstName"/></Last_Name>
<PaySlip>
<PaySlip_Val>y</PaySlip_Val>
<ListOfPaySlipMonth>
<xsl:apply-templates select="Type[Payslip='yes' or Payslip='y']/Details"/>
</ListOfPaySlipMonth>
</PaySlip>
</xsl:template>
<xsl:template match="Details">
<ListOfPaySlipData>
<xsl:copy-of select="Month"/>
<Salary><xsl:value-of select="Amount"/></Salary>
<xsl:copy-of select="Year"/>
</ListOfPaySlipData>
</xsl:template>
</xsl:stylesheet>
I have a strange transformation that I'm trying to do.
XML looks like this:
<?xml version="1.0" standalone="yes"?>
<Parent>
<RecordCount>4</RecordCount>
<Record name="1">
<Child1>Value 1</Child1>
<Child2>Value 2</Child2>
</Record>
</Parent>
And this is what it needs to look like:
<?xml version="1.0" standalone="yes"?>
<Parent>
<RecordCount>4</RecordCount>
<Record name="1">
<Child1>Value 1</Child1>
<Child2>Value 2</Child2>
</Record>
<Record name="2">
<Child1>Value 1</Child1>
<Child2>Value 2</Child2>
</Record>
<Record name="3">
<Child1>Value 1</Child1>
<Child2>Value 2</Child2>
</Record>
<Record name="4">
<Child1>Value 1</Child1>
<Child2>Value 2</Child2>
</Record>
</Parent>
Is something like this even possible with XSLT or should I rather just handle this in code?
Here's another way using XSLT 2.0...
XML Input
<Parent>
<RecordCount>4</RecordCount>
<Record name="1">
<Child1>Value 1</Child1>
<Child2>Value 2</Child2>
</Record>
</Parent>
XSLT 2.0
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="RecordCount">
<xsl:variable name="record" select="../Record"/>
<xsl:copy-of select="."/>
<xsl:for-each select="1 to .">
<xsl:apply-templates select="$record" mode="replicate">
<xsl:with-param name="cnt" select="."/>
</xsl:apply-templates>
</xsl:for-each>
</xsl:template>
<xsl:template match="Record" mode="replicate">
<xsl:param name="cnt"/>
<Record name="{$cnt}">
<xsl:apply-templates select="#* except #name|node()"/>
</Record>
</xsl:template>
<xsl:template match="Record"/>
</xsl:stylesheet>
Output
<Parent>
<RecordCount>4</RecordCount>
<Record name="1">
<Child1>Value 1</Child1>
<Child2>Value 2</Child2>
</Record>
<Record name="2">
<Child1>Value 1</Child1>
<Child2>Value 2</Child2>
</Record>
<Record name="3">
<Child1>Value 1</Child1>
<Child2>Value 2</Child2>
</Record>
<Record name="4">
<Child1>Value 1</Child1>
<Child2>Value 2</Child2>
</Record>
</Parent>
Try following xlst
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/Parent">
<Parent>
<xsl:variable name="count" select="RecordCount" />
<xsl:call-template name="multiply">
<xsl:with-param name="maxCount" select="$count" />
<xsl:with-param name="nodeToCopy" select="Record" />
</xsl:call-template>
</Parent>
</xsl:template>
<xsl:template name="multiply">
<xsl:param name="maxCount" />
<xsl:param name="i" select="1" />
<xsl:param name="nodeToCopy" />
<xsl:choose>
<xsl:when test="$i <= $maxCount">
<xsl:element name="{name($nodeToCopy)}">
<xsl:attribute name="name">
<xsl:value-of select="$i" />
</xsl:attribute>
<xsl:copy-of select="$nodeToCopy/child::*" />
</xsl:element>
<xsl:call-template name="multiply">
<xsl:with-param name="maxCount" select="$maxCount" />
<xsl:with-param name="nodeToCopy" select="$nodeToCopy" />
<xsl:with-param name="i" select="$i+1" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise />
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
It is based on recursive calling of named template with increasing of "iterating" value. If would be something unclear just write a comment.
For example the source xml is
<records>
<record>
<personId>111</personId>
<location>Australia</location>
<year>1999</year>
</record>
<record>
<personId>222</personId>
<location>Netherland</location>
<year>1919</year>
</record>
<record>
<personId>111</personId>
<location>USA</location>
<year>2000</year>
</record>
</records>
now after xsl it should be something like below where it does takes latest year's (max on elemtnt year) and discards old records for that personID:
<records>
<record>
<personId>222</personId>
<location>Netherland</location>
<year>1919</year>
</record>
<record>
<personId>111</personId>
<location>USA</location>
<year>2000</year>
</record>
</records>
This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="kRecordByPersonId" match="record" use="personId"/>
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="record"/>
<xsl:template match="record[count(.|key('kRecordByPersonId',personId)[1])
= 1]">
<xsl:for-each select="key('kRecordByPersonId',personId)">
<xsl:sort select="year" data-type="number" order="descending"/>
<xsl:if test="position()=1">
<xsl:call-template name="identity"/>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Output:
<records>
<record>
<personId>111</personId>
<location>USA</location>
<year>2000</year>
</record>
<record>
<personId>222</personId>
<location>Netherland</location>
<year>1919</year>
</record>
</records>
Just for fun, preserving input source order but also good performance:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="kRecordByPersonId" match="record" use="personId"/>
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="records">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:call-template name="group-max">
<xsl:with-param name="pGroup"
select="record[count(.|key('kRecordByPersonId',personId)[1])
= 1]"/>
</xsl:call-template>
</xsl:copy>
</xsl:template>
<xsl:template name="group-max">
<xsl:param name="pGroup" select="/.."/>
<xsl:param name="pGroup-Max" select="/.."/>
<xsl:choose>
<xsl:when test="$pGroup">
<xsl:for-each
select="key('kRecordByPersonId',$pGroup[1]/personId)">
<xsl:sort select="year"
data-type="number"
order="descending"/>
<xsl:if test="position()=1">
<xsl:call-template name="group-max">
<xsl:with-param name="pGroup"
select="$pGroup[position()!=1]"/>
<xsl:with-param name="pGroup-Max"
select="$pGroup-Max|."/>
</xsl:call-template>
</xsl:if>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="$pGroup-Max"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Output:
<records>
<record>
<personId>222</personId>
<location>Netherland</location>
<year>1919</year>
</record>
<record>
<personId>111</personId>
<location>USA</location>
<year>2000</year>
</record>
</records>