清单 2 的主要问题是,查找总是通过 $result 的字符串值在 lookup 模板中呈现。但这不是我所期望的。如果标签是“CO”,我希望显示“Colorado”。在清单 2 中,$result 的值是“COColorado”,因为查找的结果是整个匹配的 s:state 元素。与清单 1 相比,在清单 1 中,通过选择 s:name 子元素,就可以立即将查找结果限制为目标字符串。
不同的查找情况需要不同的 XPath 表达式,从 XSLT key 返回的节点中提取特定的结果。为了解决上述问题,您可能希望以某种方式在 lookup 模板中传递用于 XSLT key 结果的 XPath 表达式。但是这样做需要能够动态指定 XPath,而 XSLT 1.0 没有提供这种能力。或者,您也许认为可以将 xsl:call-template 放在一个 xsl:variable 中,然后从该变量获得需要的 s:name 子元素。但这样做也不行,因为创建的变量将计算得到一个结果树片段(RTF),而 XSLT 1.0 不允许对 RTF 执行轴操作(axis operation)。
清单 2 还提出了其他一些不那么显著的问题。整个 xsl:call-template 结构笨拙而冗长。XSLT key 的行为有一个容易造成混乱的要求,即使用 xsl:for-each 将上下文变成查找表文档中的一个节点,这个文档通常仍然与源文档(该例中是样式表文档本身)有所不同。
让 EXSLT 来帮忙
除了最后一个问题之外,使用 EXSLT 可以很好地解决其他所有问题。清单 3 中的代码完全模仿了清单 1 的行为,并使用两个 EXSLT 函数解决了上述的问题。它还允许进行整洁的打包。
清单 3. 清单 1 的正确模块化,使用 EXSLT
<?xml version="1.0"?>
<xsl:transform version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:s="http://example.com/states.data"
xmlns:func="http://exslt.org/functions"
xmlns:dyn="http://exslt.org/dynamic"
extension-element-prefixes="func"
>
<xsl:output method="text"/>
<xsl:key name="state-lookup" match="s:state" use="s:abbr"/>
<xsl:variable name="states-top" select="document('')/*/s:states"/>
<xsl:template match="label">
<xsl:value-of select="name"/>
<xsl:text> of </xsl:text>
<xsl:value-of
select="s:lookup('state-lookup', address/state,
$states-top/s:default, $states-top,
'$result/s:name')"/>
</xsl:template>
<func:function name="s:lookup">
<xsl:param name="key-name"/> <!-- name of XSLT key -->
<xsl:param name="look-for"/> <!-- what to look up -->
<xsl:param name="default"/>
<!-- Node set whose first item can be used to set the proper context
for key lookup. -->
<xsl:param name="table-doc" select="$default"/>
<!-- A string containing an XPath expression to be evaluated to
get the final result. By default, just render the XSLT key
result as is -->
<xsl:param name="result-expr" select="'$result'"/>
<!-- Force context to document where the lookup table is defined -->
<xsl:for-each select='$table-doc[1]'>
<xsl:variable name="result" select="key($key-name, $look-for)"/>
<xsl:choose>
<xsl:when test="$result">
<func:result select="dyn:evaluate($result-expr)"/>
</xsl:when>
<xsl:otherwise>
<func:result select="$default"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</func:function>
<s:states>
<s:state><s:abbr>CO</s:abbr><s:name>Colorado</s:name></s:state>
<s:state><s:abbr>CT</s:abbr><s:name>Connecticut</s:name></s:state>
<s:state><s:abbr>ID</s:abbr><s:name>Idaho</s:name></s:state>
<s:state><s:abbr>NJ</s:abbr><s:name>New Jersey</s:name></s:state>
<!-- Added default value -->
<s:default><s:name>[UNKNOWN]</s:name></s:default>
</s:states>
</xsl:transform>
与期望的相同,用户定义函数 s:lookup 可以在多数查找中重用。可以从第一个模板中看到更简洁 XPath 语法是如何简化查找代码。函数调用中传递的参数顺序与函数定义中 xsl:param 的顺序对应。func:result 扩展用于确定回传给调用者的值。在其他方面,用户定义函数体的行为类似于 XSLT 1.0 模板。这种技术提供了很大的灵活性。下面的片段是调用函数的另一种方法,它也能很好地工作:
<xsl:value-of
select="s:lookup('state-lookup', address/state,
$states-top/s:default, $states-top')/s:name"/>
用户定义函数可以巧妙地绕开 XSLT 的 RTF 限制。
结束语
您可以找到很多克服 XSLT 1.0 缺点和局限性的 EXSLT 扩展。这篇技巧介绍了如何使用 EXSLT 简化已经非常棒的 XSLT 1.0 技术