XSLT: Combine Elements Without Duplication
I want to do a conversion from XML to text by combining some elements but avoiding duplicates in the output. XML would be something like this:
<A>
<B>
<param1>value0</param1>
<param2>value1</param2>
</B>
<B>
<param1>value2</param1>
<param2>value3</param2>
</B>
<C>
<param3>valueC1</param3>
<D>
<param4>value0</param4>
<param5>value4</param5>
</D>
<D>
<param4>value0</param4>
<param5>value5</param5>
</D>
<D>
<param4>value2</param4>
<param5>value6</param5>
</D>
</C>
<C>
<param3>valueC2</param3>
<D>
<param4>value0</param4>
<param5>value5</param5>
</D>
</C>
</A>
And the output is:
OBJECT: param1=value0, param2=value1, param3=valueC1, param4=value0;
OBJECT: param1=value2, param2=value3, param3=valueC1, param4=value2;
OBJECT: param1=value0, param2=value1, param3=valueC2, param4=value0;
Notes:
- For each D object, find a match with B objects using D.param4 = B.param1
- If there are two or more D objects in the same C and they match the same B, print only one of them (in this example, nothing is done with the second D object, because it will produce the same string as and the first one)
- If there are two D objects matching the same B, but in different C, print both (third line in the example output)
I was looking for some similar question, but I could not find it in the same case.
I guess it can be done with keys, but it's too complicated.
Thank!
Best regards, Ale.
PS: Sorry for my english.
source to share
Considering that you are not using param5
in your release, it seems like it might be possible to simplify the problem to
- for each C
- find all the distinct elements of B whose
param1
matchesparam4
any of the contained Ds - for each of these
- fetching B / param1, B / param2, currentC / param3, B / param1 again (but tagged param4)
- find all the distinct elements of B whose
This is one way to achieve this with templates.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text" />
<xsl:key name="BbyParam1" match="B" use="param1" />
<xsl:template match="/">
<xsl:apply-templates select="A/C" />
</xsl:template>
<xsl:template match="C">
<xsl:apply-templates select="key('BbyParam1', D/param4)">
<xsl:with-param name="currentC" select="." />
</xsl:apply-templates>
</xsl:template>
<xsl:template match="B">
<xsl:param name="currentC" />
<xsl:text>OBJECT: param1=</xsl:text>
<xsl:value-of select="param1" />
<xsl:text>, param2=</xsl:text>
<xsl:value-of select="param2" />
<xsl:text>, param3=</xsl:text>
<xsl:value-of select="$currentC/param3" />
<xsl:text>, param4=</xsl:text>
<xsl:value-of select="param1" />
<xsl:text> </xsl:text>
</xsl:template>
</xsl:stylesheet>
"find all the distinct elements of B whose param1
matches param4
any of the contained Ds" is actually very simple, because when you pass a node as the second argument to key
it it does exactly that - it returns a set of all nodes whose key value is the string value of any from the nodes in the argument node, and the set node (being a set) is guaranteed to contain no duplicates.
source to share
This should do the trick:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" indent="yes"/>
<xsl:key name="kB" match="B" use="param1" />
<xsl:key name="kD" match="D"
use="concat(param4, '+', generate-id(..))"/>
<xsl:template match="/">
<xsl:apply-templates select="A/C" />
</xsl:template>
<xsl:template match="C">
<xsl:apply-templates select="D[key('kB', param4) and
generate-id() =
generate-id(key('kD',
concat(
param4,
'+',
generate-id(..)
)
)[1])]" />
</xsl:template>
<xsl:template match="D">
<xsl:value-of
select="concat('OBJECT: param1=',
key('kB', param4)/param1,
', param2=',
key('kB', param4)/param2,
', param3=',
../param3,
', param4=',
param4,
'
')"/>
</xsl:template>
</xsl:stylesheet>
Output when run on your sample:
OBJECT: param1=value0, param2=value1, param3=valueC1, param4=value0
OBJECT: param1=value2, param2=value3, param3=valueC1, param4=value2
OBJECT: param1=value0, param2=value1, param3=valueC2, param4=value0
source to share