XSLT:我可以创建一个本身就是模板的函数库吗?
XSLT: can I create a library of functions which are a templates theirselves?
我有几个 XSLT 转换。它们都包含相同的模板,看起来像这样(它的实现和功能无关紧要):
<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>
如您所见,我已经列出了要在模板匹配中应用的此模板的字段。
但实际上我想在我所有的转换中使用这个模板,但当然要使用不同的字段名称。换句话说,我想像使用函数一样使用它,我可以插入任何 .xsl 文件并指定一个参数列表,这些参数是要以这种方式修改的字段的名称。
我可以用 XSLT 做吗?
有点。
您可以做的是创建一个命名模板,其中包含您要重复使用的实现,如下所示:
<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>
然后您就可以调用此模板来创建匹配不同元素的模板,而无需重复模板主体。
<xsl:template match="firstField | secondField | thirdField">
<xsl:call-template name="MyTemplate" />
</xsl:template>
(这是未经测试的,所以语法可能有点偏差)
更新,我可能误解了你的问题并再次查看了你的模板(请进一步查看解决方案方向的更通用描述)。
您写道:
<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>
和:
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.
如果 "this way" 你的意思是:
- 将本地名称作为新元素名称(即,我假设您故意选择将其从名称空间中剥离,否则,
xsl:copy
就足够了)
- 如果元素内容为空,则输出"ABSENT"
- 否则取当前节点的值
那么您可以按以下方式进行。但这仅在您的要求像您所说的那样普遍适用于您的用例时才有效。
编写模板如下:
<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>
将以上代码放在一个单独的文件中,除了变量,你应该把它放在那里,但留空。使用 xsl:import
导入此文件,您现在唯一需要做的就是覆盖 xsl:variable
.
在 XSLT 2.0 和 3.0 中有比这更通用和更简单的方法,但这个版本适用于 XSLT 1.0。
免责声明:未经测试,可能包含错误,当然,请根据您的需要进行调整;)
更好/通用的方法
是的,你可以做到。不同的 XSLT 版本具有不同的抽象级别:
XSLT 1.0
您可以创建命名模板(只需为其命名)。如果使用 xsl:call-template
调用命名模板,则当前上下文项将用作模板内的上下文项。这将解决您的模板中字段名称匹配的问题。
您可以将它放在一个单独的文件中并使用 xsl:import
导入它,这允许您在必要时覆盖它,或者使用 xsl:include
,它不允许覆盖,并且会引发错误如果出现命名冲突。
XSLT 2.0
您可以在 XSLT 2.0 中创建一个可以像调用任何其他函数一样调用的函数。函数可以包含您在上面显示的模板。
在 XSLT 2.0 中,您还可以使用导入和包含。
XSLT 3.0
您可以像在以前的版本中那样做,但您现在可以将它们放在(预编译的)包中,这使得重用、重新分发和调用它们变得容易得多。
此外,XSLT 3.0 在重写和 accepting/exposing 已用包的组件方面有了很大的改进。
所有版本
您可能有一个地方目前正在使用 xsl:apply-templates
。如果你想防止重复声明匹配 xsl:template
,你可以通过创建一个泛型来解决这个问题:
<xsl:template match="node()" mode="special">
<xsl:call-template name="yourNamedTemplate" />
</xsl:template>
然后 "called" 使用:
<xsl:apply-templates select="firstField | secondField | thirdField" mode="special" />
提示
如果可重用性很重要,那么您的 "library stylesheet"(在 XSLT 3.0 中会弹出官方术语 "library package"),您应该在命名空间中创建模式名称。事实上,可重用样式表中的任何命名组件(命名模板、模式、函数、累加器、键)都应该在它们自己的命名空间中。这可以防止冲突,如果用户想要覆盖它们,他们将必须明确地这样做。
您可以创建一个 "inheritance chain"。如果 A 导入 B 导入 C,那么最高优先级被赋予 A 中的命名组件,然后是 B,然后是 C。对于冲突的匹配模板也是如此。这(通常)在您的主要样式表中是不允许的(因此设置了优先级),但是 A 可以具有与 B 或 C 相同的匹配模板。在这种情况下,A 超越 B 超越 C。
specify a list of arguments, which are the names of the fields to be
modified this way.
您的问题所问的答案是否定的:您不能告诉模板将自身应用于参数中提供的节点列表。当您调用命名模板或函数时,上下文不会改变。
但是,一旦建立了上下文,您可以调用命名模板(或函数,在 XSLT 2.0 中)- 如 Justin 的回答所示。
我有几个 XSLT 转换。它们都包含相同的模板,看起来像这样(它的实现和功能无关紧要):
<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>
如您所见,我已经列出了要在模板匹配中应用的此模板的字段。
但实际上我想在我所有的转换中使用这个模板,但当然要使用不同的字段名称。换句话说,我想像使用函数一样使用它,我可以插入任何 .xsl 文件并指定一个参数列表,这些参数是要以这种方式修改的字段的名称。
我可以用 XSLT 做吗?
有点。
您可以做的是创建一个命名模板,其中包含您要重复使用的实现,如下所示:
<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>
然后您就可以调用此模板来创建匹配不同元素的模板,而无需重复模板主体。
<xsl:template match="firstField | secondField | thirdField">
<xsl:call-template name="MyTemplate" />
</xsl:template>
(这是未经测试的,所以语法可能有点偏差)
更新,我可能误解了你的问题并再次查看了你的模板(请进一步查看解决方案方向的更通用描述)。
您写道:
<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>
和:
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.
如果 "this way" 你的意思是:
- 将本地名称作为新元素名称(即,我假设您故意选择将其从名称空间中剥离,否则,
xsl:copy
就足够了) - 如果元素内容为空,则输出"ABSENT"
- 否则取当前节点的值
那么您可以按以下方式进行。但这仅在您的要求像您所说的那样普遍适用于您的用例时才有效。
编写模板如下:
<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>
将以上代码放在一个单独的文件中,除了变量,你应该把它放在那里,但留空。使用 xsl:import
导入此文件,您现在唯一需要做的就是覆盖 xsl:variable
.
在 XSLT 2.0 和 3.0 中有比这更通用和更简单的方法,但这个版本适用于 XSLT 1.0。
免责声明:未经测试,可能包含错误,当然,请根据您的需要进行调整;)
更好/通用的方法
是的,你可以做到。不同的 XSLT 版本具有不同的抽象级别:
XSLT 1.0
您可以创建命名模板(只需为其命名)。如果使用 xsl:call-template
调用命名模板,则当前上下文项将用作模板内的上下文项。这将解决您的模板中字段名称匹配的问题。
您可以将它放在一个单独的文件中并使用 xsl:import
导入它,这允许您在必要时覆盖它,或者使用 xsl:include
,它不允许覆盖,并且会引发错误如果出现命名冲突。
XSLT 2.0
您可以在 XSLT 2.0 中创建一个可以像调用任何其他函数一样调用的函数。函数可以包含您在上面显示的模板。
在 XSLT 2.0 中,您还可以使用导入和包含。
XSLT 3.0
您可以像在以前的版本中那样做,但您现在可以将它们放在(预编译的)包中,这使得重用、重新分发和调用它们变得容易得多。
此外,XSLT 3.0 在重写和 accepting/exposing 已用包的组件方面有了很大的改进。
所有版本
您可能有一个地方目前正在使用 xsl:apply-templates
。如果你想防止重复声明匹配 xsl:template
,你可以通过创建一个泛型来解决这个问题:
<xsl:template match="node()" mode="special">
<xsl:call-template name="yourNamedTemplate" />
</xsl:template>
然后 "called" 使用:
<xsl:apply-templates select="firstField | secondField | thirdField" mode="special" />
提示
如果可重用性很重要,那么您的 "library stylesheet"(在 XSLT 3.0 中会弹出官方术语 "library package"),您应该在命名空间中创建模式名称。事实上,可重用样式表中的任何命名组件(命名模板、模式、函数、累加器、键)都应该在它们自己的命名空间中。这可以防止冲突,如果用户想要覆盖它们,他们将必须明确地这样做。
您可以创建一个 "inheritance chain"。如果 A 导入 B 导入 C,那么最高优先级被赋予 A 中的命名组件,然后是 B,然后是 C。对于冲突的匹配模板也是如此。这(通常)在您的主要样式表中是不允许的(因此设置了优先级),但是 A 可以具有与 B 或 C 相同的匹配模板。在这种情况下,A 超越 B 超越 C。
specify a list of arguments, which are the names of the fields to be modified this way.
您的问题所问的答案是否定的:您不能告诉模板将自身应用于参数中提供的节点列表。当您调用命名模板或函数时,上下文不会改变。
但是,一旦建立了上下文,您可以调用命名模板(或函数,在 XSLT 2.0 中)- 如 Justin 的回答所示。