IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope: Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  XML  >

技巧: 多步骤 XSLT

使用节点集扩展将 XSLT 操作分成两个或多个步骤

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 中级

Uche Ogbuji (uche.ogbuji@fourthought.com), 首席顾问, Fourthought, Inc.

2002 年 9 月 01 日

如果用多个阶段或步骤执行转换,那么转换通常会变得更干净和更清晰。首先产生一些中间输出,然后将其进一步转换成最终的输出形式。甚至还可以有多个中间形式。在本篇技巧文章中,Uche Ogbuji 讨论了使用公共节点集扩展将 XSLT 操作分成两个或多个清晰的转换步骤的方法。

UNIX 用户十分熟悉 管道的思想 — 即控制一个程序的输出以使它成为另一个程序的输入的机制。 管道或许位于模块化松散耦合代码的最前面的几个主要示例之后。每个 UNIX 命令都非常简单并且目标明确;将它们串在一起可以产生复杂操作。 通过这种相同的模块化,使用 XSLT 的 XML 处理会获益良多。

通过将转换分成一组单独的阶段或步骤,可以改进代码的易用性并进行重用。 遗憾的是,在纯粹的 XSLT 1.0 中,大多数用于处理转换输入的命令都被禁止用于输出。在 XSLT 2.0 中已经取消了这个限制, 即使在 XSLT 1.0(已经使用了多年)中,也可以使用通常由 XSLT 供应商提供的扩展函数来取消这个限制。

要理解本篇技巧文章,您应该熟悉 XSLT。

剖析表

我有一个小的 XSLT 模板,用于获得文档表并且仅显示每行中的第一项。 设计它是为了与 DocBook 中使用的表(它们基于 SGML 中流行的表模型)一起工作。 清单 1(db-table.xml)显示了样本表。


清单 1. DocBook 形式的简单表(db-table.xml)
<table frame="all">
<title>Numbers and tongues</title>
<tgroup cols="3" align="left" colsep="1" rowsep="1">
<thead>
<row>
<entry>1</entry>
<entry>2</entry>
<entry>3</entry>
</row>
</thead>
<tfoot>
<row>
<entry>I</entry>
<entry>II</entry>
<entry>III</entry>
</row>
</tfoot>
<tbody>
<row>
<entry>one</entry>
<entry>two</entry>
<entry>three</entry>
</row>
<row>
<entry>uno</entry>
<entry>dos</entry>
<entry>tres</entry>
</row>
<row>
<entry>otu</entry>
<entry>abuo</entry>
<entry>ato</entry>
</row>
</tbody>
</tgroup>
</table>

清单 2(db-onecol.xslt)是一个仅呈现表的第一列的转换。


清单 2. 用于呈现 DocBook 样式的表的第一列的 XSLT 转换(db-onecol.xslt)
<?xml version="1.0" encoding="utf-8"?>
<xsl:transform
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exslt="http://exslt.org/common"
version="1.0"
>
<xsl:output method="text"/>
<xsl:template match="table">
<xsl:value-of select="title"/><xsl:text>
</xsl:text>
<xsl:for-each select="tgroup/thead/row">
<xsl:value-of select="entry[1]"/><xsl:text>
</xsl:text>
</xsl:for-each>
<xsl:for-each select="tgroup/tbody/row">
<xsl:value-of select="entry[1]"/><xsl:text>
</xsl:text>
</xsl:for-each>
<xsl:for-each select="tgroup/tfoot/row">
<xsl:value-of select="entry[1]"/><xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:transform>

这输出了简单的文本。实体 是放在 xsl:text 中的换行, 这样就不会将它们作为空白从样式表中除去。其余部分很简单。当遇到表元素时,就输出标题,后跟表头、主体和页脚行中的第一项。 我没有使用 tgroup/*/row 或类似的命令简化成一个 xsl:for-each 循环, 因为 theadtbodytfoot 元素可以以任何顺序出现在文档中, 而我希望按照特定的顺序处理它们。下面的会话演示了这种转换是如何运行的:

$ 4xslt db-table.xml db-onecol.xslt
Numbers and tongues
1
one
uno
otu
I





回页首


表模型不匹配

现在,在 清单 3中我有一个 XHTML 样式的表(xhtml-table.xml),我希望用同样的方法处理它。


清单 3. XHTML 样式的表(xhtml-table.xml)
<table border="1" frame="box">
<caption>Numbers and tongues</caption>
<thead>
<tr>
<th>1</th>
<th>2</th>
<th>3</th>
</tr>
</thead>
<tbody>
<tr>
<td>one</td>
<td>two</td>
<td>three</td>
</tr>
<tr>
<td>uno</td>
<td>dos</td>
<td>tres</td>
</tr>
<tr>
<td>otu</td>
<td>abuo</td>
<td>ato</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>I</td>
<td>II</td>
<td>III</td>
</tr>
</tfoot>
</table>

因为这个表有不同的元素名称和略微不同的组织结构,所以不能简单地重用 DocBook 表模板。 我可以复制该模板并做一些修改,以创建用于 XHTML 元素的特殊版本,但这不是一种很模块化的方法。 另一种方法是将 XHTML 转换成 DocBook 形式,然后通过 DocBook 模板处理它;其优点在于,一旦做了转换, 我还可以重用其它用于 DocBook 表的工具。

清单 4(xhtml-onecol.xslt)是使用 DocBook 表模块来对 XHTML 表进行操作的转换。


清单 4. 用于呈现 XHTML 样式的表的第一列的 XSLT 转换(xhtml-onecol.xslt)
<?xml version="1.0" encoding="utf-8"?>
<xsl:transform
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exslt="http://exslt.org/common"
version="1.0"
>
<xsl:import href="db-onecol.xslt"/>
<xsl:template match="/">
<xsl:apply-templates mode="xhtml"/>
</xsl:template>
<xsl:template match="table" mode="xhtml">
<xsl:variable name="db-table">
<xsl:call-template name="xhtml-table-to-db"/>
</xsl:variable>
<xsl:apply-templates
select="exslt:node-set($db-table)/table"/>
</xsl:template>
<xsl:template name="xhtml-table-to-db">
<xsl:copy>
<title><xsl:value-of select="caption"/></title>
<tgroup cols="{count(thead/tr/th)}">
<thead>
<row>
<xsl:for-each select="thead/tr/th">
<entry><xsl:apply-templates/></entry>
</xsl:for-each>
</row>
</thead>
<tfoot>
<row>
<xsl:for-each select="tfoot/tr/td">
<entry><xsl:apply-templates/></entry>
</xsl:for-each>
</row>
</tfoot>
<tbody>
<xsl:for-each select="tbody/tr">
<row>
<xsl:for-each select="td">
<entry><xsl:apply-templates/></entry>
</xsl:for-each>
</row>
</xsl:for-each>
</tbody>
</tgroup>
</xsl:copy>
</xsl:template>
</xsl:transform>

一个要点:我故意简化了这些示例,以便于着重讨论主要问题。样式表使用了 样式的 XSLT(意味着经常使用 xsl:for-eachxsl:value-of ),而不是使用 样式(它使用许多模板和方式)。 我这样做是因为拉样式更为大家普遍熟悉,虽然推样式在许多方面比较优越。 例如,在实际项目中,我要编写用来将 XHTML 表转换成 DocBook 的模板,以作为标识转换的变体。此外,模板还需要非常多的逻辑来处理 XHTML 和 DocBook 表的常见情形。

多步骤技术的难题出现在下面的行中:

<xsl:apply-templates select="exslt:node-set($db-table)/table"/>

这是从一个阶段到下一个阶段的“切换(hand-off)”。在第一个步骤中,XHTML 表被转换成变量 db-table 内部的 DocBook 形式。这创建了非常类似于 清单 1 中的输出的结果树片段。 为了将它作为第二个步骤的输入,必须将它从结果树片段转换成节点集,这就是 exslt:node-set 函数所做的工作。好几种处理器都支持这个扩展函数, 甚至不支持 EXSLT 扩展的处理器几乎也总是提供它们自己专用的节点集扩展(工作方式是相同的)。

我从这个新的节点集选择表元素以开始第二个步骤,在这个步骤中,来自导入的 db-onecol.xslt 模块的表模板执行其工作。我使用方式( xhtml )来选择 XHTML 表, 这样该模板就不会妨碍 DocBook 模板的操作,DocBook 模板具有相同的匹配,但其导入优先权较低。

这种转换的输出与对纯粹的 DocBook 表进行转换的输出相同。正象我想要的那样,我可以重用 DocBook 代码。





回页首


结束语

该示例是对我在实际项目中所遇到情形作了大量简化的结果。 我需要对 XHTML 源文件重用许多 DocBook 处理模板。 通过在第一步骤中将 XHTML 内容转换成 DocBook,然后在后续的步骤中重用标准 DocBook 模板,我节省了大量工作和调试。与之相比,多步骤 XSLT 的思想甚至更全面。 除了促进代码重用,它还可以将复杂的转换分成易于理解的程序块。 下次面临 XSLT 中的复杂问题时,您可以决定是否将它简化或模块化成一系列管道化的操作。



参考资料



关于作者

Uche Ogbuji 的照片

Uche Ogbuji 是 Fourthought Inc. 的顾问和共同创始人,该公司是专为企业知识管理提供 XML 解决方案的软件供应商和咨询公司。Fourthought 开发了 4Suite,它是 XML、RDF 和知识管理应用程序的开放源码平台。Ogbuji 先生是一名出生于尼日利亚的计算机工程师和作家,他在美国科罗拉多州博耳德生活和工作。可以通过 uche.ogbuji@fourthought.com与 Ogbuji 先生联系。




对本文的评价










回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款