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

developerWorks 中国  >  XML | Web development  >

在运行时将数据与 XSLT 样式表集成

使用 XML、XSLT 和 MVC 设计模式创建表示标记

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 初级

Andre Tost (atost@us.ibm.com), 资深软件工程师, IBM

2002 年 7 月 01 日

现在,许多应用程序利用 XML 来格式化业务数据。这允许使用可在范围广泛的平台和编程语言上处理的自描述性标记数据。通过使用 XML 数据格式,异构应用程序之间的集成变得更加容易。例如,Web 服务技术促进了基于 XML 的消息格式用于后端应用程序数据。然而,在运行时将该数据集成到用户输出可能是一次挑战。在本文中,Andre Tost 描述了如何通过使用 XSLT 样式表来完成数据集成。

本文描述了一种用于进行下列操作的机制:分别开发应用程序业务数据和表示数据,然后使用通用的 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 层体系结构
n 层体系结构示例

这里,我将集中讨论第 1 层和第 2 层。 如果您期望用户输出由 servlet 处理,则可以改进这个体系结构。 这个 servlet 调用与后端通信的应用程序业务逻辑。这个后端可以是(例如)存在于网络中独立服务器上的 Enterprise JavaBean。可以假设所有业务数据都被格式化成 XML。 然后,通过使用 Java Server Page(JSP)准备该数据以进行表示,并将它发回客户机,如图 2 所示。


图 2. 使用 JSP 准备用于表示的数据
使用 JSP 准备用于表示的数据

如果将该图片转换成“模型-视图-控制器(Model-View-Controller (MVC))”设计模式上, 则 servlet 充当控制器角色,应用程序逻辑代表模型,JSP 是视图元素。(诚然,这种分配是简化了的,但它显示了基本原理。)





回页首


准备用于表示的应用程序数据

为了充分利用使用 XML 创建应用程序数据的好处, 必须先将数据转换成可表示的格式。做到这一点的常用方法是 XSLT 样式表。在 XSL 规范(另见 参考资料中的可扩展样式表语言(eXtensible Stylesheets Language))中有两种可能的方法。您可以:

  • 使用所谓的 格式化对象(FO)
  • 创建简单的 XSLT 样式表

FO 通常创建二进制输出格式,但至今仍未被广泛使用(即使它们更专用于编辑表示数据)。 另外,常用的 XSL 处理器和工具几乎也不支持它们。而另一方面,XSLT 样式表被更广泛地使用,所以本文中我将集中讨论 XSLT 样式表。

XSLT 样式表处理结构

XSLT 样式表 始终使用一个 XML 文档来充当其输入。XSL 处理器根据样式表中的规则来处理这个输入文档。 这会产生一个新的 XML 输出文档,如图 3 所示。


图 3. 使用 XSLT 样式表来处理 XML 文档
使用 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。

使用 Apache Xalan 来处理用 Java 编写的 XSLT 样式表


如果您要试验本文中给出的一些示例, 则可以考虑开发利用 Apache Xalan XSLT 处理器的 Java 应用程序。这个 XSLT 引擎基于 Apache Xerces XML 解析器, 它支持 Java API for XML Processing(JAXP)版本 1.1。JAXP 1.1 规范定义一个名为 javax.xml.transform.Transformer 的接口,它是应用程序用来执行实际转换的实例。Transformer 实例是通过 javax.xml.transform.TransformerFactory 创建的。

DoTransform 样本类将两个文件名作为输入 ― 一个用于输入 XML 文件,另一个用于包含 XSLT 样式表的文件。

注: 因为使用标准的 Java API,所以可以在任何符合 XSLT 的处理实现上运行该代码。

将表示数据与样式表分开

让我们撇开样式表, 单独地描述表示数据。 为了表示数据和表示元素之间的关联,可以为所使用的 HTML 标记定义扩展。 然后,可以创建一个使用这些扩展的样式表来创建最终的输出数据。

一个问题是 XSLT 处理器只处理一个 XML 输入文档, 然后使用样式表创建一个 XML 输出文档。(注:XML 文档可以以各种形式传递给 XSLT 处理器: 作为文件、作为字节流或作为实际的 DOM 对象。)然而,在本例中, 想要有两个输入源:应用程序数据和表示数据。可以用两种方法解决这个问题:

  • 使用 XSL 中的 document() 函数,嵌入一个附加的 XML 文件以供样式表使用。
  • 通过编程将输入源合并到一个 XML 文档,这样就向 XSLT 处理器提供了一个输入源。

两个选项都是有效的。我将在这里使用第二个选项,它会产生图 4 中演示的结构。


图 4. 将多个输入源合并到一个 XML 文档
将多个输入源合并到一个 XML 文档

请注意,按照 XSLT 处理器的要求, 这里的显示数据必须以格式良好的 XML 格式存在,这一点很重要。HTML 并不是在所有情况下都是格式良好的, 然而可以合并 XHTML 标准来弥补了这一缺陷。 我不打算详细讨论这个主题,而只提供一个示例,让我们看一下 HTML 中的 <br> 元素。 通常,这个元素被用做空元素,但并没有按 XML 标准要求的那样来结束它。 在格式良好的 XML 中,该元素必须写成 <br /> (虽然最常用的浏览器同时允许这两种用法)。

扩展 HTML 元素

要将数据转换成期望的输出格式,需要知道哪个表示元素与哪个数据元素相关联。 可以通过将属性添加到 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 服务定义组件模型,这种模型将产生类似与在这里表述的技术。



参考资料



关于作者

Andre Tost 为 IBM 位于美国明尼苏达州罗切斯特(Rochester)的 Software Group 工作。近几年来,他一直从事各种面向对象的项目,主要是 IBM 的 SanFrancisco 的产品开发和 WebSphere Business Component 产品开发。他目前同 IBM 战略业务伙伴一起工作。 可以通过 atost@us.ibm.com与 Andre 联系。




对本文的评价










回页首


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