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.