foreach inside tokenize
Asked Answered
I

3

6

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>

Conditions:

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??

Intendancy answered 27/9, 2012 at 10:17 Comment(0)
O
6

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

Ostensory answered 27/9, 2012 at 14:47 Comment(0)
S
4

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>
Salian answered 27/9, 2012 at 10:56 Comment(2)
TimC, the version attribute must be "2.0". And @Shil, there is a much simpler solution.Ostensory
Ah yes, I have corrected that. And yes, your solution is much simpler!Salian
P
2

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.

Pinguid answered 27/9, 2012 at 13:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.