-- 作者:oceans
-- 发布时间:3/3/2005 3:22:00 PM
-- 技巧:将 XSLT 查找表打包成 EXSLT 函数
使用社区标准 XSLT 扩展 级别: 中级 Uche Ogbuji (uche.ogbuji@fourthought.com) 首席顾问, Fourthought, Inc. 2005 年 2 月 在前一篇技巧文章中,Uche Ogbuji 示范了如何用 XSLT 建立查找表。在后续技巧文章中,他又介绍了如何处理这种查找中的错误和默认条件。这篇技巧将说明如何使用 EXSLT(XSLT 扩展的社区标准)的函数模块,以及如何通过将代码打包成易于重用的函数来改进这项技术。 在“XSLT lookup tables”和“技巧:XSLT 查找表中的默认值和错误处理”这两篇技巧中,我说明了如何创建 XSLT 代码来查找为处理程序提供的静态值,其中包括默认值和错误处理的支持。处理这类查找的代码非常简单但是相当笨拙,尤其是在转换中使用多个表的时候。清单 1 是查找表的示例实现(摘自最近的那篇技巧),在查找失败时,它可以提供默认值。 清单 1. 带有默认值的查找表示例(states-lookup-default.xslt) <?xml version="1.0"?> <xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:s="http://example.com/states.data" version="1.0" > <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:apply-templates select="$states-top"> <xsl:with-param name="curr-label" select="."/> </xsl:apply-templates> </xsl:template> <xsl:template match="s:states"> <!-- This template updated to add a default value signal --> <xsl:param name="curr-label"/> <xsl:variable name="look-for" select="$curr-label/address/state"/> <xsl:variable name="default" select="s:default"/> <xsl:variable name="result" select="key('state-lookup', $look-for)/s:name"/> <xsl:choose> <xsl:when test="$result"> <xsl:value-of select="$result"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="$default"/> </xsl:otherwise> </xsl:choose> </xsl:template> <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> 在 XSLT 1.0 中进行模块化的局限性 您可能希望将键查找代码打包在指定模板中,使代码更加清晰。清单 2 就是这样做的,这种方法不是很好,但在没有其他复杂因素(或者没有查找类型的限制)的情况下,这已经是 XSLT 1.0 所能做到的最好的表现了。 清单 2. 将查找代码打包成可重用的模板 <?xml version="1.0"?> <xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:s="http://example.com/states.data" version="1.0" > <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:call-template name="lookup"> <xsl:with-param name="key-name" select="'state-lookup'"/> <xsl:with-param name="look-for" select="address/state"/> <xsl:with-param name="default" select="$states-top/s:default"/> <xsl:with-param name="table-doc" select="$states-top"/> </xsl:call-template> </xsl:template> <xsl:template name="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"/> <!-- 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"> <xsl:value-of select="$result"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="$default"/> </xsl:otherwise> </xsl:choose> </xsl:for-each> </xsl:template> <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> 清单 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 技术。
|