user1484782
user1484782

Reputation: 107

XSLT : generate csv from xml which has multiple occurences of tags complex scenario

For this xml:

 <G>
        <P>
            <A>
                <b>value b</b>
                <c>value c</c>
            </A>
            <A>
                <b>value b2</b>
                <c>value c2</c>
            </A>
            <D>value ew</D>
        <D>value e2</D>     
            <E>value f</E>
        </P>
        <P>
            <A>
                <b>value bx</b>
                <c>value cx</c>
            </A>
            <A>
                <b>value b2x</b>
                <c>value c2x</c>
            </A>
             <D>value exw</D>
        <D>value ex2</D>

         <D>value ex2</D>
            <E>value fx</E>
        </P>
    </G>

The tags like A and D can occur multiple times , the original xml on which I am working has large number of tags and many of them have multiple occurences. I have to genetrate output as :
value b     value c     value ew     value f
value b     value c     value e2     value f
value b2     value c2     value ew     value f
value b2     value c2     value e2     value f
value bx     value cx     value exw     value fx
value bx     value cx     value ex2     value fx
value bx     value cx     value ex3     value fx
value b2x     value c2x     value exw     value fx
value b2x     value c2x     value ex2     value fx
value b2x     value c2x     value ex3     value fx

This xslt generates correct output if A tag has multiple occurence ,but when other tags like D in above xml repeat this does not work.

<xsl:for-each select="//A">
    <xsl:value-of select="b"/>
    <xsl:text>,</xsl:text>
    <xsl:value-of select="c"/>
    <xsl:text>,</xsl:text>
    <xsl:value-of select="ancestor::P/D"/>
    <xsl:text>,</xsl:text>
    <xsl:value-of select="ancestor::P/E"/>
    <xsl:text>  
</xsl:text>
</xsl:for-each>

Please suggest correct xslt

Upvotes: 0

Views: 216

Answers (2)

Tim C
Tim C

Reputation: 70618

I think this can be achieved by using repeated calls to templates. First you have a template to match A but instead of outputting the b and c elements, you pass them as parameters to a template that matches the D elemenet

<xsl:template match="A">
  <xsl:apply-templates select="following-sibling::D">
     <xsl:with-param name="prefix">
        <xsl:value-of select="b"/>
        <xsl:text>,</xsl:text>
        <xsl:value-of select="c"/>
     </xsl:with-param>
  </xsl:apply-templates>
</xsl:template>

And then, within the the template that matches the D element, you concatenate the prefix with the value of D, and pass it to a template that matches the E element.

<xsl:template match="D">
  <xsl:param name="prefix"/>
  <xsl:apply-templates select="following-sibling::E">
     <xsl:with-param name="prefix">
        <xsl:value-of select="$prefix"/>
        <xsl:text>,</xsl:text>
        <xsl:value-of select="."/>
     </xsl:with-param>
  </xsl:apply-templates>
</xsl:template>

And then the template that matches the E element can output the whole string. Here is the full XSLT

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:output method="text" indent="yes"/>

   <xsl:template match="/">
      <xsl:apply-templates select="//A"/>
   </xsl:template>

   <xsl:template match="A">
      <xsl:apply-templates select="following-sibling::D">
         <xsl:with-param name="prefix">
            <xsl:value-of select="b"/>
            <xsl:text>,</xsl:text>
            <xsl:value-of select="c"/>
         </xsl:with-param>
      </xsl:apply-templates>
   </xsl:template>

   <xsl:template match="D">
      <xsl:param name="prefix"/>
      <xsl:apply-templates select="following-sibling::E">
         <xsl:with-param name="prefix">
            <xsl:value-of select="$prefix"/>
            <xsl:text>,</xsl:text>
            <xsl:value-of select="."/>
         </xsl:with-param>
      </xsl:apply-templates>
   </xsl:template>

   <xsl:template match="E">
      <xsl:param name="prefix"/>
      <xsl:value-of select="$prefix"/>
      <xsl:text>,</xsl:text>
      <xsl:value-of select="."/>
      <xsl:value-of select="'&#13;'"/>
   </xsl:template>
</xsl:stylesheet>

When applied to your XML sample, the following is output

value b,value c,value ew,value f
value b,value c,value e2,value f
value b2,value c2,value ew,value f
value b2,value c2,value e2,value f
value bx,value cx,value exw,value fx
value bx,value cx,value ex2,value fx
value bx,value cx,value ex2,value fx
value b2x,value c2x,value exw,value fx
value b2x,value c2x,value ex2,value fx
value b2x,value c2x,value ex2,value fx

Upvotes: 1

BxlSofty
BxlSofty

Reputation: 501

Here's a possible solution:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text"/>
    <xsl:variable name='s' select='","' />
    <xsl:template match="A">
        <xsl:variable name='b' select='b' />
        <xsl:variable name='c' select='c' />
        <xsl:for-each select="../D"><xsl:value-of select='$b' /><xsl:value-of select='$s' /><xsl:value-of select='$c' /><xsl:value-of select='$s' /><xsl:value-of select='.'/><xsl:value-of select='$s' /><xsl:value-of select='../E'/><xsl:text>
</xsl:text></xsl:for-each>
    </xsl:template>
    <xsl:template match="text()" />
</xsl:stylesheet>

Note that it looks for all A, and for each A outputs a line for each D sibling of A. Note sure that this is what you wanted

It gives the output:

value b,value c,value ew,value f
value b,value c,value e2,value f
value b2,value c2,value ew,value f
value b2,value c2,value e2,value f
value bx,value cx,value exw,value fx
value bx,value cx,value ex2,value fx
value bx,value cx,value ex2,value fx
value b2x,value c2x,value exw,value fx
value b2x,value c2x,value ex2,value fx
value b2x,value c2x,value ex2,value fx

Upvotes: 1

Related Questions