在“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 技術。