给定以下XML:
<root>
Pacman <format bold="1" italic="1">rules</format>!
</root>
有什么比下面的更好的实现,它会导致2n-1个可能的条件语句?
<xsl:template match="format">
<xsl:choose>
<xsl:when test="@bold='1' and @italic='1'">
<b><i><xsl:value-of-select="."/></i></b>
</xsl:when>
<xsl:when test="@bold='1'">
<b><xsl:value-of-select="."/></b>
</xsl:when>
<xsl:when test="@italic='1'">
<i><xsl:value-of-select="."/></i>
</xsl:when>
</xsl:choose>
</xsl:template>
你可以看到,如果我想添加一个新的可能属性,比如underline="1"
,这将导致这里出现4个新的条件。
编辑:还假设我不能使用CSS类,必须使用HTML标记进行样式设置。
我的XSLT太生疏了,铰链不会移动,但我认为可以使用<xsl:call-template … />
每次处理一个属性,每个属性使用一个模板。
下面可能有一些非常明显的错误,但希望它能给你带来共鸣。
<xsl:template name="bold">
<xsl:choose>
<xsl:when test="@bold='1'">
<b><xsl:call-template name="italics" /></b>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="italics" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="italics">
…
</xsl:template>
我将从XSLT2.0解决方案开始,然后告诉您如何将其转换为XSLT1.0。
<xsl:template match="format[@italic='1']" priority="10">
<i><xsl:next-match/></i>
</xsl:template>
<xsl:template match="format[@bold='1']" priority="9">
<b><xsl:next-match/></b>
</xsl:template>
<xsl:template match="format[@underline='1']" priority="8">
<u><xsl:next-match/></u>
</xsl:template>
<xsl:template match="format" priority="7">
<xsl:value-of select="."/>
</xsl:template>
现在,xsl:next match需要XSLT2.0,但1.0有xsl:apply-imports,这几乎可以完成相同的工作,只是四个模板规则现在需要在单独的模块中,每个模块导入下一个。不方便,但这就是为什么人们更喜欢2.0。
模板可以通过以下方式进行链接:每个模板在运行时只调用一次,在XSLT中只调用两次。
<xsl:template name="bold">
<xsl:choose>
<xsl:when test="@bold='1'">
<b><xsl:call-template name="italics" /></b>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="italics" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="italics">
<xsl:when test="@italics='1'">
<i><xsl:call-template name="underscore" /></i>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="underscore" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="underscore">
<xsl:when test="@underscore='1'">
...
</xsl:when>
<xsl:otherwise>
...
</xsl:otherwise>
</xsl:choose>
</xsl:template>
当前接受的解决方案以固定的硬编码顺序生成所需的属性,该顺序与源XML文档中包含的format
元素的属性顺序不符。
在HTML的情况下,这样的解决方案可能是可以接受的,但在其他情况下,需要保留属性的顺序。
此答案提供了一个保留属性顺序的解决方案。此外,属性的名称是而不是硬编码到代码中(可以包含在单独的文档中),这使得代码完全独立于对源属性名称或要生成的元素的相应名称的任何更改。
这里有一个简单的XSLT1.0解决方案,它只占用一个XSLT样式表,不使用<xsl:apply-imports>
此解决方案不依赖或不知道属性的顺序或数量,如果以任何方式更改属性的顺序和数量,则可以正常工作:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my" exclude-result-prefixes="my">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<my:mapping>
<map old="strikeout">strike</map>
</my:mapping>
<xsl:variable name="vMap" select="document('')/*/my:mapping/*"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="format">
<xsl:variable name="vAttribs" select="@*"/>
<xsl:call-template name="genAttribs">
<xsl:with-param name="pAttribs" select="$vAttribs"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="genAttribs">
<xsl:param name="pAttribs" select="/.."/>
<xsl:choose>
<xsl:when test="$pAttribs">
<xsl:variable name="vMappedElemName"
select="$vMap[@old = name($pAttribs)]"/>
<xsl:variable name="vElemName" select=
"concat($vMappedElemName,
substring(name($pAttribs[not($vMappedElemName)])
,1,1)
)
"/>
<xsl:element name="{$vElemName}">
<xsl:call-template name="genAttribs">
<xsl:with-param name="pAttribs"
select="$pAttribs[position() > 1]"/>
</xsl:call-template>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
当此转换应用于以下XML文档时:
<root>
Pacman
<format bold="1" italic="1"
underscore="1"
strikeout="1">rules</format>!
</root>
生成所需的正确结果:
<root>
Pacman
<b>
<i>
<u>
<strike>rules</strike>
</u>
</i>
</b>!
</root>
解释:
对属性进行逐一处理以生成嵌套元素。只有在处理完最后一个属性后,我们才会在生成的最内部元素的主体中生成属性父级的文本节点
我们维护一个映射表——只有当所需的属性名称到元素名称的转换不仅仅是属性名称的第一个字母时,才需要指定映射
attribute-name --> element-name
。如果在映射表中指定了属性名称,那么我们使用映射表中该元素的字符串值来生成元素名称。如果映射表中没有指定属性名称,那么对于元素名称,我们使用属性名称的第一个字母。
因此,如果指定了任何新属性,并且其对应(要生成的)元素的名称是该属性名称的第一个字母,则解决方案不需要任何修改。
最后:请注意,映射表不需要是XSLT代码的一部分(这里只是为了方便起见)——在现实世界中,这将是一个单独的XML文档(文件),并且当需要添加新的attribute-name --> element-name
映射时,将不必更新XSLT代码。