1

There seem to be quite a few similar questions, but none of them helped me. I'd like to refactor the following piece of XSLT:

<fo:table-cell xsl:use-attribute-sets="table-item-bordered">
    <fo:block>
        <xsl:value-of select="/forms//instance[@versionNumber = '1']/content//textbox[@id = $id]/text()" />
    </fo:block>
</fo:table-cell>
<fo:table-cell xsl:use-attribute-sets="table-item-bordered">
    <fo:block>
        <xsl:value-of select="/forms//instance[@versionNumber = '2']/content//textbox[@id = $id]/text()" />
    </fo:block>
</fo:table-cell>
<fo:table-cell xsl:use-attribute-sets="table-item-bordered">
    <fo:block>
        <xsl:value-of select="/forms//instance[@versionNumber = '3']/content//textbox[@id = $id]/text()" />
    </fo:block>
</fo:table-cell>

Note the changing versionNumber attribute.

The xml source looks something like this:

<forms>
  <instance versionNumber="3">
    <content>
      <textbox id="1">THREE</textbox>
    </content>
    <previousVersions>
      <instance versionNumber="1">
        <content>
          <textbox id="1">ONE</textbox>
        </content>
      </instance>
      <instance versionNumber="2">
        <content>
          <textbox id="1">TWO</textbox>
        </content>
      </instance>
    </previousVersions>
  </instance>
</forms>

The output should be a table row formatted like this:

+-----+-----+-------+
| ONE | TWO | THREE |
+-----+-----+-------+

In code this will look something like this:

<table-cell>ONE</table-cell>
<table-cell>TWO</table-cell>
<table-cell>THREE</table-cell>

This would be easy enough without using a loop counter, however I always want need 3 cells, regardless of how many versions there are. If there is no instance with versionNumber=3, the table needs to look like this:

+-----+-----+-----+
| ONE | TWO |     |
+-----+-----+-----+

Or in XML:

<table-cell>ONE</table-cell>
<table-cell>TWO</table-cell>
<table-cell></table-cell>
4
  • You don't need loop counters in XSLT. What is $instanceId and $id? Also show the relevant part of your input and the output you want to create. Maybe these variables are not necessary. Commented Jul 20, 2015 at 7:24
  • I've updated the question. I would prefer not to use loop-counters, as xslt is a functional language which does away with loops. In a functional language I might call 'map' on the set [1, 2, 3] and go from there, is there an XSLT equivalent of doing that? Commented Jul 21, 2015 at 0:09
  • Please post your expected output as code. Commented Jul 21, 2015 at 1:51
  • @michael.hor257k I've posted code of the same schema of what I expect to be created. Does that help? Commented Jul 21, 2015 at 3:23

4 Answers 4

2

Without loop but with additional template:

<xsl:template match="instance">
  <xsl:param name="id" />

  <fo:table-cell xsl:use-attribute-sets="table-item-bordered">
    <fo:block>
      <xsl:value-of select="content//textbox[@id = $id]/text()" />
    </fo:block>
  </fo:table-cell>
</xsl:template>

<!-- in another template... -->
<xsl:apply-templates select="/forms//instance[@id = $instanceId and (@versionNumber &gt;= 1 and @versionNumber &lt;= 3)]">
  <xsl:with-param name="id" select="$id"/>
  <xsl:sort select="@versionNumber" data-type="number"/>
</xsl:apply-templates>
Sign up to request clarification or add additional context in comments.

3 Comments

Oops, missed this table-cell. Thanks, Tomalak.
No problem. I was writing up the same thing when I saw your answer popping up. :)
Thanks for the answer Rudolf. Sorry I wasn't clear in the question originally but I need to have exactly 3 table cells regardless of how many instance nodes there are, even if there are only 1 or 2.
2

I always want need 3 cells, regardless of how many versions there are.

With only three cells, you could simply hard-code them as yo have done. If you don't want to do that, then you will have to use a loop, for example:

XSLT 1.0

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

<xsl:key name="instance" match="instance" use="@versionNumber" />

<xsl:template match="/forms">
    <root>
        <xsl:call-template name="generate-cells">
            <xsl:with-param name="n" select="3" />
        </xsl:call-template>
    </root>
</xsl:template>

<xsl:template name="generate-cells">
    <xsl:param name="n"/>
    <xsl:if test="$n">
        <!-- recursive call -->
        <xsl:call-template name="generate-cells">
            <xsl:with-param name="n" select="$n - 1" />
        </xsl:call-template>
        <!-- current cell -->
        <table-cell>
            <xsl:value-of select="key('instance', $n)/content/textbox" />
        </table-cell>
    </xsl:if>   
</xsl:template>

</xsl:stylesheet>

In a functional language I might call 'map' on the set [1, 2, 3] and go from there, is there an XSLT equivalent of doing that?

In XSLT 2.0, you can do:

<xsl:template match="/forms">
    <xsl:variable name="forms" select="." />
    <root>
        <xsl:for-each select="('1','2','3')">
            <table-cell>
                <xsl:value-of select="key('instance', ., $forms)/content/textbox" />
            </table-cell>
        </xsl:for-each>
    </root>
</xsl:template>

Or even:

    <xsl:for-each select="1 to 3">
        <table-cell>
            <xsl:value-of select="key('instance', string(.), $forms)/content/textbox" />
        </table-cell>
    </xsl:for-each>

Comments

1

From the comments:

I need to have exactly 3 table cells regardless of how many instance nodes there are, even if there are only 1 or 2.

Then you could use a little trick and loop over three unrelated nodes and abuse position():

<xsl:for-each select="//*[position() &lt;= 3]">
  <xsl:variable name="i" select="position()" />

  <fo:table-cell xsl:use-attribute-sets="table-item-bordered">
    <fo:block>
      <xsl:value-of select="content/forms//instance[@versionNumber = $i]//textbox[@id = $id]/text()" />
    </fo:block>
  </fo:table-cell>
</xsl:for-each>

or you could use recursion, which is a little more idiomatic for XSLT. It works for arbitrary amounts of repetition but it's also longer:

<xsl:template name="output-table-cell">
  <xsl:param name="i" />
  <xsl:param name="max" />

  <xsl:if test="$i &lt;= $max">
    <fo:table-cell xsl:use-attribute-sets="table-item-bordered">
      <fo:block>
        <xsl:value-of select="content/forms//instance[@versionNumber = $i]//textbox[@id = $id]/text()" />
      </fo:block>
    </fo:table-cell>

    <!-- recursive step -->
    <xsl:call-template name="output-table-cell">
      <xsl:with-param name="i" select="$i + 1" />
      <xsl:with-param name="max" select="$max" />
    </xsl:call-template>
  </xsl:if>
</xsl:template>

Note that I assume $id to be defined elsewhere in both snippets. Adapt as needed.

Comments

1

Inspired by @michael.hor257k, I ended up not looping, but creating a named template and calling that 3 times. This was satisfying enough to reduce code duplication and make it read cleaner:

<xsl:call-template name="table-cell">
    <xsl:with-param name="node" select="/forms//instance[@versionNumber = '1']/content//textbox[@id = $id]" />
</xsl:call-template>
<xsl:call-template name="table-cell">
    <xsl:with-param name="node" select="/forms//instance[@versionNumber = '2']/content//textbox[@id = $id]" />
</xsl:call-template>
<xsl:call-template name="table-cell">
    <xsl:with-param name="node" select="/forms//instance[@versionNumber = '3']/content//textbox[@id = $id]" />
</xsl:call-template>

<xsl:template name="tableCell">
    <xsl:param name="node" />
    <fo:table-cell xsl:use-attribute-sets="table-item-bordered">
        <fo:block>
            <xsl:value-of select="$node/text()" />
        </fo:block>
    </fo:table-cell>
</xsl:template>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.