4
\$\begingroup\$

I have been able to create an XSLT (included) to convert a source XML to a target desired XML format (included). However, I am sure this can be optimized leveraging some built in XSLT capabilities which are beyond my proficiency.

The core requirement is that Input XML contains header values and data (the first record contains the header/labels and subsequent records contain data for those respective label, hence output data is positionally matched with the first record), OutPut XML contains Name Value Pairs, the name describes the data element (header/label from 1st record in the source) and the elements are "labelXX","valueXX", hence the need for counters to increment XX. The output also contains a header record which essentially just contains the name of the element, rest is self explanatory from the output.

Source XML

<?xml version="1.0" encoding="UTF-8"?>
<SpecExportFF>
 <recordset>
<value>12345</value>
<value>xyz</value>
    <value>a0</value>
    <value>b0</value>
    <value>c0</value>
    <value>d0</value>
    <value>e0</value>
    <value>f0</value>
    <value>g0</value>
    <value>h0</value>
    <value>i0</value>
    <value>j0</value>
    <value>k0</value>
    <value>l0</value>
    <value>m0</value>
    <value>n0</value>
    <value>o0</value>
    <value>p0</value>
    <value>q0</value>
    <value>r0</value>
    <value>s0</value>
    <value>t0</value>
    <value>a1</value>
    <value>b1</value>
    <value>c1</value>
    <value>d1</value>
    <value>e1</value>
    <value>f1</value>
    <value>g1</value>
    <value>h1</value>
    <value>i1</value>
    <value>j1</value>
    <value>k1</value>
    <value>l1</value>
    <value>m1</value>
    <value>n1</value>
    <value>o1</value>
    <value>p1</value>
    <value>q1</value>
    <value>r1</value>
    <value>s1</value>
    <value>t1</value>
    <value>a2</value>
    <value>b2</value>
    <value>c2</value>
    <value>d2</value>
    <value>e2</value>
    <value>f2</value>
    <value>g2</value>
    <value>h2</value>
    <value>i2</value>
    <value>j2</value>
    <value>k2</value>
    <value>l2</value>
    <value>m2</value>
    <value>n2</value>
    <value>o2</value>
    <value>p2</value>
    <value>q2</value>
    <value>r2</value>
    <value>s2</value>
    <value>t2</value>
 </recordset>
 <recordset>
    <value>A12345</value>
    <value>snacks</value>
    <value>1</value>
    <value>cereals</value>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
 </recordset>
 <recordset>
    <value>A245678</value>
    <value>snacks</value>
    <value>1</value>
    <value>chips</value>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value>potato</value>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value>fried</value>
    <value/>
    <value></value>
    <value/>
    <value>baked</value>
    <value></value>
    <value></value>
    <value></value>
    <value/>
    <value></value>
    <value/>
    <value>20g</value>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value/>
    <value></value>
 </recordset>
</SpecExportFF>

Output Target XML

<?xml version="1.0" encoding="UTF-8"?>
<SpecExportOutputFF>
<header>
   <item>item</item>
   <category>category</category>
   <label01>label01</label01>
   <spec01>spec01</spec01>
   <label02>label02</label02>
   <spec02>spec02</spec02>
   <label03>label03</label03>
   <spec03>spec03</spec03>
   <label04>label04</label04>
   <spec04>spec04</spec04>
   <label05>label05</label05>
   <spec05>spec05</spec05>
   <label06>label06</label06>
   <spec06>spec06</spec06>
   <label07>label07</label07>
   <spec07>spec07</spec07>
   <label08>label08</label08>
   <spec08>spec08</spec08>
   <label09>label09</label09>
   <spec09>spec09</spec09>
   <label10>label10</label10>
   <spec10>spec10</spec10>
   <label11>label11</label11>
   <spec11>spec11</spec11>
   <label12>label12</label12>
   <spec12>spec12</spec12>
   <label13>label13</label13>
   <spec13>spec13</spec13>
   <label14>label14</label14>
   <spec14>spec14</spec14>
   <label15>label15</label15>
   <spec15>spec15</spec15>
   <label16>label16</label16>
   <spec16>spec16</spec16>
   <label17>label17</label17>
   <spec17>spec17</spec17>
   <label18>label18</label18>
   <spec18>spec18</spec18>
   <label19>label19</label19>
   <spec19>spec19</spec19>
   <label20>label20</label20>
   <spec20>spec20</spec20>
   <label21>label21</label21>
   <spec21>spec21</spec21>
   <label22>label22</label22>
   <spec22>spec22</spec22>
   <label23>label23</label23>
   <spec23>spec23</spec23>
   <label24>label24</label24>
   <spec24>spec24</spec24>
   <label25>label25</label25>
   <spec25>spec25</spec25>
   <label26>label26</label26>
   <spec26>spec26</spec26>
   <label27>label27</label27>
   <spec27>spec27</spec27>
   <label28>label28</label28>
   <spec28>spec28</spec28>
   <label29>label29</label29>
   <spec29>spec29</spec29>
   <label30>label30</label30>
   <spec30>spec30</spec30>
   <label31>label31</label31>
   <spec31>spec31</spec31>
   <label32>label32</label32>
   <spec32>spec32</spec32>
   <label33>label33</label33>
   <spec33>spec33</spec33>
   <label34>label34</label34>
   <spec34>spec34</spec34>
   <label35>label35</label35>
   <spec35>spec35</spec35>
   <label36>label36</label36>
   <spec36>spec36</spec36>
   <label37>label37</label37>
   <spec37>spec37</spec37>
   <label38>label38</label38>
   <spec38>spec38</spec38>
   <label39>label39</label39>
   <spec39>spec39</spec39>
   <label40>label40</label40>
   <spec40>spec40</spec40>
   <label41>label41</label41>
   <spec41>spec41</spec41>
   <label42>label42</label42>
   <spec42>spec42</spec42>
   <label43>label43</label43>
   <spec43>spec43</spec43>
   <label44>label44</label44>
   <spec44>spec44</spec44>
   <label45>label45</label45>
   <spec45>spec45</spec45>
   <label46>label46</label46>
   <spec46>spec46</spec46>
   <label47>label47</label47>
   <spec47>spec47</spec47>
   <label48>label48</label48>
   <spec48>spec48</spec48>
   <label49>label49</label49>
   <spec49>spec49</spec49>
   <label50>label50</label50>
   <spec50>spec50</spec50>
   <label51>label51</label51>
   <spec51>spec51</spec51>
   <label52>label52</label52>
   <spec52>spec52</spec52>
   <label53>label53</label53>
   <spec53>spec53</spec53>
   <label54>label54</label54>
   <spec54>spec54</spec54>
   <label55>label55</label55>
   <spec55>spec55</spec55>
   <label56>label56</label56>
   <spec56>spec56</spec56>
   <label57>label57</label57>
   <spec57>spec57</spec57>
   <label58>label58</label58>
   <spec58>spec58</spec58>
   <label59>label59</label59>
   <spec59>spec59</spec59>
   <label60>label60</label60>
   <spec60>spec60</spec60>
</header>
<data>
   <item>A12345</item>
   <category>snacks</category>
   <label01>a0</label01>
   <spec01>1</spec01>
   <label02>b0</label02>
   <spec02>cereals</spec02>
</data>
<data>
   <item>A245678</item>
   <category>snacks</category>
   <label01>a0</label01>
   <spec01>1</spec01>
   <label02>b0</label02>
   <spec02>chips</spec02>
   <label03>q0</label03>
   <spec03>potato</spec03>
   <label04>e1</label04>
   <spec04>fried</spec04>
   <label05>i1</label05>
   <spec05>baked</spec05>
   <label06>p1</label06>
   <spec06>20g</spec06>
</data>
</SpecExportOutputFF>

My XSLT (It works but is too complicated in my opinion and would like to optimize it)

<?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:template match="/SpecExportFF">
        <SpecExportOutputFF>
            <xsl:for-each select="recordset">
                <xsl:variable name="IndexOfCurrentRecordset">
                    <xsl:value-of select="position()"/>
                </xsl:variable>
                <xsl:if test="$IndexOfCurrentRecordset = 1">
                    <header>
                        <xsl:for-each select="value">
                            <xsl:variable name="IndexOfCurrentNode" select="position()"/>
                            <xsl:variable name="ElementSuffix" select="format-number(($IndexOfCurrentNode - 2),'00')"/>
                            <!--<hello1><xsl:value-of select="$IndexOfCurrentNode"/></hello1>-->
                            <xsl:choose>
                                <xsl:when test="$IndexOfCurrentNode = 1">
                                    <xsl:element name="item">item</xsl:element>
                                </xsl:when>
                                <xsl:when test="$IndexOfCurrentNode = 2">
                                    <xsl:element name="category">category</xsl:element>
                                </xsl:when>
                                <xsl:when test="($IndexOfCurrentNode > 2)">
                                    <xsl:element name="{concat('label',$ElementSuffix)}">
                                        <xsl:value-of select="concat('label',$ElementSuffix)"/>
                                    </xsl:element>
                                    <xsl:element name="{concat('spec',$ElementSuffix)}">
                                        <xsl:value-of select="concat('spec',$ElementSuffix)"/>
                                    </xsl:element>
                                </xsl:when>
                                <xsl:otherwise>
                                </xsl:otherwise>
                            </xsl:choose>
                        </xsl:for-each>
                    </header>
                </xsl:if>
                <xsl:if test="$IndexOfCurrentRecordset > 1">
                    <data>
                        <xsl:call-template name="iterate_values">
                            <xsl:with-param name="i" select="1"/>
                            <xsl:with-param name="currentNode" select="./value[1]"/>
                            <xsl:with-param name="IndexOfCurrentRecordset" select="$IndexOfCurrentRecordset"/>
                            <xsl:with-param name="IndexOfCurrentNode" select="1"/>
                        </xsl:call-template>
                    </data>
                </xsl:if>
            </xsl:for-each>
        </SpecExportOutputFF>
    </xsl:template>

    <xsl:template name="iterate_values">
        <xsl:param name="i"/>
        <xsl:param name="currentNode"/>
        <xsl:param name="IndexOfCurrentRecordset"/>
        <xsl:param name="IndexOfCurrentNode"/>

        <xsl:variable name="ElementSuffix" select="format-number($i,'00')"/>

        <xsl:choose>
            <xsl:when test="$IndexOfCurrentNode = 1">
                <xsl:element name="item">
                    <xsl:value-of select="$currentNode/text()"/>
                </xsl:element>
                <xsl:if test="/SpecExportFF/recordset[position() = $IndexOfCurrentRecordset]/value[position() = $IndexOfCurrentNode+1]">
                    <xsl:call-template name="iterate_values">
                        <xsl:with-param name="i" select="$i"/>
                        <xsl:with-param name="currentNode" select="/SpecExportFF/recordset[position() = $IndexOfCurrentRecordset]/value[position() = $IndexOfCurrentNode+1]"/>
                        <xsl:with-param name="IndexOfCurrentRecordset" select="$IndexOfCurrentRecordset"/>
                            <xsl:with-param name="IndexOfCurrentNode" select="$IndexOfCurrentNode+1"/>
                    </xsl:call-template>
                </xsl:if>
            </xsl:when>
            <xsl:when test="$IndexOfCurrentNode = 2">
                <xsl:element name="category">
                    <xsl:value-of select="$currentNode/text()"/>
                </xsl:element>
                <xsl:if test="/SpecExportFF/recordset[position() = $IndexOfCurrentRecordset]/value[position() = $IndexOfCurrentNode+1]">
                    <xsl:call-template name="iterate_values">
                        <xsl:with-param name="i" select="$i"/>
                        <xsl:with-param name="currentNode" select="/SpecExportFF/recordset[position() = $IndexOfCurrentRecordset]/value[position() = $IndexOfCurrentNode+1]"/>
                        <xsl:with-param name="IndexOfCurrentRecordset" select="$IndexOfCurrentRecordset"/>
                            <xsl:with-param name="IndexOfCurrentNode" select="$IndexOfCurrentNode+1"/>
                    </xsl:call-template>
                </xsl:if>
            </xsl:when>
            <xsl:when test="$IndexOfCurrentNode > 2 and $currentNode/text() != ''">
                <xsl:element name="{concat('label',$ElementSuffix)}">
                    <xsl:value-of select="/*/*[1]/value[$IndexOfCurrentNode]"/>
                </xsl:element>
                <xsl:element name="{concat('spec',$ElementSuffix)}">
                    <xsl:value-of select="$currentNode/text()"/>
                </xsl:element>
                <xsl:if test="/SpecExportFF/recordset[position() = $IndexOfCurrentRecordset]/value[position() = $IndexOfCurrentNode+1]">
                    <xsl:call-template name="iterate_values">
                        <xsl:with-param name="i" select="$i+1"/>
                        <xsl:with-param name="currentNode" select="/SpecExportFF/recordset[position() = $IndexOfCurrentRecordset]/value[position() = $IndexOfCurrentNode+1]"/>
                        <xsl:with-param name="IndexOfCurrentRecordset" select="$IndexOfCurrentRecordset"/>
                            <xsl:with-param name="IndexOfCurrentNode" select="$IndexOfCurrentNode+1"/>
                    </xsl:call-template>
                </xsl:if>
            </xsl:when>
            <xsl:otherwise>
                <xsl:if test="/SpecExportFF/recordset[position() = $IndexOfCurrentRecordset]/value[position() = $IndexOfCurrentNode+1]">
                    <xsl:call-template name="iterate_values">
                        <xsl:with-param name="i" select="$i"/>
                        <xsl:with-param name="currentNode" select="/SpecExportFF/recordset[position() = $IndexOfCurrentRecordset]/value[position() = $IndexOfCurrentNode+1]"/>
                        <xsl:with-param name="IndexOfCurrentRecordset" select="$IndexOfCurrentRecordset"/>
                            <xsl:with-param name="IndexOfCurrentNode" select="$IndexOfCurrentNode+1"/>
                    </xsl:call-template>
                </xsl:if>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
</xsl:stylesheet>
\$\endgroup\$
1
\$\begingroup\$

I am sure this can be optimized leveraging some built in XSLT capabilities which are beyond my proficiency.

I think there are a couple of built-in XSLT capabilities that would simplify your transform.


Built-in Recursion

The first would be to use a "push" style transform instead of a "pull" style. By using xsl:apply-templates more instead of xsl:for-each and xsl:value-of you can take advantage of XSLTs built-in recursion.

By using xsl:apply-templates, especially with modes, you can make your stylesheet a lot more modular and easier to understand/maintain.

Keys

The second would be using xsl:key. Using keys will allow you to easily create cross references to data that you need to use in different parts of your stylesheet. Think of it as creating an index that helps you to efficiently lookup data.


Here's an example...

XSLT 1.0

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output indent="yes" encoding="UTF-8"/>
  <xsl:strip-space elements="*"/>

  <xsl:key name="headerLabels" 
    match="/*/recordset[1]/value[position() > 2]" 
    use="concat('label',count(preceding-sibling::*[position() > 2]) + 1)"/>
  <xsl:key name="dataEntries" 
    match="/*/recordset[position() > 1]/value[position() mod 2 != 0]" 
    use="concat(generate-id(..),'|',.)"/>

  <xsl:template match="/SpecExportFF">
    <SpecExportOutputFF>
      <xsl:apply-templates/>
    </SpecExportOutputFF>
  </xsl:template>

  <xsl:template match="recordset[1]">
    <header>
      <item>item</item>
      <category>category</category>
      <xsl:apply-templates select="value[position() > 2]" mode="header"/>
    </header>
  </xsl:template>

  <xsl:template match="recordset">
    <data>
      <item><xsl:apply-templates select="value[1]"/></item>
      <category><xsl:apply-templates select="value[2]"/></category>
      <xsl:apply-templates select="value[string()][position() > 2]" mode="data"/>
    </data>
  </xsl:template>

  <xsl:template match="value" mode="header">
    <xsl:variable name="suffix" select="format-number(position(),'00')"/>
    <xsl:element name="label{$suffix}">
      <xsl:value-of select="concat('label',$suffix)"/>
    </xsl:element>
    <xsl:element name="spec{$suffix}">
      <xsl:value-of select="concat('spec',$suffix)"/>
    </xsl:element>
  </xsl:template>

  <xsl:template match="value" mode="data">
    <xsl:variable name="pos" select="count(preceding-sibling::*) - 1"/>
    <xsl:variable name="suffix" select="format-number(position(),'00')"/>
    <xsl:element name="label{$suffix}">
      <xsl:apply-templates select="key('headerLabels',concat('label',$pos))"/>
    </xsl:element>
    <xsl:element name="spec{$suffix}">
      <xsl:apply-templates/>
    </xsl:element>
  </xsl:template>

</xsl:stylesheet>
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.