This is a snippet from the xml:
<sample>
<test>
<Cell1>John</Cell1>
<Cell2>A</Cell2>
<Cell4>xy</Cell4>
</test>
<test>
<Cell1>Jack</Cell1>
<Cell2>B</Cell2>
<Cell3>Red</Cell3>
<Cell6>10</Cell6>
</test>
<test>
<Cell1>John,Jade</Cell1>
<Cell2>A,Y</Cell2>
<Cell4>1</Cell4>
</test>
<test>
<Cell1>John,Jade</Cell1>
<Cell2>A B,X</Cell2>
<Cell3>Blue</Cell3>
</test>
</sample>
If Cell2
contains comma split the values and check if the preceding Cell2
contains the same value and similarly if Cell2
contains space split the values and check if the preceding Cell2
contains the same value -> If so ignore it.
This is how I want the output:
<Cell2>A</Cell2>
<Cell2>B</Cell2>
<Cell2>Y</Cell2>
<Cell2>X</Cell2>
This is the xslt which I wrote: (please look into the comment)
<xsl:template match="sample">
<xsl:for-each select="test">
<xsl:choose>
<xsl:when test="contains(Cell2,',')">
<xsl:variable name="token1Cell2" select="tokenize(Cell2,',')"/>
<xsl:for-each select="$token1Cell2">
<xsl:choose>
<xsl:when test="contains(.,' ')">
<xsl:variable name="token2Cell2" select="tokenize(.,' ')"/>
<xsl:for-each select="$token2Cell2">
<!-- I tried to check if the preceding sibling element test/Cell2 contains the text that is not equal to the current text. But I know what I am doing is wrong as I am inside for-each tokenize. How to get the preceding-sibling? -->
<xsl:if test="preceding-sibling::test/
Cell2/text() != '.'">
<Cell2>
<xsl:value-of select="."/>
</Cell2>
</xsl:if>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:if test="preceding-sibling::test/Cell2/text() != '.'">
<Cell2>
<xsl:value-of select="."/>
</Cell2>
</xsl:if>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<Cell2>
<xsl:value-of select="Cell2"/>
</Cell2>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
How Can I acheive the output? Any Ideas??
Just this simple transformation:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:for-each select="distinct-values(/*/test/Cell2/tokenize(.,'[ ,]'))">
<Cell2><xsl:value-of select="."/></Cell2>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<sample>
<test>
<Cell1>John</Cell1>
<Cell2>A</Cell2>
<Cell4>xy</Cell4>
</test>
<test>
<Cell1>Jack</Cell1>
<Cell2>B</Cell2>
<Cell3>Red</Cell3>
<Cell6>10</Cell6>
</test>
<test>
<Cell1>John,Jade</Cell1>
<Cell2>A,Y</Cell2>
<Cell4>1</Cell4>
</test>
<test>
<Cell1>John,Jade</Cell1>
<Cell2>A B,X</Cell2>
<Cell3>Blue</Cell3>
</test>
</sample>
produces the wanted, correct result:
<Cell2>A</Cell2>
<Cell2>B</Cell2>
<Cell2>Y</Cell2>
<Cell2>X</Cell2>
Explanation:
The XPath expression in the select
attribute of xsl:for-each
is the key to understand:
distinct-values(/*/test/Cell2/tokenize(.,'[ ,]'))
This produces the distinct values of the sequence of all tokenized (string values of) /*/test/Cell2
For a start, if you are tokenizing on either a comma or a space, then you could combine your tokenize into one expression
<xsl:for-each select="tokenize(., ',|\s')">
What you could do, is first match all Cell2 elements, with your template tokenizing the contents, but put the results in a variable
<xsl:variable name="cells">
<xsl:apply-templates select="test/Cell2"/>
</xsl:variable>
Then you could simply use the xsl:for-each-group to iterate over them
<xsl:for-each-group select="$cells/Cell2" group-by="text()">
<xsl:copy-of select="."/>
</xsl:for-each-group>
Here is the full XSLT
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="sample">
<xsl:variable name="cells">
<xsl:apply-templates select="test/Cell2"/>
</xsl:variable>
<xsl:for-each-group select="$cells/Cell2" group-by="text()">
<xsl:copy-of select="."/>
</xsl:for-each-group>
</xsl:template>
<xsl:template match="Cell2">
<xsl:for-each select="tokenize(., ',|\s')">
<Cell2>
<xsl:value-of select="."/>
</Cell2>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
When applied to your sample XML, the following is output
<Cell2>A</Cell2>
<Cell2>B</Cell2>
<Cell2>Y</Cell2>
<Cell2>X</Cell2>
When you have deeply nested xsl:for-each instructions, the context item changes at each level, so you often need to bind a variable to the context item at each level so you can get back to it. However, deeply-nested xsl:for-each instructions are sometimes an indication that you should be breaking up your code into smaller templates or functions.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With