Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

foreach inside tokenize

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

like image 227
Shil Avatar asked Sep 27 '12 10:09

Shil


3 Answers

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

like image 173
Dimitre Novatchev Avatar answered Nov 13 '22 17:11

Dimitre Novatchev


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>
like image 4
Tim C Avatar answered Nov 13 '22 18:11

Tim C


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.

like image 2
Michael Kay Avatar answered Nov 13 '22 17:11

Michael Kay