C D
C D

Reputation: 57

How to improve XSLT Efficiency

I have a XML-File like this:

I think you could understand my XML as a table of Content.
AElement @Name='1' is entry 1
The first AElement @Name='2' would be entry 1.1
The AElement @Name='3' below this AElement @Name='2' would directly belong to it making them entry 1.1.1, entry 1.1.2 and entry 1.1.3 and entry 1.1.4
The second AElement @Name='2' would be entry 1.2
and so on..

<ROOT>
    <AElement Name="1">
        <AElement>
          <Child1></Child1>
          <Child2>0000</Child2>
        </AElement>
    </AElement> 
    <AElement Name="2">
        <AElement>
          <Child1>E</Child1>
          <Child2>1000</Child2>
        </AElement>
    </AElement> 
    <AElement Name="3">
        <AElement>
          <Child1>E</Child1>
          <Child2>0100</Child2>
        </AElement>
    </AElement> 
    <AElement Name="3">
        <AElement>
          <Child1>U</Child1>
          <Child2>0200</Child2>
        </AElement>
    </AElement> 
    <AElement Name="3">
        <AElement>
          <Child1>E</Child1>
          <Child2>0300</Child2>
        </AElement>
    </AElement> 
    <AElement Name="2">
        <AElement>
          <Child1>E</Child1>
          <Child2>2000</Child2>
        </AElement>
    </AElement> 
    <AElement Name="3">
        <AElement>
          <Child1>N</Child1>
          <Child2>0400</Child2>
        </AElement>
    </AElement> 
    <AElement Name="3">
        <AElement>
          <Child1>E</Child1>
          <Child2>0500</Child2>
        </AElement>
    </AElement>   
    <AElement Name="2">
        <AElement>
          <Child1>E</Child1>
          <Child2>3000</Child2>
        </AElement>
    </AElement>
    <AElement Name="2">
        <AElement>
          <Child1>U</Child1>
          <Child2>4000</Child2>
        </AElement>
    </AElement> 
    <AElement Name="3">
        <AElement>
          <Child1>E</Child1>
          <Child2>0600</Child2>
        </AElement>
    </AElement> 
    <AElement Name="4">
        <AElement>
          <Child1>E</Child1>
          <Child2>0010</Child2>
        </AElement>
    </AElement>
</ROOT>

Wanted result:

For each AElement a table should be created
where for each AElement of the following step
if AElement/Child1='E' of that AElement
a table-row and table-cell should be created
this table-cell shall than be filled with the value of AElement/Child2 of that AElement.

However this table should only be created IF there are any AElement of the following step with AElement/Child1='E'.

EXAMPLE

For each AElement @Name='2' a table should be created where for each AElement @Name='3' that belongs to AElement @Name='2'
if AElement/Child1='E' of AElement @Name='3' a table-row and table-cell should be created this table-cell shall than be filled with the value of AElement/Child2 of that AElement @Name='3'.

So this:

<AElement Name="2">
    <AElement>
        <Child1>U</Child1>
        <Child2>4000</Child2>
    </AElement>
</AElement> 
<AElement Name="3">
    <AElement>
        <Child1>E</Child1>
        <Child2>0600</Child2>
    </AElement>
</AElement> 
<AElement Name="4">
    <AElement>
        <Child1>E</Child1>
        <Child2>0010</Child2>
    </AElement>
</AElement>

becomes this:

<fo:block>STEP 2  4000</fo:block>

<fo:table>
    <fo:table-row>
        <fo:table-cell>
            <fo:block>
                0600
            </fo:block>
        </fo:table-cell>
    </fo:table-row>
</fo:table>

BUT this:

<AElement Name="2">
    <AElement>
        <Child1>E</Child1>
        <Child2>3000</Child2>
    </AElement>
</AElement>
<AElement Name="2">
    <AElement>
        <Child1>U</Child1>
        <Child2>4000</Child2>
    </AElement>
</AElement>

would NOT create a table because STEP 3000 has no following AElements @Name='3'.


<fo:root>

    <fo:block>STEP 1  0000</fo:block>

    <fo:table>
        <fo:table-row>
            <fo:table-cell>
                <fo:block>
                    1000
                </fo:block>
            </fo:table-cell>
        </fo:table-row>
        <fo:table-row>
            <fo:table-cell>
                <fo:block>
                    2000
                </fo:block>
            </fo:table-cell>
        </fo:table-row>
        <fo:table-row>
            <fo:table-cell>
                <fo:block>
                    3000
                </fo:block>
            </fo:table-cell>
        </fo:table-row>
    </fo:table>



    <fo:block>STEP 2  1000</fo:block>

    <fo:table>
        <fo:table-row>
            <fo:table-cell>
                <fo:block>
                    0100
                </fo:block>
            </fo:table-cell>
        </fo:table-row>
        <fo:table-row>
            <fo:table-cell>
                <fo:block>
                    0300
                </fo:block>
            </fo:table-cell>
        </fo:table-row>
    </fo:table>



    <fo:block>STEP 2  2000</fo:block>

    <fo:table>
        <fo:table-row>
            <fo:table-cell>
                <fo:block>
                    0500
                </fo:block>
            </fo:table-cell>
        </fo:table-row>
    </fo:table>



    <fo:block>STEP 2  4000</fo:block>

    <fo:table>
        <fo:table-row>
            <fo:table-cell>
                <fo:block>
                    0600
                </fo:block>
            </fo:table-cell>
        </fo:table-row>
    </fo:table>


    <fo:block>STEP 3  0600</fo:block>

    <fo:table>
        <fo:table-row>
            <fo:table-cell>
                <fo:block>
                    0010
                </fo:block>
            </fo:table-cell>
        </fo:table-row>
    </fo:table>

</fo:root>

<xsl:transform version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:fo="http://www.w3.org/1999/XSL/Format" 
>
    <xsl:output indent="yes" />



    <!-- all <AElement> we consider for output, indexed by their @Name and group ID -->
    <xsl:key name="AElementGroup1" match="AElement[AElement/Child1 = 'E']" use="
        concat(@Name, '|', generate-id(preceding-sibling::AElement[@Name = '1'][1]))
    " />
    <xsl:key name="AElementGroup2" match="AElement[AElement/Child1 = 'E']" use="
        concat(@Name, '|', generate-id(preceding-sibling::AElement[@Name = '2'][1]))
    " />
    <xsl:key name="AElementGroup3" match="AElement[AElement/Child1 = 'E']" use="
        concat(@Name, '|', generate-id(preceding-sibling::AElement[@Name = '3'][1]))
    " />
    <xsl:key name="AElementGroup4" match="AElement[AElement/Child1 = 'E']" use="
        concat(@Name, '|', generate-id(preceding-sibling::AElement[@Name = '4'][1]))
    " />
    <xsl:key name="AElementGroup5" match="AElement[AElement/Child1 = 'E']" use="
        concat(@Name, '|', generate-id(preceding-sibling::AElement[@Name = '5'][1]))
    " />
    <xsl:key name="AElementGroup6" match="AElement[AElement/Child1 = 'E']" use="
        concat(@Name, '|', generate-id(preceding-sibling::AElement[@Name = '6'][1]))
    " />
    <xsl:key name="AElementGroup7" match="AElement[AElement/Child1 = 'E']" use="
        concat(@Name, '|', generate-id(preceding-sibling::AElement[@Name = '7'][1]))
    " />

    <xsl:template match="/">
        <fo:root>
        <fo:layout-master-set>
        <fo:simple-page-master
        master-name="DIN-A4-Landscape"
        page-height="210mm"
        page-width="297mm"
        margin-left="1mm"
        margin-right="1mm"
        margin-top="1mm"
        margin-bottom="1mm"
            >
          <fo:region-body margin="1mm"/>
          <fo:region-before region-name="header" extent="5mm"/>
          <fo:region-after region-name="footer" extent="15mm"/>
          <fo:region-start  region-name="left"   extent="5mm"/>
          <fo:region-end    region-name="right"  extent="5mm"/>
        </fo:simple-page-master>
      </fo:layout-master-set>
      <fo:page-sequence master-reference="DIN-A4-Landscape">
        <fo:flow flow-name="xsl-region-body">                      
            <xsl:call-template name="ink">
                <xsl:with-param name="Now" select="1"/>
            </xsl:call-template>
        </fo:flow>
      </fo:page-sequence>

        </fo:root>
    </xsl:template>

    <xsl:template name="ink">

    <xsl:param name="Now"/>
    <xsl:param name="Next" select="$Now + 1"/>
    <xsl:param name="Ende" select="3"/>

    <xsl:apply-templates select="ROOT">
      <xsl:with-param name="nowStep" select="$Now"/>
      <xsl:with-param name="nextStep" select="$Next"/>
    </xsl:call-template>

    <xsl:if test="$Ende > $Now">

      <xsl:call-template name="ink">
        <xsl:with-param name="Now" select="$Now + 1"/>     
    </xsl:call-template>
    </xsl:if>

  </xsl:template>

    <xsl:template match="ROOT">
        <xsl:param name="nowStep"/>
        <xsl:param name="nextStep"/>

        <xsl:for-each select="AElement[@Name = $nowStep]">
            <xsl:variable name="groupId" select="generate-id()" />            
                <xsl:variable name="groupKey" select="concat($nextStep, '|', $groupId)" />
                <fo:block><xsl:value-of select="concat('STEP ' , $nowStep)" /></fo:block>

<xsl:variable name="group">
    <xsl:value-of select="concat('InternalElementGroup',$nowStep)"/>
</xsl:variable>

<xsl:if test="count(key($group, $groupKey))>=1">

                <fo:table page-break-after="always">
                    <fo:table-body>
                    <xsl:choose>
                    <xsl:when test="$nowStep = 1">
                        <xsl:apply-templates select="key('AElementGroup1', $groupKey)/AElement" />
                    </xsl:when>
                    <xsl:when test="$nowStep = 2">
                        <xsl:apply-templates select="key('AElementGroup2', $groupKey)/AElement" />
                    </xsl:when>
                    <xsl:when test="$nowStep = 3">
                        <xsl:apply-templates select="key('AElementGroup3', $groupKey)/AElement" />
                    </xsl:when>
                    </xsl:choose>


                    </fo:table-body>
                </fo:table>
</xsl:if>            
        </xsl:for-each>
    </xsl:template>

    <xsl:template match="AElement/AElement">
        <fo:table-row>
            <fo:table-cell>
                <fo:block><xsl:value-of select="Child2"/></fo:block>
            </fo:table-cell>
        </fo:table-row>
    </xsl:template>

</xsl:transform>

This code by @Tomalak returns the wanted result.

Upvotes: 1

Views: 121

Answers (1)

Tomalak
Tomalak

Reputation: 338396

It's not 100% clear what your intended output is since you only give a single example and no further context.

I am making the following guesses:

  1. All elements are pre-ordered properly in the input.
  2. You have multiple <AElement Name="1">, each of those starts a new "section".
  3. In each "section" you want multiple <fo:block>STEP N</fo:block>, where N is the @Name of each <AElement>.
  4. Each of those groups should contain the elements that belong to the next ordinal position (i.e. STEP 1 should contain the elements that have @Name = '2', and so on). This makes a total of 8 groups (N <= 8).
  5. In each of those groups you want an <fo:table> with M rows where M is the number of <AElement> items that have the right @Name and contain a <Child1>E</Child1>.
  6. All of the above needs to be XSLT 1.0

So the general order of processing would be

  • for each AElement[@Name = '1']
    • for N = 1 .. 8
      • output <fo:block>STEP N</fo:block>
      • output <fo:table> with M rows for group (N+1) </fo:table>

I do not have a realistic 5000-element input XML to test with, but here is how I would solve this in XSLT 1.0.

<xsl:transform version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:fo="http://www.w3.org/1999/XSL/Format" 
>
    <xsl:output indent="yes" />

    <!-- all <AElement> we consider for output, indexed by their @Name and group ID -->
    <xsl:key name="AElementGroup" match="AElement[AElement/Child1 = 'E']" use="
        concat(@Name, '|', generate-id(preceding-sibling::AElement[@Name = '1'][1]))
    " />

    <!-- some random nodes, so we have something to count with -->
    <xsl:variable name="counter" select="(//node())[position() &lt; 9]" />

    <xsl:template match="/">
        <fo:root>
            <xsl:apply-templates select="ROOT" />
        </fo:root>
    </xsl:template>

    <xsl:template match="ROOT">
        <xsl:for-each select="AElement[@Name = '1']">
            <xsl:variable name="groupId" select="generate-id()" />
            <xsl:for-each select="$counter">
                <xsl:variable name="groupKey" select="concat(position() + 1, '|', $groupId)" />
                <fo:block><xsl:value-of select="concat('STEP ' , position())" /></fo:block>
                <fo:table>
                    <fo:table-body>
                        <xsl:apply-templates select="key('AElementGroup', $groupKey)/AElement" />
                    </fo:table-body>
                </fo:table>
            </xsl:for-each>
        </xsl:for-each>
    </xsl:template>

    <xsl:template match="AElement/AElement">
        <fo:table-row>
            <fo:table-cell>
                <fo:block><xsl:value-of select="Child2"/></fo:block>
            </fo:table-cell>
        </fo:table-row>
    </xsl:template>

</xsl:transform>

Before I go into further detail about the suggested solution, please give some feedback on whether this captures your task in the first place.

Upvotes: 1

Related Questions