用于数据的 XML: XSL 样式表:推还是拉?
英文原文
内容:
推样式表
拉样式表
因此,拉总是比推好,对吗?
结束语
参考资料
关于作者
对本文的评价
相关内容:
订阅 developerWorks 时事通讯
在 XML 专区还有:
教程
工具和产品
代码与组件
文章
查看两种 XSL 样式表的制作技术以及如何将其综合用于数据
Kevin Williams(kevin@blueoxide.com)
CEO,Blue Oxide Technologies,LLC
2002 年 5 月
专栏作家 Kevin Williams 研究了用于创建 XSL 样式表的两种最常见的制作样式:推(push)和拉(pull)。他研究了一些简单的 XML 和 XSL 示例,并讨论了每种方法的优缺点。
当制作 XSL 样式表时,可以使用这两种主要制作样式中的任何一种:
推 样式是由在输出文档中创建输出的处理程序构造的。输出基于源文档中遇到的元素类型。元素内容被推 给适当的处理程序。
另一方面,拉 样式根据需要通过拉出 源文档中的内容来构建输出文档。
您所选择的样式对样式表的代码复杂性和可维护性具有重要的影响。
推样式表
推样式表是样式表的经典形式。所有主要的 XSL 生成工具都可以生成推样式表,它们是 XSL 参考资料中通常讨论的形式。这种格式的样式表由一系列 xsl:template 元素组成,每个元素处理文档中的一个特定元素类型。例如,假设您有下列源文档:
清单 1. 样本数据文档
<Orders>
<Invoice>
<CustomerName>Kevin Williams</CustomerName>
<Address>100 Nowhere Lane</Address>
<City>Nowheresville</City>
<State>VA</State>
<Zip>24182</Zip>
<Item>
<ID>E38-19273</ID>
<Description>3-inch Grommets</Description>
<Count>37</Count>
<TotalCost>37.00</TotalCost>
</Item>
<Item>
<ID>E22-18272</ID>
<Description>2-inch Widgets</Description>
<Count>22</Count>
<TotalCost>11.00</TotalCost>
</Item>
</Invoice>
<Invoice>
<CustomerName>John Public</CustomerName>
<Address>200 Anywhere Street</Address>
<City>Anytown</City>
<State>VA</State>
<Zip>24177</Zip>
<Item>
<ID>E22-18272</ID>
<Description>2-inch Widgets</Description>
<Count>27</Count>
<TotalCost>13.50</TotalCost>
</Item>
<Item>
<ID>E19-28376</ID>
<Description>1-inch Bolts</Description>
<Count>16</Count>
<TotalCost>4.00</TotalCost>
</Item>
</Invoice>
</Orders>
现在,假设您要将其转换成下面的 XHTML 文档:
清单 2. 样本数据输出
<html>
<body>
<h1>Kevin Williams</h1>
<p>100 Nowhere Lane<br />
Nowheresville, VA 24182</p>
<table>
<tr>
<th>Description</th>
<th>Cost</th>
</tr>
<tr>
<td>3-inch Grommets</td>
<td>37.00</td>
</tr>
<tr>
<td>2-inch Widgets</td>
<td>11.00</td>
</tr>
</table>
<p />
<h1>John Public</h1>
<p>200 Anywhere Street<br />
Anytown, VA 24177</p>
<table>
<tr>
<th>Description</th>
<th>Cost</th>
</tr>
<tr>
<td>2-inch Widgets</td>
<td>13.50</td>
</tr>
<tr>
<td>1-inch Bolts</td>
<td>4.00</td>
</tr>
</table>
</body>
</html>
需要在样式表中创建一系列模板。这些模板处理您要带入输出文档的每个不同元素。每个模板还必须创建支持的 XHTML 元素结构,例如表标题和行。使用推方法从源文档(清单 1)创建清单 2 中所示的输出的样式表类似于下面的样子:
清单 3. 用于数据的样本推样式表
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="Orders">
<html>
<body>
<xsl:apply-templates select="Invoice"/>
</body>
</html>
</xsl:template>
<xsl:template match="Invoice">
<xsl:apply-templates select="CustomerName" />
<p>
<xsl:apply-templates select="Address" />
<xsl:apply-templates select="City" />
<xsl:apply-templates select="State" />
<xsl:apply-templates select="Zip" />
</p>
<table>
<tr>
<th>Description</th>
<th>Cost</th>
</tr>
<xsl:apply-templates select="Item" />
</table>
<p />
</xsl:template>
<xsl:template match="CustomerName">
<h1><xsl:value-of select="." /></h1>
</xsl:template>
<xsl:template match="Address">
<xsl:value-of select="." /><br />
</xsl:template>
<xsl:template match="City">
<xsl:value-of select="." />
<xsl:text>, </xsl:text>
</xsl:template>
<xsl:template match="State">
<xsl:value-of select="." />
<xsl:text> </xsl:text>
</xsl:template>
<xsl:template match="Zip">
<xsl:value-of select="." />
</xsl:template>
<xsl:template match="Item">
<tr>
<xsl:apply-templates />
</tr>
</xsl:template>
<xsl:template match="Description">
<td><xsl:value-of select="." /></td>
</xsl:template>
<xsl:template match="TotalCost">
<td><xsl:value-of select="." /></td>
</xsl:template>
<xsl:template match="*">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="text()" />
</xsl:stylesheet>
请注意:有两个令人感兴趣的添加项,您应当将它们包括在样式表中:
最后的空模板确保从输出文档中省去多余内容(如发票上项目的单价)。
以文档次序处理其所有子元素的模板确保处理自始至终都沿着源树进行。那样,您不会仅因为没有特定的发票模板而丢失任何发票内容。
正如您看到的那样,这种结构有点臃肿。虽然制作代码是非常简单的,但维护代码是棘手的。即使对于这个十分简单的示例,如果您要查明 XHTML 表是如何填充的,那么就必须从一个模板跳到另一个模板来发现 td 对象的创建位置。在更复杂的示例中,可能会有许多页的模板,许多模板都是从其它模板调用的。代码的维护也更困难。
拉样式表
另一方面,拉样式表依靠编码者的能力来知道接下来期望源文档中的哪些元素出现。通过重复元素、在输出中创建适当的结构以及根据需要从源文档中拉出值,代码可以显式地进行循环处理。这里是一个执行与清单 2 相同转换的样式表,但它使用拉策略,而不是推策略:
清单 4. 用于数据的样本拉样式表
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="Orders">
<html>
<body>
<xsl:for-each select="Invoice">
<h1>
<xsl:value-of select="CustomerName" />
</h1>
<p>
<xsl:value-of select="Address" /><br />
<xsl:value-of select="City" />
<xsl:text>, </xsl:text>
<xsl:value-of select="State" />
<xsl:text> </xsl:text>
<xsl:value-of select="Zip" />
</p>
<table>
<tr>
<th>Description</th>
<th>Cost</th>
</tr>
<xsl:for-each select="Item">
<tr>
<td><xsl:value-of select="Description" /></td>
<td><xsl:value-of select="TotalCost" /></td>
</tr>
</xsl:for-each>
</table>
<p />
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
在这种形式的样式表中,您只有一个模板,它对应于您正在尝试检索的信息的根。嵌在源文档中的嵌套信息是通过使用 xsl:for-each 元素处理当前特定类型的节点的所有子节点来检索的。正如您可看到的那样,这种样式表非常易于读取。它比推形式的样式表短,其布局与期望的输出文档布局非常接近。当用拉形式编码时,只要采用期望的输出样本,将其添加到顶级模板。然后,可以用适当的代码替换该样本中的值来检索源文档的值。
因此,拉总是比推好,对吗?
尽管拉格式的确优于推格式(可读性、简洁且易于读取的布局),但您还是应该根据源文档的特征来选择样式表。请查看下面的结构示例,它包含描述一个虚构医疗过程的叙述:
清单 5. 样本叙述文档
<SurgeryLog>
<Narrative>
<section time="13:00:00">First, the surgeon used the <tool>3-inch
scalpel</tool> to make a <procedure>two-inch incision</procedure>
in the patient's <location>lower left abdomen</location>.
<procedure>Cautery</procedure> was then applied to the
<target>severed blood vessels</target> to stop the
bleeding.</section>
<section time="13:14:23">The surgeon then <procedure>cleaned and
dressed the incision</procedure>, using <tool>four two-by-two
sterile gauze pads</tool> and <tool>medium sutures</tool>.</section>
</Narrative>
</SurgeryLog>
在这个叙述中,信息可能会以任何次序出现。文档中的一行可能有两条 tool 子句,后面跟了一条 procedure 子句,或者三条 location 子句。假设您想要产生与下面相似的输出:
清单 6. 样本叙述输出
<html>
<body>
<h1>Surgery Log</h1>
<p><b>13:00:00</b></p>
<p>First, the surgeon used the <i>3-inch scalpel</i> to make a
<font color="#FF8800">two-inch incision</font> in the patient's
lower left abdomen. <font color="#FF8800">Cautery</font> was
then applied to the severed blood vessels to stop the bleeding.</p>
<p><b>13:14:23</b></p>
<p>The surgeon then <font color="#FF8800">cleaned and dressed
the incision</font>, using <i>four two-by-two sterile gauze pads</i>
and <i>medium sutures</i>.
</body>
</html>
有关样式表推格式的一件很重要的事,就是信息的出现次序是没有关系的。下面的样式表将清单 5 中的源 XML 处理成期望的 XHTML:
清单 7. 叙述的样本推样式表
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="SurgeryLog/Narrative">
<html>
<body>
<h1>Surgery Log</h1>
<xsl:apply-templates />
</body>
</html>
</xsl:template>
<xsl:template match="section">
<p>
<b>
<xsl:value-of select="@time" />
</b>
</p>
<xsl:apply-templates />
</xsl:template>
<xsl:template match="tool">
<i>
<xsl:apply-templates />
</i>
</xsl:template>
<xsl:template match="procedure">
<font color="#FF8800">
<xsl:apply-templates />
</font>
</xsl:template>
</xsl:stylesheet>
请注意:因为模板是以正确的次序自动处理的,所以嵌套项是怎么样的不是现在主要关心的问题。您可以处理更复杂的情形,如清单 8 中的情形,其中,procedure 元素包含了一个 tool 元素:
清单 8. 带嵌套元素的样本叙述
<SurgeryLog>
<Narrative>
<section time="13:00:00">First, the surgeon <procedure>used the
<tool>3-inch scalpel</tool> to make a two-inch incision</procedure>
in the patient's <location>lower left abdomen</location>.
<procedure>Cautery</procedure> was then applied to the
<target>severed blood vessels</target> to stop the
bleeding.</section>
<section time="13:14:23">The surgeon then <procedure>cleaned and
dressed the incision</procedure>, using <tool>four two-by-two
sterile gauze pads</tool> and <tool>medium sutures</tool>.</section>
</Narrative>
</SurgeryLog>
当将样式表应用于该源文档时,结果正如期望的那样:嵌在过程中的工具是斜体并且是橙色的。现在,请看一下用拉样式创建的相同样式表:
清单 9. 叙述的样本拉样式表
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="SurgeryLog/Narrative">
<html>
<body>
<h1>Surgery Log</h1>
<xsl:for-each select="section">
<p>
<b>
<xsl:value-of select="./@time" />
</b>
</p>
<p>
<xsl:for-each select="*|text()">
<xsl:choose>
<xsl:when test="name()='procedure'">
<font color="#FF8800">
<xsl:value-of select="." />
</font>
</xsl:when>
<xsl:when test="name()='tool'">
<i>
<xsl:value-of select="." />
</i>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="." />
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</p>
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
请注意这个拉样式表多么臃肿。您不得不检查每个节点,并且 — 如果该节点需要特殊处理 — 为其添加适当的格式。这个样式表甚至不能处理清单 8 中引述的复杂情形。该代码忽略了过程(procedure)标记内的工具(tool)标记。如果您扩展了样式表以正确处理所有可能的工具、目标、过程等嵌套,那么样式表会迅速膨胀至一个难以控制的(且不可维护的)大小。
结束语
这些示例演示了我在我的大多数专栏中得出的要点:数据文档与叙述文件有本质区别,因为:
数据文档中的信息以可在代码中预料的次序出现。
数据文档中不包含松散文本,因此,将该文本转换成输出文档无需专门的代码。
比起重命名元素、对它们进行重新排序并在目标树中将它们向上聚合一个级别(或将它们向下推至一个更详细的级别),将源数据文档映射为输出文档并不复杂。如果您使用拉模型,映射很简单且易于维护。
另一方面,基于叙述的文档则完全相反。
信息以不能轻易预见的次序出现。
一些文本游离于元素的上下文之外,所以需要将它正确地移到输出文档。
为了在输出文档中准确地重现叙述,不管元素出现在哪里,样式表都必须处理它们,而推模型在这一方面比较擅长。
简而言之,当设计数据文档的样式表时,首先考虑使用拉模型。对于叙述文档,如果可能,请使用推模型。精通于编写这两种样式表 — 并知道它们各自的优点和缺点 — 将使您能够处理您将来可能会遇到的任何样式表设计工作。
参考资料
通过单击本文顶部或底部的讨论,参与有关本文的论坛。
从 W3C 学习 XSLT 和 XPath 技术的良好基础知识。
请阅读 Kevin 以前的专栏和文章:
XML for Data #1:使用 XML Schema 原型(developerWorks,2001 年 6 月)
XML for Data #2:用模式样式化(developerWorks,2001 年 7 月)
XML for Data #3:XLink 和数据(developerWorks,2001 年 7 月)
XML for Data #4:灵活体系结构的四点技巧(developerWorks,2001 年 8 月)
XML for Data #5:Native-XML 数据库:一个关于数据的坏主意?(developerWorks,2001 年 10 月)
XML for Data #6:多对多关系的建模(developerWorks,2002 年 1 月)
XML for Data #7:对 XQuery 的前瞻(developerWorks,2002 年 2 月)
Soapbox:Kevin 讲述了他提倡为什么在数据方面,XML Schema 不费吹灰之力击败了 DTD(developerWorks,2001 年 6 月)的原因。
请阅读 XML structures for existing databases,摘自 Wrox 出版的书籍 Professional XML Databases中的一个章节(developerWorks,2001 年 1 月)。
获取 IBM WebSphere Studio Site Developer,它是构建、测试和部署 Java Server Pages、servlet 和与 XML 相关的应用程序和网站的一种易于使用的集成开发环境。
查明如何才能成为一名 XML 和相关技术领域的 IBM 认证开发人员。
请在 developerWorks XML 专区上查找更多有关 XML 的参考资料。
关于作者
Kevin Williams 是 Blue Oxide Technologies, LLC 的 CEO,这是一家从事设计 XML 和 Web 服务创作软件的公司。请访问该公司的网站 http://www.blueoxide.com。可以通过 kevin@blueoxide.com 与他联系。