1

I am converting same multi tag elements into different records. In this i am using XSLT. I need help in XSLT. i am getting first tag result is correct, nut idont know why second tags not getting.

Explanation:: *In First CD :: Single atrist to many titles. I want to split this into defferent tags based on title. In Second CD:: First title by First artist and second title by second artist. I need this also in different(two) CD tags based on both title and artist.[First title-first artist and second title-second aritst].*

My Source XML is Like Following::

<?xml version="1.0" encoding="ISO-8859-1"?>
<!-- Edited by XMLSpy® -->
<catalog>
    <cd>
        <title>Burlesque</title>        
        <title>Empire</title>
        <title>Emp</title>
        <title>Em</title>
        <artist>Bob Dylan</artist>
        <country>USA</country>
        <company>Columbia</company>
        <price>10.90</price>
        <year>1985</year>
    </cd>
    <cd>
        <title>Hide your</title>
        <title>heart</title>
        <artist>Bonnie</artist>
        <artist> Tyler</artist>
        <country>UK</country>
        <company>CBS Records</company>
        <price>9.90</price>
        <year>1988</year>
    </cd>
    <cd>
        <title/>
        <artist>Dolly Parton</artist>
        <country>USA</country>
        <company>RCA</company>
        <price>9.90</price>
        <year>1982</year>
    </cd>
</catalog>

XSLT ::

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"  >
    <xsl:output method="xml" indent="yes"/>
    <xsl:key name="k" match="title" use="text()"/>
    <xsl:key name="l" match="artist" use="text()"/>
            <xsl:template match="/">
        <catalog>
            <xsl:apply-templates select="//cd/title | artist[not(node() = preceding-sibling::node())]"/>
        </catalog>
    </xsl:template>
    <xsl:template match="//cd">
        <xsl:param name="title" select="title"/>
        <xsl:param name="artist" select="artist"/>
        <cd>
            <xsl:copy-of select="key('k', $title)[not(node() = preceding-sibling::node())]"/>
            <xsl:copy-of select="key('l', $artist)[not(node() = preceding-sibling::node())]"/>
            <xsl:copy-of select="./*[name() != 'title' and 'artist']"/>
        </cd>
    </xsl:template>
    <xsl:template match="title">
            <xsl:apply-templates select="..">
            <xsl:with-param name="title" select="."/>
        </xsl:apply-templates>
    </xsl:template>
</xsl:stylesheet>

What i am getting ::

<?xml version="1.0" encoding="utf-8"?>
<catalog>
  <cd>
    <title>Burlesque</title>
    <artist>Bob Dylan</artist>
    <artist>Bob Dylan</artist>
    <country>USA</country>
    <company>Columbia</company>
    <price>10.90</price>
    <year>1985</year>
  </cd>
  <cd>
    <title>Empire</title>
    <artist>Bob Dylan</artist>
    <artist>Bob Dylan</artist>
    <country>USA</country>
    <company>Columbia</company>
    <price>10.90</price>
    <year>1985</year>
  </cd>
  <cd>
    <title>Emp</title>
    <artist>Bob Dylan</artist>
    <artist>Bob Dylan</artist>
    <country>USA</country>
    <company>Columbia</company>
    <price>10.90</price>
    <year>1985</year>
  </cd>
  <cd>
    <title>Em</title>
    <artist>Bob Dylan</artist>
    <artist>Bob Dylan</artist>
    <country>USA</country>
    <company>Columbia</company>
    <price>10.90</price>
    <year>1985</year>
  </cd>
  <cd>
    <title>Hide your</title>
    <artist>Bonnie</artist>
    <artist> Tyler</artist>
    <artist>Bonnie</artist>
    <artist> Tyler</artist>
    <country>UK</country>
    <company>CBS Records</company>
    <price>9.90</price>
    <year>1988</year>
  </cd>
  <cd>
    <title>heart</title>
    <artist>Bonnie</artist>
    <artist> Tyler</artist>
    <artist>Bonnie</artist>
    <artist> Tyler</artist>
    <country>UK</country>
    <company>CBS Records</company>
    <price>9.90</price>
    <year>1988</year>
  </cd>
  <cd>
    <artist>Dolly Parton</artist>
    <artist>Dolly Parton</artist>
    <country>USA</country>
    <company>RCA</company>
    <price>9.90</price>
    <year>1982</year>
  </cd>
 </catalog>

What I need ::

<?xml version="1.0" encoding="utf-8"?>
<catalog>
  <cd>
    <title>Burlesque</title>
    <artist>Bob Dylan</artist>
    <country>USA</country>
    <company>Columbia</company>
    <price>10.90</price>
    <year>1985</year>
  </cd>
  <cd>
    <title>Empire</title>
    <artist>Bob Dylan</artist>
    <country>USA</country>
    <company>Columbia</company>
    <price>10.90</price>
    <year>1985</year>
  </cd>
  <cd>
    <title>Emp</title>
    <artist>Bob Dylan</artist>
    <country>USA</country>
    <company>Columbia</company>
    <price>10.90</price>
    <year>1985</year>
  </cd>
  <cd>
    <title>Em</title>
    <artist>Bob Dylan</artist>
    <country>USA</country>
    <company>Columbia</company>
    <price>10.90</price>
    <year>1985</year>
  </cd>
  <cd>
    <title>Hide your</title>
    <artist>Bonnie</artist>
    <country>UK</country>
    <company>CBS Records</company>
    <price>9.90</price>
    <year>1988</year>
  </cd>
  <cd>
    <title>heart</title>
    <artist> Tyler</artist>
    <country>UK</country>
    <company>CBS Records</company>
    <price>9.90</price>
    <year>1988</year>
  </cd>
  <cd>
    <artist>Dolly Parton</artist>
    <artist>Dolly Parton</artist>
    <country>USA</country>
    <company>RCA</company>
    <price>9.90</price>
    <year>1982</year>
  </cd>
</catalog>

Can any one help me in this ???

ram.bi
  • 283
  • 4
  • 15

1 Answers1

1

This is an application of a "generic XML shredding" transformation, where the parameter is set to the wanted leaf nodes and a minor post-processing is done to remove two top wrapping nodes from the result and re-wrap:

<xsl:stylesheet version="1.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
     xmlns:ext="http://exslt.org/common" exclude-result-prefixes="ext"
>
     <xsl:output omit-xml-declaration="yes" indent="yes"/>
     <xsl:strip-space elements="*"/>

     <xsl:param name="pLeafNodes" select="/*/*/title"/>

     <xsl:template match="/">
      <xsl:variable name="vrtfResult">
        <xsl:call-template name="StructRepro"/>
      </xsl:variable>

      <catalog>
       <xsl:copy-of select="ext:node-set($vrtfResult)/catalog/node()"/>
      </catalog>
     </xsl:template>

     <xsl:template name="StructRepro">
       <xsl:param name="pLeaves" select="$pLeafNodes"/>

       <xsl:for-each select="$pLeaves">
         <xsl:apply-templates mode="build" select="/*">
          <xsl:with-param name="pChild" select="."/>
          <xsl:with-param name="pLeaves" select="$pLeaves"/>
         </xsl:apply-templates>
       </xsl:for-each>
     </xsl:template>

      <xsl:template mode="build" match="node()|@*">
          <xsl:param name="pChild"/>
          <xsl:param name="pLeaves"/>

         <xsl:copy>
           <xsl:apply-templates mode="build" select="@*"/>

           <xsl:variable name="vLeafChild" select=
             "*[count(.|$pChild) = count($pChild)]"/>

           <xsl:choose>
            <xsl:when test="$vLeafChild">
             <xsl:apply-templates mode="build"
                 select="$vLeafChild
                        |
                          node()[not(count(.|$pLeaves) = count($pLeaves))]">
                 <xsl:with-param name="pChild" select="$pChild"/>
                 <xsl:with-param name="pLeaves" select="$pLeaves"/>
             </xsl:apply-templates>
            </xsl:when>
            <xsl:otherwise>
             <xsl:apply-templates mode="build" select=
             "node()[not(.//*[count(.|$pLeaves) = count($pLeaves)])
                    or
                     .//*[count(.|$pChild) = count($pChild)]
                    ]
             ">

                 <xsl:with-param name="pChild" select="$pChild"/>
                 <xsl:with-param name="pLeaves" select="$pLeaves"/>
             </xsl:apply-templates>
            </xsl:otherwise>
           </xsl:choose>
         </xsl:copy>
     </xsl:template>
     <xsl:template match="text()"/>
</xsl:stylesheet>

when this transformation is applied on the provided XML document:

<catalog>
    <cd>
        <title>Burlesque</title>
        <title>Empire</title>
        <title>Emp</title>
        <title>Em</title>
        <artist>Bob Dylan</artist>
        <country>USA</country>
        <company>Columbia</company>
        <price>10.90</price>
        <year>1985</year>
    </cd>
    <cd>
        <title>Hide your</title>
        <title>heart</title>
        <artist>Bonnie</artist>
        <artist> Tyler</artist>
        <country>UK</country>
        <company>CBS Records</company>
        <price>9.90</price>
        <year>1988</year>
    </cd>
    <cd>
        <title/>
        <artist>Dolly Parton</artist>
        <country>USA</country>
        <company>RCA</company>
        <price>9.90</price>
        <year>1982</year>
    </cd>
</catalog>

the wanted, correct result is produced:

<catalog>
   <cd>
      <title>Burlesque</title>
      <artist>Bob Dylan</artist>
      <country>USA</country>
      <company>Columbia</company>
      <price>10.90</price>
      <year>1985</year>
   </cd>
   <cd>
      <title>Empire</title>
      <artist>Bob Dylan</artist>
      <country>USA</country>
      <company>Columbia</company>
      <price>10.90</price>
      <year>1985</year>
   </cd>
   <cd>
      <title>Emp</title>
      <artist>Bob Dylan</artist>
      <country>USA</country>
      <company>Columbia</company>
      <price>10.90</price>
      <year>1985</year>
   </cd>
   <cd>
      <title>Em</title>
      <artist>Bob Dylan</artist>
      <country>USA</country>
      <company>Columbia</company>
      <price>10.90</price>
      <year>1985</year>
   </cd>
   <cd>
      <title>Hide your</title>
      <artist>Bonnie</artist>
      <artist> Tyler</artist>
      <country>UK</country>
      <company>CBS Records</company>
      <price>9.90</price>
      <year>1988</year>
   </cd>
   <cd>
      <title>heart</title>
      <artist>Bonnie</artist>
      <artist> Tyler</artist>
      <country>UK</country>
      <company>CBS Records</company>
      <price>9.90</price>
      <year>1988</year>
   </cd>
   <cd>
      <title/>
      <artist>Dolly Parton</artist>
      <country>USA</country>
      <company>RCA</company>
      <price>9.90</price>
      <year>1982</year>
   </cd>
</catalog>

Update: The OP has changed his question and he is asking that for CDs with more than one artist there should be an additional split by artist.

Here is the solution -- it is a two-pass-processing. In the first pass the document is converted to a catalog in which each cd has a single artist. Then the second pass is the solution I already gave above:

<xsl:stylesheet version="1.0"
         xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
         xmlns:ext="http://exslt.org/common" exclude-result-prefixes="ext">
         <xsl:output omit-xml-declaration="yes" indent="yes"/>
         <xsl:strip-space elements="*"/>

         <xsl:template match="/">

          <xsl:variable name="vrtfPass1">
            <xsl:apply-templates mode="pass1"/>
          </xsl:variable>

          <xsl:variable name="vPass1" select="ext:node-set($vrtfPass1)"/>

          <xsl:variable name="pLeafNodes" select="$vPass1/*/*/title"/>

          <xsl:variable name="vrtfResult">
            <xsl:call-template name="StructRepro">
              <xsl:with-param name="pLeaves" select="$pLeafNodes"/>
            </xsl:call-template>
          </xsl:variable>

          <catalog>
            <xsl:copy-of select="ext:node-set($vrtfResult)/catalog/node()"/>
          </catalog>
         </xsl:template>

         <xsl:template name="StructRepro">
           <xsl:param name="pLeaves"/>

           <xsl:variable name="vDoc" select=
               "$pLeaves[1]/ancestor::node()[last()]"/>

           <xsl:for-each select="$pLeaves">
             <xsl:apply-templates mode="build" select="$vDoc/*">
              <xsl:with-param name="pChild" select="."/>
              <xsl:with-param name="pLeaves" select="$pLeaves"/>
             </xsl:apply-templates>
           </xsl:for-each>
         </xsl:template>

          <xsl:template mode="build" match="node()|@*">
              <xsl:param name="pChild"/>
              <xsl:param name="pLeaves"/>

             <xsl:copy>
               <xsl:apply-templates mode="build" select="@*"/>

               <xsl:variable name="vLeafChild" select=
                 "*[count(.|$pChild) = count($pChild)]"/>

               <xsl:choose>
                <xsl:when test="$vLeafChild">
                 <xsl:apply-templates mode="build"
                     select="$vLeafChild
                            |
                              node()[not(count(.|$pLeaves) = count($pLeaves))]">
                     <xsl:with-param name="pChild" select="$pChild"/>
                     <xsl:with-param name="pLeaves" select="$pLeaves"/>
                 </xsl:apply-templates>
                </xsl:when>
                <xsl:otherwise>
                 <xsl:apply-templates mode="build" select=
                 "node()[not(.//*[count(.|$pLeaves) = count($pLeaves)])
                        or
                         .//*[count(.|$pChild) = count($pChild)]
                        ]
                 ">

                     <xsl:with-param name="pChild" select="$pChild"/>
                     <xsl:with-param name="pLeaves" select="$pLeaves"/>
                 </xsl:apply-templates>
                </xsl:otherwise>
               </xsl:choose>
             </xsl:copy>
         </xsl:template>

 <xsl:template match="node()|@*" mode="pass1">
     <xsl:copy>
       <xsl:apply-templates select="node()|@*" mode="pass1"/>
     </xsl:copy>
 </xsl:template>

 <xsl:template match="cd[artist[2]]" mode="pass1">
  <xsl:for-each select="artist">
   <xsl:apply-templates select=".." mode="singleArtist">
     <xsl:with-param name="pArtistPos" select="position()"/>
   </xsl:apply-templates>
  </xsl:for-each>
 </xsl:template>

 <xsl:template match="cd" mode="singleArtist">
   <xsl:param name="pArtistPos"/>

   <cd>
     <xsl:apply-templates mode="pass1" select=
        "title[position() = $pArtistPos]"/>
     <xsl:apply-templates mode="pass1" select=
         "artist[position() = $pArtistPos]"/>
     <xsl:apply-templates mode="pass1" select=
         "node()[not(self::title or self::artist)]"/>
   </cd>
 </xsl:template>

 <xsl:template match="text()"/>
</xsl:stylesheet>

When this transformation is applied on the same XML document (above), the result now satisfies the additional requirements:

<catalog>
   <cd>
      <title>Burlesque</title>
      <artist>Bob Dylan</artist>
      <country>USA</country>
      <company>Columbia</company>
      <price>10.90</price>
      <year>1985</year>
   </cd>
   <cd>
      <title>Empire</title>
      <artist>Bob Dylan</artist>
      <country>USA</country>
      <company>Columbia</company>
      <price>10.90</price>
      <year>1985</year>
   </cd>
   <cd>
      <title>Emp</title>
      <artist>Bob Dylan</artist>
      <country>USA</country>
      <company>Columbia</company>
      <price>10.90</price>
      <year>1985</year>
   </cd>
   <cd>
      <title>Em</title>
      <artist>Bob Dylan</artist>
      <country>USA</country>
      <company>Columbia</company>
      <price>10.90</price>
      <year>1985</year>
   </cd>
   <cd>
      <title>Hide your</title>
      <artist>Bonnie</artist>
      <country>UK</country>
      <company>CBS Records</company>
      <price>9.90</price>
      <year>1988</year>
   </cd>
   <cd>
      <title>heart</title>
      <artist> Tyler</artist>
      <country>UK</country>
      <company>CBS Records</company>
      <price>9.90</price>
      <year>1988</year>
   </cd>
   <cd>
      <title/>
      <artist>Dolly Parton</artist>
      <country>USA</country>
      <company>RCA</company>
      <price>9.90</price>
      <year>1982</year>
   </cd>
</catalog>
Community
  • 1
  • 1
Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431
  • Once please see tag. It is coming two times. Hide your Bonnie Tyler UK CBS Records 9.90 1988 heart Bonnie Tyler UK CBS Records 9.90 1988 – ram.bi Mar 20 '12 at 13:12
  • @ram.bi: I see, so what you want is even more complicated than simple shredding and different artists need to be combined with different titles. But then you must edit the question and expain which artist should get matched to which title. It is possible that both artists sing the same song (which I assumed), or that both artists sing both songs, or that the first artist sings the second song (imagine artists and songs are ordered alphabetically by name. Please, edit the question and specify the exact rules. – Dimitre Novatchev Mar 20 '12 at 13:37
  • Hi Dimitre, Thanks for responce i added explanation, can please put me in the right deirection. – ram.bi Mar 21 '12 at 09:46
  • @ram.bi: Sure, do have a look at my update at the end of this answer :) – Dimitre Novatchev Mar 21 '12 at 12:45
  • I am using that XML and XSLT in SQL Server Integration Services XML Task at that time i am getting an error like:: Cannot find the script or external object that implements prefix 'ext'. i dont have much knowledge on XSLT can please help me ? – ram.bi Mar 26 '12 at 07:19
  • @ram.bi: Yes, it is likely you are using a MSXSL or another Microsoft XSLT processor that doesn't implement EXSLT. You can use the msxsl:node-set() exension function. Add to the `xsl:stylesheet` instruction the following: `xmlns:msxsl="urn:schemas-microsoft-com:xslt"` and replace in the code `ext:node-set($vrtfPass1)` with `msxsl:node-set($vrtfPass1)` – Dimitre Novatchev Mar 26 '12 at 12:53
  • Thank you Dimitre. i have some doubts can you please clarify me, those are 1. in my source XML have hexa decimal values how i can remove those. 2.Tags are like i need to change this to , i have to replace . with _ in all tags how can i achive these ? Thanks, – ram.bi Mar 27 '12 at 10:55
  • @ram.bi: These are two new, different and separate questions. Please, ask them as new questions.And No, the provided XML document in *this* question doesn't contain either hexadecimal characters or elements named `FIRST.NAME`. – Dimitre Novatchev Mar 27 '12 at 12:41