Search code examples
xmllistxsltxslt-1.0xsl-fo

creating Multi-level lists in XSL FO


im attempting to create a multilevel list output. im using an XSLT in XML 1.0 running through apache FOP

im using an XML input like below where there are many different procedures in the procedureList which have many steps. substeps are embedded in steps.

<ProcedureList>
<Procedure title="The First Procedure" type="Removal">
<Steps> 
   <Step id="1">Step 1 </Step>
   <Step id="2">Step 2 </Step>
   <Step id="3">Step 3 <Step id="4">Step 4 <Step id="5">Step 5 </Step></Step></Step>
</Steps>
</Procedure>
<Procedure title="The Second Procedure" type="removal">
<Steps> 
   <Step id="6">Step 6 </Step>
   <Step id="7">Step 7 <Step id="8">Step 8</Step></Step>
</Steps>
</Procedure>
</ProcedureList>

the output im after is

1. The First Procedure
1.1 Step 1 
1.2 Step 2 
1.3 Step 3 
1.3.a Step 4 
1.3.a.i Step 5

2. The Second Procedure
2.1 Step 6 
2.2 Step 7 
2.2.a Step 8

using the below I get close to the right answer (position is passed as a leading number to the template depending where in the document the procedure occurs.

  <xsl:template match="Step">
    <xsl:param name="position" />
    <fo:block >
      <xsl:apply-templates select="WCN" />
      <xsl:if test="normalize-space(text()) != ''">
        
        <fo:block text-indent='-15mm' start-indent='15mm' xsl:use-attribute-sets="BodyText" wrap-option="wrap" white-space="pre">
          <xsl:if test="$position != ''">
            <xsl:value-of select="$position"/><xsl:text>.</xsl:text>
          </xsl:if>
          <xsl:if test="$position = ''">
            <xsl:text>         </xsl:text>
          </xsl:if>
          <xsl:number format="1.1.a.i.a.i" count="Step" level="multiple"/><xsl:text>      </xsl:text><xsl:apply-templates select="text()"/></fo:block>
      </xsl:if>
      
      <xsl:apply-templates select="Step" />
      
    </fo:block>
  </xsl:template>

the problem im having is when I need two procedures of the same type in one area of the document. i call the template like

<xsl:choose>
  <xsl:when test="root/ProcedureList/Procedure[@type='inspection' or @type='repair'] != ''">
    <xsl:for-each select="root/ProcedureList/Procedure[@type='inspection' or @type='repair']">
      <xsl:apply-templates >
        <xsl:with-param name="position" select="8" />
      </xsl:apply-templates>
    </xsl:for-each>
  </xsl:when>
  <xsl:otherwise>
    <fo:block xsl:use-attribute-sets="BodyText">Not Applicable</fo:block>
  </xsl:otherwise>
</xsl:choose>

i get an output like

1. The First Procedure
1.1 Step 1 
1.2 Step 2 
1.3 Step 3 
1.3.a Step 4 
1.3.a.i Step 5

1. The Second Procedure
1.1 Step 6 
1.2 Step 7 
1.2.a Step 8

Solution

  • It's hard to comment directly on your code because I'm not seeing all of it and the code you've provided doesn't work for your example XML. However, solving the basic problem is fairly easy. You will need to add filters and formatting. And work it into your program. You can add a mode to the templates and apply templates, if needed.

    <xsl:template match="ProcedureList">
      <xsl:value-of select="'&#13;'"/>
      <xsl:apply-templates select="Procedure"/>
    </xsl:template>
    
    <xsl:template match="Procedure">
      <xsl:value-of select="concat(position(), '. ', @title)"/>
      <xsl:value-of select="'&#13;'"/>
      <xsl:apply-templates select="Steps/Step">
        <xsl:with-param name="procedure" select="concat(position(), '.')"/>
      </xsl:apply-templates>
    </xsl:template>
    
    <xsl:template match="Step">
      <xsl:param name="procedure" />
      <xsl:value-of select="$procedure"/>
      <xsl:number format="1.a.i.a.i" count="Step" level="multiple"/>
      <xsl:value-of select="concat(' ', normalize-space(text()))"/>
      <xsl:value-of select="'&#13;'"/>
    
      <xsl:apply-templates select="Step">
        <xsl:with-param name="procedure" select="$procedure"/>
      </xsl:apply-templates>
    </xsl:template>