以文本方式查看主题

-  计算机科学论坛  (http://bbs.xml.org.cn/index.asp)
--  『 其他W3C规范 』  (http://bbs.xml.org.cn/list.asp?boardid=25)
----  技巧:将 XSLT 查找表打包成 EXSLT 函数  (http://bbs.xml.org.cn/dispbbs.asp?boardid=25&rootid=&id=15008)


--  作者: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 技术。


W 3 C h i n a ( since 2003 ) 旗 下 站 点
苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
62.500ms