Search code examples
xmltemplatesxsltxslt-1.0generalization

Generalizing XSLT code


I'm trying to learn the different possibilities to generalize XSLT templates for the sake of reusing them at different places. So far, I have two cases where I do not know how to proceed.

Case 1 - source XML might contain nodes Foo1, Foo2, ..., Foo10 (but doesn't have to contain any or all of them). For example,

<Foo1>some value</Foo1>
<Foo3>some other value</Foo3>

I need to create nodes as follows:

<Bar number="1">some value</Bar>
<Bar number="3">some other value</Bar>

My XSLT currently is very simple:

<xsl:if test="Foo1 != ''">
  <xsl:element name="Bar">
    <xsl:attribute name="number">1</xsl:attribute>
    <xsl:value-of select="Foo1"/>
  </xsl:element>
</xsl:if>

But I obviously need 10 of these code blocks. How can I generalize this?

Case 2 - In the source XML, I have a couple of nodes of basically the same structure:

<Foo>
  <item>
    <Start>2015-06-01</Start>
    <End>9999-12-31</End>
    <Foo>00000008</Foo>
  </item> <!-- 0..n items -->
</Foo>

The nodes differ in the name Foo, but the rest stays the same. The structure I need to build looks like this:

<Bars>
  <Bar From="2015-06-01" To="9999-12-31">
    <Value>00000008</Value>
  </Bar>
</Bars>

here's my XSLT attempt, but once again, I need many templates which are very similar to each other:

<xsl:element name="Bars>
  <apply-templates select="Foo"/>
</xsl:element>

...

<xsl:template match="Foo/item">
  <xsl:element name="Bar">
    <xsl:attribute name="From">
      <xsl:call-template name="convertDate">
        <xsl:with-param name="theDate" select="Start"/>
      </xsl:call-template>
    </xsl:attribute>
    <xsl:attribute name="To">
      <xsl:call-template name="convertDate">
        <xsl:with-param name="theDate" select="End"/>
      </xsl:call-template>
    </xsl:attribute>
    <xsl:element name="Value">
      <xsl:value-of select="Foo"/>
    </xsl:element>
  </xsl:element>
</xsl:template>

And once more, I have several of the templates, all of which look very similar (i.e., they only differ in the names of the Foo, Bar, and Value elements). Any chance to generalize this, i.e. to provide a single template which can handle all these cases?


Solution

  • You can use

    <xsl:template match="*[starts-with(local-name(), 'Foo')]">
      <Bar number="{translate(local-name(), translate(local-name(), '1234567890', ''), '')}">
        <xsl:apply-templates/>
      </Bar>
    </xsl:template>
    

    for the first sample.

    To convert

      <item>
        <Start>2015-06-01</Start>
        <End>9999-12-31</End>
        <Foo>00000008</Foo>
      </item>
    

    to

      <Bar From="2015-06-01" To="9999-12-31">
        <Value>00000008</Value>
      </Bar>
    

    you can use

    <xsl:template match="item">
      <Bar From="{Start}" To="{End}">
        <xsl:value-of select="Foo"/>
      </Bar>
    </xsl:template>