How to transform attributes into tags using XSLT and avoid duplicates?

199 views Asked by At

first of all, I spend a lot of time here and got often help in the already given answers here. But now I facing a new topic for me - XSLT - and I'm really need help now.

Ok, the problem I have is to transform a XML file I receive from Excel to reuse it in a XFA form. Excel only support flat data but this doesn't match the date hierarchy of my XFA form.

Here is the data I receive from Excel:

<Accounts>
    <Account Category="001" Region="AAA" Name="dolor" Value="123" Type="A" Rating="1,25"/>
    <Account Category="001" Region="AAA" Name="sit amet" Value="134" Type="A" Rating="1,25"/>
    <Account Category="004" Region="BBB" Name="consetetur" Value="434" Type="A" Rating="1,25"/>
    <Account Category="002" Region="AAA" Name="sadipscing" Value="84" Type="A" Rating="1,25"/>
    <Account Category="007" Region="ZZZ" Name="elitr" Value="33" Type="A" Rating="1,25"/>
    <Account Category="004" Region="CCC" Name="aliquyam" Value="6" Type="A" Rating="1,25"/>
    <Account Category="001" Region="BBB" Name="ipsum" Value="34" Type="A" Rating="1,25"/>
    <Account Category="003" Region="ZZZ" Name="lorem" Value="75" Type="A" Rating="2.87"/>
</Accounts>

And this is the way I want to transform it:

<Accounts>
    <Category name="001">
        <Region name="AAA">
            <Account Name="dolor" Value="123" Type="A" Rating="1,25"/>
            <Account Name="sit amet" Value="134" Type="A" Rating="1,25"/>
        </Region>   
        <Region name="BBB">
            <Account Name="ipsum" Value="34" Type="A" Rating="1,25"/>
        </Region>
    </Category>
    <Category name="002">
        <Region name="BBB">
            <Account Name="sadipscing" Value="84" Type="A" Rating="1,25"/>
        </Region>
    </Category>
    <Category name="003">
        <Region name="ZZZ">
            <Account Name="lorem" Value="75" Type="A" Rating="2.87"/>
        </Region>
    </Category>
    ...
</Accounts>

I already spend a couple of days but the only thing I've reached was to create new tags for the Category without duplicates. Here's my current XSLT file.

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

<xsl:variable name="Root" select="Accounts" />
<xsl:variable name="CategoryList" select="$Root/Account[not(@Category=following::Account/@Category)]" />

<xsl:template match="/">
    <Accounts>
        <xsl:for-each select="$CategoryList">
            <xsl:variable name="varCategory" select="./@Category" />
            <Category>
                <xsl:value-of select="$varCategory"/>
            </Category>
        </xsl:for-each>
    </Accounts>
</xsl:template>
</xsl:stylesheet>

Any help is welcome.

1

There are 1 answers

1
G. Ken Holman On BEST ANSWER

You don't say if you are using XSLT 1.0 or XSLT 2.0.

This is a multi-level sort that, for XSLT 1.0, I tell my students is more easily written using the variable-based grouping method.

For XSLT 2.0 it is very easily done with available constructs.

Both solutions are here:

T:\ftemp>type mxps.xml 
<Accounts>
    <Account Category="001" Region="AAA" Name="dolor" Value="123" Type="A" Rating="1,25"/>
    <Account Category="001" Region="AAA" Name="sit amet" Value="134" Type="A" Rating="1,25"/>
    <Account Category="004" Region="BBB" Name="consetetur" Value="434" Type="A" Rating="1,25"/>
    <Account Category="002" Region="AAA" Name="sadipscing" Value="84" Type="A" Rating="1,25"/>
    <Account Category="007" Region="ZZZ" Name="elitr" Value="33" Type="A" Rating="1,25"/>
    <Account Category="004" Region="CCC" Name="aliquyam" Value="6" Type="A" Rating="1,25"/>
    <Account Category="001" Region="BBB" Name="ipsum" Value="34" Type="A" Rating="1,25"/>
    <Account Category="003" Region="ZZZ" Name="lorem" Value="75" Type="A" Rating="2.87"/>
</Accounts>
T:\ftemp>call xslt mxps.xml mxps.xsl 
<?xml version="1.0" encoding="utf-8"?>
<Accounts>
   <Category name="001">
      <Region name="AAA">
         <Account Name="dolor" Value="123" Type="A" Rating="1,25"/>
         <Account Name="sit amet" Value="134" Type="A" Rating="1,25"/>
      </Region>
      <Region name="BBB">
         <Account Name="ipsum" Value="34" Type="A" Rating="1,25"/>
      </Region>
   </Category>
   <Category name="002">
      <Region name="AAA">
         <Account Name="sadipscing" Value="84" Type="A" Rating="1,25"/>
      </Region>
   </Category>
   <Category name="003">
      <Region name="ZZZ">
         <Account Name="lorem" Value="75" Type="A" Rating="2.87"/>
      </Region>
   </Category>
   <Category name="004">
      <Region name="BBB">
         <Account Name="consetetur" Value="434" Type="A" Rating="1,25"/>
      </Region>
      <Region name="CCC">
         <Account Name="aliquyam" Value="6" Type="A" Rating="1,25"/>
      </Region>
   </Category>
   <Category name="007">
      <Region name="ZZZ">
         <Account Name="elitr" Value="33" Type="A" Rating="1,25"/>
      </Region>
   </Category>
</Accounts>
T:\ftemp>type mxps.xsl 
<?xml version="1.0" encoding="US-ASCII"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                version="1.0">

<xsl:output indent="yes"/>

<xsl:template match="Accounts">
  <Accounts>
    <xsl:variable name="accounts" select="Account"/>
    <xsl:for-each select="$accounts">
      <xsl:sort select="@Category"/>
      <xsl:if test="generate-id(.)=
                    generate-id($accounts[@Category=current()/@Category][1])">
        <Category name="{@Category}">
          <xsl:variable name="categories"
                        select="$accounts[@Category=current()/@Category]"/>
          <xsl:for-each select="$categories">
            <xsl:sort select="@Region"/>
            <xsl:if test="generate-id(.)=
                       generate-id($categories[@Region=current()/@Region][1])">
              <Region name="{@Region}">
                <xsl:for-each select="$categories[@Region=current()/@Region]">
                  <xsl:sort select="@Name"/>
                  <Account>
                    <xsl:copy-of select="@Name"/>
                    <xsl:copy-of select="@Value"/>
                    <xsl:copy-of select="@Type"/>
                    <xsl:copy-of select="@Rating"/>
                  </Account>
                </xsl:for-each>
              </Region>
            </xsl:if>
          </xsl:for-each>
        </Category>
      </xsl:if>
    </xsl:for-each>
  </Accounts>
</xsl:template>

</xsl:stylesheet>
T:\ftemp>call xslt2 mxps.xml mxps2.xsl 
<?xml version="1.0" encoding="UTF-8"?>
<Accounts>
   <Category name="001">
      <Region name="AAA">
         <Account Name="dolor" Value="123" Type="A" Rating="1,25"/>
         <Account Name="sit amet" Value="134" Type="A" Rating="1,25"/>
      </Region>
      <Region name="BBB">
         <Account Name="ipsum" Value="34" Type="A" Rating="1,25"/>
      </Region>
   </Category>
   <Category name="002">
      <Region name="AAA">
         <Account Name="sadipscing" Value="84" Type="A" Rating="1,25"/>
      </Region>
   </Category>
   <Category name="003">
      <Region name="ZZZ">
         <Account Name="lorem" Value="75" Type="A" Rating="2.87"/>
      </Region>
   </Category>
   <Category name="004">
      <Region name="BBB">
         <Account Name="consetetur" Value="434" Type="A" Rating="1,25"/>
      </Region>
      <Region name="CCC">
         <Account Name="aliquyam" Value="6" Type="A" Rating="1,25"/>
      </Region>
   </Category>
   <Category name="007">
      <Region name="ZZZ">
         <Account Name="elitr" Value="33" Type="A" Rating="1,25"/>
      </Region>
   </Category>
</Accounts>

T:\ftemp>type mxps2.xsl 
<?xml version="1.0" encoding="US-ASCII"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                version="2.0">

<xsl:output indent="yes"/>

<xsl:template match="Accounts">
  <Accounts>
    <xsl:for-each-group select="Account" group-by="@Category">
      <xsl:sort select="@Category"/>
        <Category name="{@Category}">
          <xsl:for-each-group select="current-group()" group-by="@Region">
            <xsl:sort select="@Region"/>
              <Region name="{@Region}">
                <xsl:for-each select="current-group()">
                  <xsl:sort select="@Name"/>
                  <Account>
                    <xsl:copy-of select="@Name,@Value,@Type,@Rating"/>
                  </Account>
                </xsl:for-each>
              </Region>
          </xsl:for-each-group>
        </Category>
    </xsl:for-each-group>
  </Accounts>
</xsl:template>

</xsl:stylesheet>
T:\ftemp>rem Done!