3

I have several XSLT transformations. They all contain same template, which looks like this (its implementation and functionality do not matter):

<xsl:template match="firstField| secondField | thirdField">
    <xsl:element name="{local-name(.)}">    
    <xsl:choose>
        <xsl:when test="string-length(.)!=0"><xsl:value-of select="."/></xsl:when>  
        <xsl:otherwise>ABSENT</xsl:otherwise>   
    </xsl:choose>   
    </xsl:element>
</xsl:template>

As you see I have listed the fields this template to be applied in template match.

But in fact I want to use this template in all my transformations, but with different field names, of course. In other words, I want to use it like a function I can plug in to any .xsl file and specify a list of arguments, which are the names of the fields to be modified this way.

Can I do it with XSLT ?

Abel
  • 56,041
  • 24
  • 146
  • 247
MiamiBeach
  • 3,261
  • 6
  • 28
  • 54

3 Answers3

3

Update, I may have misunderstood your question and had a second look at your template (see further down for a more generic description of solution directions).

You wrote:

<xsl:template match="firstField| secondField | thirdField">
    <xsl:element name="{local-name(.)}">    
    <xsl:choose>
        <xsl:when test="string-length(.)!=0"><xsl:value-of select="."/></xsl:when>  
        <xsl:otherwise>ABSENT</xsl:otherwise>   
    </xsl:choose>   
    </xsl:element>
</xsl:template>

and:

I want to use it like a function I can plug in to any .xsl file and specify a list of arguments, which are the names of the fields to be modified this way.

If with "this way" you mean to say:

  • take the local name as new element name (i.e., I assume you deliberately choose to strip it from namespaces, otherwise, xsl:copy would suffice)
  • if the contents of the element is empty, output "ABSENT"
  • otherwise, take the value of the current node

Then you could do it the following way. But this only works if your requirement is as generically applicable to your use-cases as you say.

Write a template as follows:

<xsl:variable name="names">
    <names>
        <n>firstField</n>
        <n>secondField</n>
        <n>thirdField</n>
    </names>
</xsl:variable>

<xsl:template match="*" mode="by-name">
   <xsl:element name="{local-name(.)}">
      <xsl:apply-templates select="self::node()" mode="text" />
   </xsl:element>
</xsl:template>

<xsl:template match="node()" mode="by-name" />

<xsl:template match="*[text()]" mode="text">
    <xsl:copy />
</xsl:template>

<xsl:template match="*[not(text())]" mode="text">
    <xsl:text>ABSENT</xsl:text>
</xsl:template>

<xsl:template match="/">
    <xsl:apply-templates select="your/current/whatever" />
</xsl:template>

<xsl:template match="*">
    <xsl:for-each select="exslt:node-set($names)/names/n/text()">
        <xsl:apply-templates select="self::*[name() = .]" mode="by-name" />
    </xsl:for-each>
</xsl:template>

Place above code in a separate file, except for the variable, you should place it there, but leave it empty. Use xsl:import to import this file and the only thing you now need to do is to override the xsl:variable.

There are more generic and simpler approaches than this in XSLT 2.0 and 3.0, but this version works with XSLT 1.0.

Disclaimer: not tested, may contain errors, and of course, adjust to your needs ;)

Better / generic approaches

Yes, you can do this. Different XSLT version have different abstraction levels:

XSLT 1.0

You can create a named template (just give it a name). If you call a named template using xsl:call-template, the current context-item will be used as the context item inside the template. This will solve your issue with the names of the fields in your template match.

You can place this in a separate file and import it using xsl:import, which allows you to override it if necessary, or xsl:include, which does not allow overrides, and will raise an error if a naming conflict arises.

XSLT 2.0

You can create a function in XSLT 2.0 which can be called as any other function. A function can contain the template that you showed above.

In XSLT 2.0 you can also use import and include.

XSLT 3.0

You can do the same as in previous versions, but you can now place them in (precompiled) packages, which makes reuse, redistribution and calling them a lot easier.

In addition, XSLT 3.0 has a much improved system with regards to overriding and accepting/exposing components of used packages.

All versions

You probably have a place where you currently use xsl:apply-templates. If you want to prevent the repetition of declaring the matching xsl:template, you can resolve this by creating a generic:

<xsl:template match="node()" mode="special">
    <xsl:call-template name="yourNamedTemplate" />
</xsl:template>

Which is then "called" by using:

<xsl:apply-templates select="firstField | secondField | thirdField" mode="special" />

Tips

If reusability is important, then your "library stylesheet" (in XSLT 3.0 the official term "library package" pops up), you should make the name of the mode in a namespace. In fact, any named component (named templates, modes, functions, accumulators, keys) in your reusable stylesheets should be in their own namespaces. This prevents conflicts and if a user wants to override them, they will have to explicitly do so.

You can create an "inheritance chain". If A imports B imports C, then the highest precedence is given to a named component in A, then B, then C. Same is true for conflicting matching templates. This is (usually) not allowed in your principal stylesheet (hence setting the priority), but A can have the same matching template as B, or as C. In that case, A goes over B goes over C.

Abel
  • 56,041
  • 24
  • 146
  • 247
1

specify a list of arguments, which are the names of the fields to be modified this way.

The answer to your question as asked is no: you cannot tell a template to apply itself to a list of nodes provided in a parameter. The context does not change when you call a named template or a function.

However, you can call a named template (or a function, in XSLT 2.0) once you have established a context - as shown in the answer by Justin.

michael.hor257k
  • 113,275
  • 6
  • 33
  • 51
  • When I read your answer I read his statement again and I'm not sure whether this is impossible, _if his shown template is as generically applicable as his description suggests_. But then again, will need comments from the OP to be sure. – Abel Sep 15 '15 at 14:11
  • @Abel OP stated that the "*implementation and functionality [of the template] do not matter*". OP did **not** state what **other** templates the stylesheet might have. – michael.hor257k Sep 15 '15 at 14:39
  • No, that's why in my answer I suggest to use other modes. Which, in the case that the content of his template uses apply-templates, my suggested approach won't work. – Abel Sep 15 '15 at 16:07
  • @Abel The way I read this, OP wants this template to override any (unknown) templates that would otherwise be applied to the nodes in question. I don't think that's possible when the list of nodes is passed as a parameter. – michael.hor257k Sep 15 '15 at 16:23
  • Aha, in that way. If he _must_ stick to matching templates and want this to behave given a parameter then no, that is not possible, as the params will be out of scope on the match pattern. A notable exception is in XSLT 3.0, where you can mimic this behavior with `xsl:accumulator`, but my guess is that's too advanced a tool for probably a simple requirement. – Abel Sep 15 '15 at 16:27
  • Well, with _mimic_ I do not mean that accumulators will prevent other templates from being called or matched, I mean that an accumulator will always be called (given a pattern) regardless of other matches and its value can be requested at any given time (special rules with streaming). – Abel Sep 15 '15 at 16:28
0

Sort of.

What you could do is create a named template containing the implementation that you want to re-use, like so:

<xsl:template name="MyTemplate">
    <xsl:element name="{local-name(.)}">    
    <xsl:choose>
        <xsl:when test="string-length(.)!=0"><xsl:value-of select="."/></xsl:when>  
        <xsl:otherwise>ABSENT</xsl:otherwise>   
    </xsl:choose>   
    </xsl:element>
</xsl:template>

You would then be able to call this template to create templates matching different elements without needing to repeat the template body.

<xsl:template match="firstField | secondField | thirdField">
    <xsl:call-template name="MyTemplate" />
</xsl:template>

(This is untested, so the syntax might be a bit off)

Justin
  • 84,773
  • 49
  • 224
  • 367