Search code examples
xsltbiztalkxslt-1.0biztalk-2009biztalk-mapper

XSLT Transforming sequential XML to hierarchical XML


I have a requirement to transform a sequential XML node list into a hierarchical, but I run into some XSLT specific knowledge gap. The input XML contains articles, colors and sizes. In the sample below 'Record1' is an article, 'Record2' represents a color and 'Record3' are the sizes. The number of colors and sizes (record2 and record3) elements can vary.

<root>
 <Record1>...</Record1>
 <Record2>...</Record2>
 <Record3>...</Record3>
 <Record3>...</Record3>
 <Record2>...</Record2>
 <Record3>...</Record3>
 <Record3>...</Record3>
 <Record3>...</Record3>
 <Record3>...</Record3>
 <Record1>...</Record1>
 <Record2>...</Record2>
 <Record3>...</Record3>
 <Record3>...</Record3>
 <Record2>...</Record2>
 <Record3>...</Record3>
 <Record3>...</Record3>
 <Record3>...</Record3>
 <Record3>...</Record3>
</root> 

All fields are on the same hierarchical level, but still I have to create this structure as output:

<root>
 <article>              -> Record1
  <color>               -> Record2
   <size>...</size>     -> Record3
   <size>...</size>     -> Record3
  </color>
  <color>               -> Record2
   <size>...</size>     -> Record3
   <size>...</size>     -> Record3
   <size>...</size>     -> Record3
   <size>...</size>     -> Record3
  </color>
 </article>
 <article>              -> Record1
  <color>               -> Record2
   <size>...</size>     -> Record3
   <size>...</size>     -> Record3
  </color>
  <color>               -> Record2
   <size>...</size>     -> Record3
   <size>...</size>     -> Record3
   <size>...</size>     -> Record3
   <size>...</size>     -> Record3
  </color>
 </article>
</root>

I've tried to iterate the nodes sequentially but for example the 'article' (=record1) node tag needs to remain unclosed while 'color' (=record2) nodes are processed. The same counts for processing 'size' (=record3) having 'color' unclosed, but that is not allowed by XSLT. My next idea was to call a template for every article, color and size level, but I don't know how to select for example all 'record3' nodes between the current 'record2' and the next article represented by 'record1'.

I've also got a limitation on the XSLT version because I need this transformation in BizTalk Server which only supports XSLT 1.0

Can someone push me in the right direction?


Solution

  • Here's one XSLT 1.0 option. I'm not sure what you wanted to do with the values of Record1 and Record2, so I put them in a val attribute.

    XML Input

    <root>
        <Record1>article1</Record1>
        <Record2>color1</Record2>
        <Record3>size1</Record3>
        <Record3>size2</Record3>
        <Record2>color2</Record2>
        <Record3>size3</Record3>
        <Record3>size4</Record3>
        <Record3>size5</Record3>
        <Record3>size6</Record3>
        <Record1>article2</Record1>
        <Record2>color3</Record2>
        <Record3>size7</Record3>
        <Record3>size8</Record3>
        <Record2>color4</Record2>
        <Record3>size9</Record3>
        <Record3>size10</Record3>
        <Record3>size11</Record3>
        <Record3>size12</Record3>
    </root>
    

    XSLT 1.0

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:output indent="yes"/>
        <xsl:strip-space elements="*"/>
    
        <xsl:template match="/*">
            <xsl:copy>
                <xsl:apply-templates select="Record1"/>
            </xsl:copy>
        </xsl:template>
    
        <xsl:template match="Record1">
            <article val="{.}">
                <xsl:apply-templates select="following-sibling::Record2[generate-id(preceding-sibling::Record1[1])=generate-id(current())]"/>
            </article>
        </xsl:template>
    
        <xsl:template match="Record2">
            <color val="{.}">
                <xsl:apply-templates select="following-sibling::Record3[generate-id(preceding-sibling::Record2[1])=generate-id(current())]"/>
            </color>
        </xsl:template>
    
        <xsl:template match="Record3">
            <size>
                <xsl:value-of select="."/>
            </size>
        </xsl:template>
    
    </xsl:stylesheet>
    

    XML Output

    <root>
       <article val="article1">
          <color val="color1">
             <size>size1</size>
             <size>size2</size>
          </color>
          <color val="color2">
             <size>size3</size>
             <size>size4</size>
             <size>size5</size>
             <size>size6</size>
          </color>
       </article>
       <article val="article2">
          <color val="color3">
             <size>size7</size>
             <size>size8</size>
          </color>
          <color val="color4">
             <size>size9</size>
             <size>size10</size>
             <size>size11</size>
             <size>size12</size>
          </color>
       </article>
    </root>