本文描述了一种用于进行下列操作的机制:分别开发应用程序业务数据和表示数据,然后使用通用的 XSLT 样式表将它们组合在一起。应用程序业务数据被格式化成 XML 形式,而表示数据可以用传统工具来创建。 附加属性增强了表示数据,样式表使用这些属性来将表示细节应用于实际内容。 对于最终格式的创建,不需要专门的编程,因为这个过程是由 XSLT 处理器完成的。
为最充分地理解本文,您应该了解 XML 和 XSLT 的基础知识。 请参阅 参考资料一节,以获得至可作为这些主题介绍的教程和文章的链接。 这里显示的所有示例都是使用 Apache Xerces XML 解析器,版本 2.0.1 和 Apache Xalan XSLT 处理器,版本 2.3.1(请参阅 参考资料中的 Apache XML Project)产生的。
对于应用程序体系结构, 我选择了 Java 2 企业版(J2EE)提议的 n 层体系结构,如图 1 所示。
图 1. n 层体系结构
这里,我将集中讨论第 1 层和第 2 层。 如果您期望用户输出由 servlet 处理,则可以改进这个体系结构。 这个 servlet 调用与后端通信的应用程序业务逻辑。这个后端可以是(例如)存在于网络中独立服务器上的 Enterprise JavaBean。可以假设所有业务数据都被格式化成 XML。 然后,通过使用 Java Server Page(JSP)准备该数据以进行表示,并将它发回客户机,如图 2 所示。
图 2. 使用 JSP 准备用于表示的数据
如果将该图片转换成“模型-视图-控制器(Model-View-Controller (MVC))”设计模式上, 则 servlet 充当控制器角色,应用程序逻辑代表模型,JSP 是视图元素。(诚然,这种分配是简化了的,但它显示了基本原理。)
为了充分利用使用 XML 创建应用程序数据的好处, 必须先将数据转换成可表示的格式。做到这一点的常用方法是 XSLT 样式表。在 XSL 规范(另见 参考资料中的可扩展样式表语言(eXtensible Stylesheets Language))中有两种可能的方法。您可以:
- 使用所谓的 格式化对象(FO)
- 创建简单的 XSLT 样式表
FO 通常创建二进制输出格式,但至今仍未被广泛使用(即使它们更专用于编辑表示数据)。 另外,常用的 XSL 处理器和工具几乎也不支持它们。而另一方面,XSLT 样式表被更广泛地使用,所以本文中我将集中讨论 XSLT 样式表。
XSLT 样式表 始终使用一个 XML 文档来充当其输入。XSL 处理器根据样式表中的规则来处理这个输入文档。 这会产生一个新的 XML 输出文档,如图 3 所示。
图 3. 使用 XSLT 样式表来处理 XML 文档
在大多数情况下,这种结构会导致样式表中规则和表示数据的混合。 我假设我有 XML 格式的地址数据,需要将这些数据转换成 HTML,以在浏览器中表示。 清单 1 显示了 XML 输入数据。注:XSLT 样式表处理步骤的输出是另一个 XML 文档。HTML 不一定是格式良好的,所以这里将始终产生 XHTML(XHTML 一定始终是格式良好)。 稍后再讨论这个问题。
清单 1. XML 输入数据
<addressbook>
<address>
<addressee>John Smith</addressee>
<streetaddress>250 18th Ave SE</streetaddress>
<city>Rochester</city>
<state>MN</state>
<postalCode>55902</postalCode>
</address>
<address>
<addressee>Bill Morris</addressee>
<streetaddress>1234 Center Lane NW</streetaddress>
<city>St. Paul</city>
<state>MN</state>
<postalCode>55123</postalCode>
</address>
</addressbook>
|
清单 2 显示了将该数据转换成 HTML 格式的 XSLT 样式表。(请参阅 使用 Apache Xalan 来处理用 Java 编写的 XSLT 样式表, 以查看使用 Apache Xalan XSLT 处理器对给定的 XML 文档运行 XSLT 样式表的 Java 样本程序。)
清单 2. 使用 XSLT 样式表来将数据转换成 HTML
<?xml version='1.0'?>
<xsl:stylesheet xmlns:xsl="<http://www.w3.org/1999/XSL/Transform>"
version="1.0">
<xsl:output method="html" indent="yes"/>
<!-- copy all elements that are not matched otherwise -->
<xsl:template match="* | @*">
<xsl:copy><xsl:copy-of select="@*"/><xsl:apply-templates/></xsl:copy>
</xsl:template>
<xsl:template match="addressbook">
<html>
<head><title>My Addressbook</title></head>
<body>
<h1>My Addressbook</h1>
<table border="1">
<thead>
<tr>
<td>Name</td>
<td>Street</td>
<td>City</td>
<td>Postal Code</td>
</tr>
</thead>
<tbody>
<xsl:for-each select="address">
<tr>
<td><xsl:value-of select="addressee"/></td>
<td><xsl:value-of select="streetaddress"/></td>
<td><xsl:value-of select="city"/></td>
<td><xsl:value-of select="postalCode"/></td>
</tr>
</xsl:for-each>
</tbody>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
|
清单 2 揭示了这样一个问题: 样式表包含有关业务数据和表示数据本身的转换信息,从而使得几乎不可能使用 HTML 编辑环境。 另外,需要为每种输出格式复制样式表。例如,如果客户机设备是移动电话,则可以将表示数据格式化成 WML,而不是 HTML。
让我们撇开样式表, 单独地描述表示数据。 为了表示数据和表示元素之间的关联,可以为所使用的 HTML 标记定义扩展。 然后,可以创建一个使用这些扩展的样式表来创建最终的输出数据。
一个问题是 XSLT 处理器只处理一个 XML 输入文档, 然后使用样式表创建一个 XML 输出文档。(注:XML 文档可以以各种形式传递给 XSLT 处理器: 作为文件、作为字节流或作为实际的 DOM 对象。)然而,在本例中, 想要有两个输入源:应用程序数据和表示数据。可以用两种方法解决这个问题:
-
使用 XSL 中的
document()函数,嵌入一个附加的 XML 文件以供样式表使用。 - 通过编程将输入源合并到一个 XML 文档,这样就向 XSLT 处理器提供了一个输入源。
两个选项都是有效的。我将在这里使用第二个选项,它会产生图 4 中演示的结构。
图 4. 将多个输入源合并到一个 XML 文档
请注意,按照 XSLT 处理器的要求,
这里的显示数据必须以格式良好的 XML 格式存在,这一点很重要。HTML 并不是在所有情况下都是格式良好的,
然而可以合并 XHTML 标准来弥补了这一缺陷。
我不打算详细讨论这个主题,而只提供一个示例,让我们看一下 HTML 中的
<br> 元素。
通常,这个元素被用做空元素,但并没有按 XML 标准要求的那样来结束它。
在格式良好的 XML 中,该元素必须写成
<br /> (虽然最常用的浏览器同时允许这两种用法)。
要将数据转换成期望的输出格式,需要知道哪个表示元素与哪个数据元素相关联。 可以通过将属性添加到 HTML 元素来完成这一任务,这允许样式表处理数据绑定,如下面的清单 3 所示。
清单 3. 将属性添加到 HTML 元素
<?xml version="1.0"?>
<combinedelement>
<view>
<html>
<head>
<title>My Addressbook</title>
</head>
<body>
<form method="POST">
<table id="AddressesPanel" dataSrc="addressbook"
rowelement="address">
<tr>
<th>Name</th>
<th>Street</th>
<th>City</th>
<th>Postal Code</th>
</tr>
<tr>
<td><input datafld="addressee" ></input></td>
<td><input datafld="streetaddress"></input></td>
<td><input datafld="city"></input></td>
<td><input datafld="postalCode"></input></td>
</tr>
</table>
<input type="submit" value="Change"/>
</form>
</body>
</html>
</view>
<model>
<addressbook>
<address>
<addressee>John Smith</addressee>
<streetaddress>250 18th Ave SE</streetaddress>
<city>Rochester</city>
<state>MN</state>
<postalCode>55902</postalCode>
</address>
<address>
<addressee>Bill Morris</addressee>
<streetaddress>1234 Center Lane NW</streetaddress>
<city>St. Paul</city>
<state>MN</state>
<postalCode>55123</postalCode>
</address>
</addressbook>
</model>
</combinedelement>
|
早先,我提到了将两个输入文档合并成一个文档。一个 XML 文档只能有一个根元素,这就是我将
<combinedelement> 元素定义为新根元素的原因。
按照 MVC 术语,我将为表示数据添加
<view> 元素,为业务数据添加
<model> 元素。
在下列代码中:
<table id="AddressesPanel" dataSrc="addressbook" rowelement="address"> |
dataSrc 属性确定
<model> 元素中的元素,该元素必须显示在表中。
rowelement 属性定义哪个元素充当迭代符。
表中的每个输入元素都有一个
datafld 属性,如下所示,它表明显示了哪个业务数据元素的值。
<td><input datafld="addressee" ></input></td> <td><input datafld="streetaddress"></input></td> <td><input datafld="city"></input></td> <td><input datafld="postalCode"></input></td> |
您可以用象 WebSphere Studio(请参阅 参考资料)之类的工具或允许创建 HTML 页面的任何其它环境,方便地创建这里显示的表示数据。 附加属性必须以手工方式插入 HTML 源文档。然而,仍可以在工具中直观地创建基本表示视图。 完成这一任务无需编程或 XSL 技能。当然,也可以根本不用任何工具的帮助来创建表示数据, 如果您感到这样最舒适的话。
最后一个难题是创建最终输出格式的 XSLT 样式表。 可以独立于实际应用程序开发该样式表,因为它不包含任何特定于应用程序的信息。 清单 5 中显示的样式表是不完整的,仅涵盖 HTML 表的处理。然而,其它 HTML 元素都可以以非常相似的方法来处理。
清单 5. 使用 XSLT 样式表来处理 HTML 表
<?xml version='1.0'?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="html" indent="yes"/>
<!-- copy all elements which don't match any template -->
<xsl:template match="* | @*">
<xsl:copy><xsl:copy-of select="@*"/><xsl:apply-templates/></xsl:copy>
</xsl:template>
<!-- delete the model element and all its content -->
<xsl:template match="model"/>
<!-- delete the combinedelement element, but not its content -->
<xsl:template match="combinedelement">
<xsl:apply-templates/>
</xsl:template>
<!-- delete the view element, but not its content -->
<xsl:template match="view">
<xsl:apply-templates/>
</xsl:template>
<!-- handle all <tr> elements -->
<xsl:template match="TR | tr">
<xsl:choose>
<!-- no special handling of the table header -->
<xsl:when test="th">
<xsl:apply-templates/>
</xsl:when>
<xsl:otherwise>
<!-- store the name of the rowelement in a variable -->
<!-- in the addressbook sample, this is 'address' -->
<xsl:variable name="rowname">
<xsl:value-of
select='(ancestor::node()[name() = 'table'])/@rowelement"/>
</xsl:variable>
<!-- call the 'processRow' template, passing the rowelement -->
<!-- and a counter -->
<xsl:call-template name="processRow">
<xsl:with-param name="numAddresses">
<xsl:value-of select="count(//*[name() = $rowname])"/>
</xsl:with-param>
<xsl:with-param name="n">1</xsl:with-param>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- the processRow template calls itself recursively -->
<!-- for all rowelements -->
<xsl:template name="processRow">
<xsl:param name="numAddresses"/>
<xsl:param name="n"/>
<!-- done iterating? -->
<xsl:if test="$numAddresses >= $n">
<xsl:copy>
<!-- for all td's -->
<xsl:for-each select="td">
<!-- copy the element content -->
<xsl:copy>
<xsl:copy-of select="@*"/>
<!-- find all elements with a 'datafld' attribute -->
<!-- and save them in a variable for later use -->
<xsl:for-each select="./*[@datafld]">
<xsl:variable name="elname">
<xsl:value-of select="./@datafld"/>
</xsl:variable>
<!-- copy the element, change the 'datafld' -->
<!-- attribute into a 'name' attribute -->
<xsl:copy>
<xsl:copy-of select='(@*)[name() != 'datafld']"/>
<xsl:attribute name="name">
<xsl:value-of select="@datafld"/>
</xsl:attribute>
<!-- retrieve the value of the element -->
<!-- from the model by using the -->
<!-- 'datafld' attribute as a search -->
<!-- argument -->
<xsl:attribute name="value">
<xsl:value-of
select='(//*[name() = $elname])[position() = $n]"/>
</xsl:attribute>
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:for-each>
<!-- call the template recursively, increase counter -->
<xsl:call-template name="processRow">
<xsl:with-param name="n">
<xsl:value-of select="$n + 1"/>
</xsl:with-param>
<xsl:with-param name="numAddresses">
<xsl:value-of select="$numAddresses"/>
</xsl:with-param>
</xsl:call-template>
</xsl:copy>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
|
清单 5 样式表中有一个细节值得深入研究, 因为它在许多情况下都有用。XSLT 没有为处理循环定义任何机制, 当必须多次处理同一个输入元素以产生正确输出时需要这些循环。可以通过定义一个递归调用其本身的模板来克服这个缺点。 该模板将变量传递给其本身以充当计数器,从而定义循环何时结束。
在本文介绍的解决方案中,应用程序业务数据和表示数据被分开来开发,并由通用 XSLT 样式表将它们合在一起, 该解决方案并不完整;它只描述了概念。我期望以后将开发相似的(但完整的和健壮的)解决方案。
然后,就可以使用象 清单 5 中所示的通用样式表, 而且在适用的地方,可以重用它。例如,结构化信息标准促进组织(Organization for the Advancement of Structured Information Standards (OASIS))提出了交互式应用程序 Web 服务(Web Services for Interactive Applications)倡议(请参阅 参考资料)。 该倡议也基于 MVC 模式,它尝试为可视的 Web 服务定义组件模型,这种模型将产生类似与在这里表述的技术。
- 您可以参阅本文在 developerWorks 全球站点上的
英文原文.
- 通过单击本文顶部或底部的
讨论,参与有关本文的
论坛。
- 请参阅
Java 2 Platform, Enterprise Edition (J2EE),以获取有关 J2EE 的最新信息。
- 浏览
Apache XML 项目及其商业品质、基于标准的 XML 解决方案和来自实现观点的反馈。
- 访问
Apache Xalan XSLT 处理器主页,
获取有关 Xalan 的信息,以帮助您将 XML 文档转换成 HTML、文本或其它 XML 文档类型。
- 请阅读包括 XSL 新闻、教程、文章和链接的
可扩展样式表语言(eXtensible Stylesheet Language(XSL))页面。
- 访问
可扩展超文本标记语言(eXtensible HyperText Markup Language (XHTML))站点,
以获取 XHTML 规范。
- 请在
xml.com 网站上阅读 Eric van der Vlist 的优秀文章
Style-free XSLT Style Sheets。
-
OASIS提出了
交互式应用程序 Web 服务倡议,
它试图为可视的 Web 服务定义组件模型。
- 在
developerWorks XML 技术专区和
XSL专题中查找更多 XML 参考资料。
- 获取
IBM WebSphere Studio Application Developer,
这是一个易于使用的集成开发环境,用于构建、测试和部署 J2EE 应用程序,包括从 DTD 和模式生成 XML 文档。
- 了解如何成为一位
XML 和相关技术的 IBM 认证开发人员。
Andre Tost 为 IBM 位于美国明尼苏达州罗切斯特(Rochester)的 Software Group 工作。近几年来,他一直从事各种面向对象的项目,主要是 IBM 的 SanFrancisco 的产品开发和 WebSphere Business Component 产品开发。他目前同 IBM 战略业务伙伴一起工作。 可以通过 atost@us.ibm.com与 Andre 联系。