级别: 初级
		
				Martin Gerlach, 软件工程师, IBM Almaden 研究中心
		2000 年  12 月  03 日
		使
用 XML 描述 Web 应用用户界面的部件可以使通过 XSL 样式表转换用于多种设备的用户界面变得简单。本文描述了使用 XML 数据和
XSL 样式表来构建复杂 Web 应用的用户界面。Web 日历样本应用将演示基本的技术和概念。本文还包括超过 24
个的代码样本,您可以轻易扩展这些样本,以满足特定需求。
		
		
		
		要充分理解本文,您应该熟悉基于 HTTP 协议的 Web 应用和 HTTP
请求-应答机制。还应该了解 Java 编程语言以及如何使用 Java Servlet
作为 Web 应用的访问点。
		
				
						问题
				
		
		
比如,对于复杂的 Web
应用,您已经有一个想法(甚至曾经设计过)。您知道,如果既可以从标准的
Web 浏览器,又可以通过无线访问协议 (WAP) 设备甚至可能是声音来访问
Web 应用,那么就可以获得更多的利润。您将把 XML
用于核心应用和处理用户交互的服务器进程之间的通信。对于每种支持的客户机格式,用户界面将由所有可能的用户操作的很多视图组成,但是所有这些视图将共享一个公共设计(即公司设计)和某些公共的隐藏信息。
		在如 HTML
这样更复杂的输出格式中,浏览器窗口中的大部分将始终或多或少地显示相同的信息(如链接、图像等)。既然计划要使用
XSLT 来将由核心系统产生的 XML
变换成不同的客户机格式,那么,可以认识到,对每种支持的客户机格式只使用一个
XSL
样式表是不切实际的。这将使维护工作繁重而困难。简而言之,您需要一个有效的方式来组织
XML 数据和所有这些 XSL 样式表。
		
		
		
		
		
		
				
						样本应用
				
		
		
为说明和演示解决这个问题的框架,我编写了一个简单的、基于 Web
的日历应用,名为 WebCal。它维护多个用户的日历,并提供以下特性:
		
				- 
新用户可以通过注册页面注册日历。用户提供个人信息,然后被分配系统用户名和口令。
- 已注册用户可以使用登录页面登录到系统。
- 
登录之后,用户可以在其日历(日历视图)的日、月和年视图之间切换。
- 用户可以创建、查看、修改和删除活动。
- 
活动有一个摘要、描述、开始时间和结束时间。开始时间、结束时间和摘要都显示在日历视图中。
图 1 显示登录页面的屏幕快照:
		
		
				图 1. WebCal
登录页面
		
		
		 
		
		一旦成功登录,用户即可使用其日历。典型的日历视图如
        图 2 所示。它显示从 2001 年 2 月 11
日到 2001 年 2 月 17 日之间的那个星期。
      
		
		
				图 2.
WebCal 星期视图
		
		
		 
		
		
所有视图都在蓝框中显示,该框架包含应用标题和徽标、一些链接、主导航栏和一个子导航栏。
		
框架支持多个外部链接、内部视图和内部操作的导航栏。主导航(位于徽标之下)提供从除登录和注册页面之外的所有视图至日历视图的链接。子导航栏允许内部操作,例如,在日历视图中创建活动以及在活动视图中编辑或删除活动。
		由于使用标准显示尺寸(1024 x 768 或更大)的标准 Web 浏览器的
HTML 通常是最复杂的输出,所以,WebCal 应用只演示用于 HTML 输出的
XSL 样式表。所有原则对其它基于 HTTP 的格式都相同。出于与用作 Web
服务器和 Servlet 引擎的 WebSphere 3.0 兼容的原因,WebCal 用 Java
1.1.7 编写(请参阅 
        参考资料 )。它使用可以在任何 Servlet
引擎中运行的 Servlet。该 Servlet 使用浏览器 cookie(所有普通的基于
HTTP 的浏览器,包括 WAP 电话在内,都可以接受)来跟踪用户会话。
      
		WebCal 理解一系列请求类型,这些请求类型都使用自己的 HTTP
参数集。在样本应用中,通过 Servlet 做所有的事:
		
				- 分析请求的 HTTP 参数
- 执行必需的操作
- 生成应答 XML
- 将 XML 变换成 HTML(或任何其它格式)
- 将应答发回客户机
清单 1. HTTP 请求示例
				
						
								| 
												
														http://localhost/webcal/WebCalServlet?action=ShowDay&date=2001-02-17
 
 | 
				
		
		
		
		
		
		
		
		
				
						第 1 步. 基本
XML/XSL 框架
				
		
		
首先,我们讨论如何构造和生成应用所用的 XML 数据。然后,显示 XSL
样式表的通用结构,样式表用来将 XML
数据变换成任何期望的输出格式。
		
				构造 XML 数据
				
从 XML 到任何期望格式的 XSL 变换通过一系列对 XML
数据中模式的匹配尝试来进行。应该构造 XML
数据,使模式匹配尽可能简单。
      
		从应用核心发送到前端的 XML
数据应该只包含显示和进一步处理所必需的信息。首先,需要一个根元素,如清单
2
中所示。它应该对所有视图都是相同的,因为某些显示部分也对所有视图相同。如我们将看到的,通过匹配根元素,XSL
处理器可以最轻易地找到该信息。
		
		
				清单 2. 文档头和根元素
		
		
		
				
						
								| 
												
														<?xml version="1.0"?>
 <!DOCTYPE webcal>
 <webcal>
 <!--general stuff-->
 <!--view specific-->
 </webcal>
 
 | 
				
		
		
		
既然页面只是表单输入的模板而且它们看起来总是相同的,所以,几乎不需要任何其它处理或
XML 数据来显示注册和登录页面。
		
对于日历视图,需要包括有关要显示时期的信息。其中包括这些天的名称和确切日期,以及那段时期内的所有活动。既然该样本演示应用不在日历视图中显示活动描述,所以不需要在
XML 中包括它。因为要创建至那些活动的更详细视图的链接,所以,在 XML
中存储每个活动的活动标识。
		
		
				清单 3. 天的表示
		
		
		
				
						
								| 
												
														<webcal>
 ...
 <day date="2001-02-17">
 <event id="..." start="2001-02-17T20:00" end="2001-02-18T06:00">
 <summary>Martin's birthday party</summary>
 </event>
 </day>
 ...
 </webcal>
 
 | 
				
		
		
		显示超过一天的视图有几个 
        <day> 
元素。可以对这些元素排序,或以其出现的顺序处理它们。为了排序,可以使用
date 属性(以 ISO8601 的格式)。对于月视图,可以将 
        <day> 元素组合成 
        <week> 
标记。这在 XML 生成过程中不需要太多计算,并且可以简化 XSL
处理。有关详细信息,请查看 
        用 Java 代码还是 XSL
进行计算?侧栏。
      
		
				
						
								|   | 
												
														
																| 用 Java 代码还是 XSL 进行计算? 
 
必须权衡用 XML 生成代码和通过 XSL 生成代码之间的利弊。通常,当 XSL
计算过于复杂(例如将天组合成星期,以及生成用于在天、星期和月之间导航的 
          <next>和<previous>元素)时,最好用 Java 处理,并用 XML
反映结果。另外,即使在某些天中没有活动,最好也为月份中的每一天生成一个标记,而不要试着在
XSL 中实现循环(这很重要)。有关 XSL 的详细信息,请参阅 
          第 2 步。 |  | 
				
		
		在细节视图中显示活动需要所有可用的活动信息。对于 WebCal,这将与
        清单 3 基本相同,只是添加了要以 
        <description> 元素出现的活动描述。
      
		显示要创建活动的视图不一定需要任何 XML 数据,但是可以包括一个 
        <event> 元素来提供缺省值。修改活动再一次需要所有活动数据,以便用当前值填充编辑表单的字段。
      
		
当用户登录时,将需要所有视图的某些信息(如框架的不同部分、链接和导航栏)。子导航根据视图类型的不同而不同,但是,对于同一类型的两个视图,其子导航
是相同的(例如,显示不同天的两个天视图)。用户登录之后,可能还要包括一些个人数据。示例应用只用到了名称和用户名称。事实证明,包括当前视图的
HTTP 参数也十分有用,这样可以在 XSL 样式表中使用它们。
		以下代码样本显示为从星期天开始的、在“Martin
的生日聚会”之前的那个星期生成的完整
XML。它已准备好变换成客户机所喜爱的格式。 
        
		
				
		
		
				清单 4. 表示视图的完整 XML
		
		
		
				
						
								| 
												
														<webcal>
 <http-params>
 <param name="action" value="ShowWeek"/>
 <param name="date" value="2001-02-11"/>
 </http-params>
 <user>
 <login>martin</login>
 <firstname>Martin</firstname>
 <middlename/>
 <lastname>Gerlach</lastname>
 </user>
 <navigation>
 <global>
 <main-nav>
 <link type="internal" text="Day" href="ShowDay">
 <pass-param name="date"/>
 </link>
 <link type="internal" text="Day" href="ShowWeek">
 <pass-param name="date"/>
 </link>
 <link type="internal" text="Day" href="ShowMonth">
 <pass-param name="date"/>
 </link>
 </main-nav>
 <links>
 <link type="internal" text="Logout" href="Logout"/>
 <link type="external" text="Home" href="http://www.ibm.com/developer"/>
 </links>
 </global>
 <actions>
 ...
 <action name="ShowWeek">
 <sub-nav>
 <link type="internal" text="New Event" href="ShowNewEvent">
 <pass-param name="date"/>
 </link>
 </sub-nav>
 </action>
 ...
 </actions>
 </navigation>
 <now date="2000-11-07T15:30"/>
 <week date="2001-02-11" previous="2001-02-04" next="2001-02-18">
 <day date="2001-02-11"/>
 <day date="2001-02-12"/>
 <day date="2001-02-13"/>
 <day date="2001-02-14"/>
 <day date="2001-02-15">
 <event id="(id1)" start="2001-02-15T18:00" end="2001-02-15T20:00">
 <summary>Shopping for Martin's birthday party</summary>
 </event>
 </day>
 <day date="2001-02-16"/>
 <day date="2001-02-17">
 <event id="(id2)" start="2001-02-17T20:00" end="2001-02-18T06:00">
 <summary>Martin's birthday party</summary>
 </event>
 </day>
 </week>
 </webcal>
 
 | 
				
		
		
		对星期视图只特别生成了 
        <week> 元素,其余所有内容都在所有视图中相同,并可以保存在文件系统或数据库中。
      
		
				图 2 显示了清单 4
在屏幕上的显示结果。(将在 
        第 2 步 中讨论必要的 XSL
变换)。如果正在运行样本应用,可以将 
        "&format=xml" 
附加在当前 URL 之后,然后重新装入该页面,以查看当前视图的应答
XML。
      
		
				用 Java 生成 XML
				
要处理的数据驻留在服务器上。访问之后,需要将其格式化
XML。可以使用任何可用的 Java XML 包来生成包含应答 XML 的 DOM
树。下例引用 XML4J(请参阅 
        参考资源)。
      
		可以用 
        org.w3c.dom.Document 或 
        org.w3c.dom.Element 实例的方式在 Java 中传递 XML
数据。在提供几种有用的方法来分析所包含元素的语法方面,文档接口具有一定的优势。
      
		
		
				清单 5. 用根节点创建文档。
		
		
		
				
						
								| 
												
														import org.w3c.dom.*; // DOM interfaces
 import org.apache.xerces.dom.*; // DOM implementation for XML4J
 
 ...
 Document responseXML = createDocument("webcal");
 
 public Document createDocument(String rootName)
 {
 Document doc = new DocumentImpl();
 doc.appendChild(doc.createElement(rootName));
 return doc;
 }
 
 | 
				
		
		
		
		
		
				清单 6. 获得文档根元素
		
		
		
				
						
								| 
												
														Element root = doc.getDocumentElement();
 
 | 
				
		
		
		
		
		
				清单 7. 将 <day date="2001-02-17"/>附加到元素之后
		
		
		
				
						
								| 
												
														Element dayElem = doc.createElement("day");
 dayElem.setAttribute("date","2001-02-17");
 someElement.appendChild(dayElem);
 
 | 
				
		
		
		
		
		
				清单 8. 将文本附加到元素 summaryElem: <summary>This is
a summary</summary> 之后
		
		
		
				
						
								| 
												
														org.w3c.dom.Text textNode = doc.createTextNode("This is a summary");
 summaryElem.appendChild(textNode);
 
 | 
				
		
		
		您可能要编写自己的实用程序方法来构建应用的 XML
文档。可以使用可用的 XML 包中的语法分析器,以从流(包括文件)读取
XML 或将其写入。在源代码中,查看 
        ShowAction.displayResult() 或任何其它 ShowAction 的 
        generateXML() 方法,以了解这是如何完成的。
      
		您可能还想参考其它
        developerWorks 中关于 XML 和 Java
的文章,以获得其它观点(请参阅 
        参考资料)。
      
		
				构造 XSL 样式表
				
XSL 样式表包含从一种 XML 格式到另一种 XML
格式(例如,任何有良好格式的标记语言)的变换规则。这些规则被编写成所谓的模板,可以用两种方式将它们应用到源
XML。一种是通过名称从其它模板调用它们,另一种是自动将它们匹配到源
XML 的各个部分。转换由 XSL 处理器完成。
      
		通常以对源 XML 的引用和对要使用的样式表的引用来初始化 XSL
处理器。作为变换的第一步,应该给 XSL 处理器一个明确匹配源 XML
某一部分的模板。然后,将从那个通常匹配根节点的模板调用所有其它模板。这与调用所有其它子程序的“主例程”类似。
		
		
				清单 9. ShowWeek.xsl (V.1):用于 WebCal 星期视图的 HTML
输出的 XSL 模板
		
		
		
				
						
								| 
												
														<xsl:style sheet>
 <xsl:template match="webcal">
 <html>
 <head>
 <title>WebCal - Week</title>
 </head>
 <body>
 <!-- week view content -->
 </body>
 </html>
 </xsl:template>
 </xsl:style sheet>
 
 | 
				
		
		
		
				包括样式表和通过名称调用模板
				
每个视图都需要这样的一个模板,但是,所有视图的 
        <html> 、 
        <head> 和 
        <body> 
元素都将相同。因此,如果抽取这些元素并将其放入公共模板,则可以从所有视图调用该模板(通过名称)。
      
		
		
				清单 10. ShowWeek.xsl
(V.2):包括和调用生成框架的样式表
		
		
		
				
						
								| 
												
														<xsl:style sheet>
 <xsl:include href="http://localhost/webcal/html/Frame.xsl"/>
 <xsl:template match="webcal">
 <xsl:call-template name="frame"/>
 </xsl:template>
 </xsl:style sheet>
 
 | 
				
		
		
		
假设现在已经由所包括的样式表生成了内容,那么,对每个视图,还需要样式表吗?实际的视图内容(在框架中)又在哪里生成呢?生成框架的模板需要调用另一个模板,即生成内容的模板。但是,框架如何知道应该是 
        
哪个 模板(例如,生成的框架用于哪个视图)呢?事实上,框架模板无需知道,它只需调用一个名为
"main" 的模板即可。"main" 模板在样式表中定义。在包含与 "webcal"
匹配的模板的样式表中包括该样式表。这就是为什么需要对每个视图使用一个样式表的原因。以下是星期视图的完整结构:
      
		
		
				清单 11. ShowWeek.xsl (V.3)
		
		
		
				
						
								| 
												
														<xsl:style sheet>
 <xsl:output method="html"/> <!-- for XT -->
 <xsl:include href="http://localhost/webcal/html/Frame.xsl"/>
 <xsl:include href="http://localhost/webcal/html/ShowWeekMain.xsl"/>
 <xsl:template match="webcal">
 <xsl:call-template name="frame"/>
 </xsl:template>
 </xsl:style sheet>
 
 | 
				
		
		
		
		
		
				清单 12. Frame.xsl
		
		
		
				
						
								| 
												
														<xsl:style sheet>
 <xsl:template name="frame">
 <html>
 <head>
 <title>
 WebCal -
 <xsl:value-of select="/webcal/http-params/param[@name='action']/@value"/>
 </title>
 </head>
 <body>
 <!-- do the frame -->
 <table>
 <xsl:comment>frame content</xsl:comment>
 ...
 <td>
 <div style="width: ...; height: ...; ...>
 <xsl:call-template name="main">
 <xsl:with-param name="width">?lt;/xsl:with-param>
 <xsl:with-param name="height">?lt;/xsl:with-param>
 </xsl:call-template>
 </div>
 </td>
 <xsl:comment>frame content continued</xsl:comment>
 ?
 </table>
 </body>
 </html>
 </xsl:template>
 </xsl:style sheet>
 
 | 
				
		
		
		
		
		
				清单 13. ShowWeekMain.xsl
		
		
		
				
						
								| 
												
														<xsl:style sheet>
 <xsl:template match="main">
 <xsl:param name="width"/>
 <xsl:param name="height"/>
 <xsl:comment>Week view content</xsl:comment>
 <!-- produce week view content here. Keep in mind this is called from
 inside a <div> element with the given width and height -->
 </xsl:template>
 </xsl:style sheet>
 
 | 
				
		
		
		如
        清单 4
所示,所有这些从 XML 数据产生以下 HTML。
      
		
		
				清单 14. 星期视图的 HTML
		
		
		
				
						
								| 
												
														<html>
 <head>
 <title>WebCal - ShowWeek</title>
 </head>
 <body>
 <table>
 <!--frame content-->
 ...
 <td>
 <div style="width: ...; height: ...; ...>
 <!--week view content-->
 ...
 </div>
 </td>
 <!--frame content continued-->
 ...
 </table>
 </body>
 </html>
 
 | 
				
		
		
		
				选择必要的样式表
				
如果输出格式没有 HTML 那样复杂(例如
WML),则通常没有必要对框架使用额外的样式表。另外,如果视图显示非常类似的内容,则它们可以共享样式表。在
WebCal 中,虽然源 XML
略有不同,但是,用于创建和编辑活动的视图使用同一个样式表。(有关代码,请参阅 
        第 3 步)。
      
		但通常,当使用这个框架时,需要以下样式表:
		
				- 每个视图和格式都需要一个根样式表(如上面的 ShowWeek.xsl)
- 每一种格式都需要一个框架样式表
- 每个视图和格式都需要一个视图内容样式表,包含由 "frame"
模板调用的命名的模板。
每个样式表都位于自己的文件中。一个样式表中可以有多个模板,这样,"frame"
和 "main" 模板可以使用 
        <xsl:call-template> 或 
        <xsl:apply-templates> 将其它模板应用到应答 XML
的某些部分中。
      
		要想使用 HTTP 来包括如上所示的样式表,所有样式表都要位于本地 Web
服务器文档目录之下,如清单 15 中所示:			
		
		
				清单 15. 本地 Web 服务器目录
		
		
		
				
						
								| 
												
														/
 webcal/
 html/
 Frame.xsl
 LoginView.xsl
 LoginViewMain.xsl
 DayView.xsl
 DayViewMain.xsl
 WeekView.xsl
 WeekViewMain.xsl
 ...
 wml/
 Frame.xsl
 LoginView.xsl
 LoginViewMain.xsl
 ...
 
 | 
				
		
		
		
				用 Java 执行变换
				
WebCal 使用 James Clark 的 XT(请参阅 
        参考资料 )来将样式表应用到应答
XML。如下所示,变换涉及到某些对 XT 包的调用。其它象 LotusXSL 这样的
XSL 处理器(请参阅 
        参考资料)以不同的方式对此进行处理。
      
		WebCal 从 HTTP 请求检索:
		
				- 浏览器类型
- 浏览器理解的格式
- 它所设置成的语言
- 请求的操作
从所有这些信息,可以找到视图的正确根 XSL 样式表。请参阅
        清单 16. 用 Java 进行 XSL
变换。
      
		
				总结第 1 步:得到的教训
				
对于 XML 设计和 XML 生成:
      
		
				- 确定视图所需的信息
- 把为公共所需的信息而编写的 XML
生成代码与为视图特别所需的信息而编写的 XML 生成代码分开。
- 在生成 XML 之前或在其过程中,用 Java 代码执行复杂计算,然后用
XML 反映结果(如果可能并且每种应答格式都需要的话)。
- 使整个结构保持灵活。
对于 XSL 样式表的通用结构:
		
				- XSL 样式表应该位于 Web 服务器的文档目录中,以允许用 
          <xsl:include>将其装入。
				
						第 2 步:用 XSLT 生成输出
				
		
		
前一节已经讲了输出的基本部分,即 
        <html> 、 
        <head> 和 
        <body> 标记。本节讨论在 
        <body> 标记内部的 HTML 生成,哪一部分用框架的
XSL 完成,哪一部分用不同的 "main" 样式表完成。
      
		
				框架
				
表格是用来布置 HTML 页面的好方式。WebCal
对框架使用一个表格,主要部分位于框架中间最大的表单元中。 
        图 3 中的红线显示在 Frame.xsl
中用来绘制框架的表。除了中间的主要部分之外,所有事都由 Frame.xsl
完成。三个导航面板上的链接在每个视图的 XML 数据中定义。
      
		
		
				图 3. WebCal
用户界面布局中的表格单元
		
		
		 
		
		
				使用 <xsl:apply-templates> 和 <xsl:for-each>
来处理 XML 元素
				
知道框架的 XSL 样式表如何访问 XML
数据这一点很重要。对于左边和顶部导航栏上的链接,可以使用 
        <xsl:apply-templates> 指令。请参阅 
        清单 17. 生成导航栏。
      
		
				<xsl:apply-templates> 将对 XML
节点应用模板。这意味着:XSL 处理器查找与 
        <xsl:apply-tempates> 的 "select"
属性定义的表达式相匹配的 XML
元素。对于边链接的情况,将匹配导航部分中 
        <global> 元素下的所有 
        <links> 
元素。如 
        清单 4
中星期视图中的 XML
代码所示,只有一个这样的元素。它可以有任意数量的作为其子代的 
        <link> 元素。可以使用 
        <xsl:with-param> 
来将任意数量的已命名的参数传递到匹配的模板。在本例中,它传递应该用于该链接的样式名称。
      
		本文不具体讨论样式、图像以及其它这些可以改进 Web
应用的外观和感觉的技术。看一下样本应用中的源代码,以了解如何在 XSL
中定义和使用 CSS 样式。
		在 Frame.xsl 中还定义了与 
        <links> 
元素匹配的模板。它使用 
        <xsl:for-each> 来迭代 
        <links> 的 
        <link> 
子代。在该循环中,它应用与当前 
        <link> 元素(由 
        "." 表示当前元素)相匹配的模板,并传递 
        "css" 参数。在每个链接之后,它插入一个 
        <br/> 元素,以便在左边,每行只有一个链接。
      
		
		
				清单 18. 链接
		
		
		
				
						
								| 
												
														<xsl:template match="links">
 <xsl:param name="css"/>
 
 <xsl:for-each select="link">
 <xsl:apply-templates select=".">
 <xsl:with-param name="css" select="$css"/>
 </xsl:apply-templates>
 <br/>
 </xsl:for-each>
 </xsl:template>
 
 
 | 
				
		
		
		可以有两种类型的链接,即内部链接和外部链接(如星期视图的 XML
中所示(请参阅 
        清单
4 ))。 
        <link> 元素的 "type"
属性定义类型。对于每一种链接类型,可以定义成自动与正确的链接节点相匹配的模板。XML
属性由 
        "@" 后面加上属性名引用。必须求出 
        <xsl:template> 的 "match"
属性中方括号中表达式的布儿值,以确定该模板是否与节点匹配。可以轻易生成外部链接,如下面的
"home" 链接所示:
      
		
		
				清单 19. XML 中的外部链接
		
		
		
				
						
								| 
												
														<link type="
 external" href="http://www.ibm.com/developer" text="Home"/>
 
 
 | 
				
		
		
		
		
		
				清单 20. 与外部链接匹配的 XSL 模板
		
		
		
				
						
								| 
												
														<xsl:template match="link
 [@type='external']">
 <xsl:param name="css"/>
 <a class="{$css}" href="{@href}"><xsl:value-of select="@text"/></a>
 </xsl:template>
 
 
 | 
				
		
		
		
		
		
				清单 21. 应用外部链接模板之后的 HTML
		
		
		
				
						
								| 
												
														<a class="..." href="http://www.ibm.com/developer">Home</a>
 
 | 
				
		
		
		然而,内部链接却链接到
WebCalServet,并可以有任意数量的、必须转换成 HTTP 参数的 
        <pass-param> 子代。这略为复杂,并要求在 XML
数据中进行一些导航。以下链接从 XML 数据的 
        <main-nav> 
部分获得。它将显示在顶部导航栏上,指向当前日期的天视图。( 
        <main-nav> 
元素由与 
        <links> 元素上所用模板类似的模板处理
。唯一区别是:该链接不由 
        <br/> 
元素分开,而是由竖线分开)。已经将该数据作为 HTTP 参数传递到
Servlet,并将其包括在 XML 的 
        <http-params> 
部分。
      
		
		
				清单 22. 从 XML 数据的主导航部分开始的外部链接
		
		
		
				
						
								| 
												
														<link type="
 internal" text="Day" href="ShowDay">
 
 <pass-param name="date"/>
 </link>
 
 
 | 
				
		
		
		
				清单 23.
				与内部链接匹配的 XSL 模板
		
		
		
				清单 24. 经过 XSL 处理之后的 HTML
		
		
		
				
						
								| 
												
														<a class="..." href="/webcal/WebCalServlet?action=ShowDay&date=...">Day</a>
 
 | 
				
		
		
		
				
						
								|   | 
												
														
																| 模板优先 
 
在 XSL 中,具有更详细匹配表达式的模板有优先。XSL 规范(请参阅 
          参考资料)定义了所有的优先规则。
         |  | 
				
		
		与 
        <link> 元素匹配的模板使用一个 
        <xsl:variable> ,后者使用 
        <xsl:apply-templates> ,将与 
        <pass-param> 
元素匹配的两个模板中的一个应用到当前 
        <link> 
元素的所有现有 
        <pass-param> 子代。然后,名为
"params" 的变量将包含那些模板应用的结果。第一个与 
        <pass-param> 匹配的模板与所有 
        <pass-param> 元素匹配,第二个只与那些具有
"value" 属性的 
        <pass-param> 元素匹配。在 XSL
中,规则越详细,其优先越高,所以,第二个模板与具有 "value" 属性的 
        <pass-param> 元素相匹配。
      
		有关 XSL 如何处理优先的提示,请参阅侧栏
        模板优先。
      
		
				复杂的 XPath 表达式
				
第一个模板使用 XPath 表达式从 
        <http-params> 
部分查询值。可以将表达式 
        "/webcal/http-params/param[@name=$pname]/@value" 
解释成:“获得 'name' 属性值为变量 'pname' 中所定义值的 
        <webcal> 元素之下的 
        <http-params> 元素的 
        <param> 
元素的名为 'value' 的属性值。有了该值之后,模板构建一个如 
        "&name=value" 这样的字符串。必须象 HTML 那样,用 
        & 实体将 & 符号转义。使用类似的表达式来从 
        <head> 部分中的 
        action HTTP
参数生成页面标题。(请参阅 
        清单
12)。
      
		第二个模板只使用表达式 
        @value 来引用当前元素的
"value" 属性,其中,当前指的是与模板匹配的 
        <pass-param> 元素。然后,模板再次从这个值和
"name" 属性生成一个字符串 
        "&name=value" 。
      
		
				
						
								|   | 
												
														
																| 变量和 XPath 表达式 
 
可以在 XSL 元素的属性中(例如,在 
          <xsl:template>的 "match" 属性中,或者在<xsl:value-of>、<xsl:apply-templates>等的 "select" 属性中)使用 XPath 表达式和对 XSL
变量的引用。也可以在非 XSL 元素的属性(如<a>的
"href"
属性)中使用它们。在这种情况下,需要将表达式放在花括号中:"{/webcal/now}"(
<now>元素的值)。 |  | 
				
		
		在与外部链接匹配的模板中,变量 
        "params" 包含从 
        <pass-param> 
元素生成的所有字符串的并置。然后将该并置包括在生成的 
        <a> element ( 
        href="...{$params}" 的
"href" 属性中)。
      
		
				将 XPath 表达式结果保存在 XSL 变量中的更多内容
				
框架的底部导航栏包含一些不固定的链接,它们依赖于当前视图。日历视图的底部导航栏包含一个导向用户可以创建新活动的页面的链接。创建活动之后,将该活动
作为到活动细节视图的链接显示在日历中。在那个视图中,子导航栏包含后退、编辑活动和删除活动的链接。但是,子导航栏的外观和感觉是一致的,所以,不在框
架样式表中创建它。 
		XML 数据的 
        <navigation> 部分以 
        <link> 
元素的形式包含有关每个视图的导航栏内容的信息。要访问该信息,"frame"
模板需要知道显示的是哪一个视图。XML 数据的 
        <http-params> 部分包含 
        action 
参数的值。清单 25 显示了如何保存当前操作以及到两个 XSL 变量的相应 
        <action> 元素的引用。然后,使用到 
        <action> 元素的引用来生成子导航栏。
      
		有关一些详细信息,请参阅
        变量和 XPath
表达式侧栏。
      
		
		
				清单 25. 将当前操作和对其元素的引用保存到 XML 变量
		
		
		
				
						
								| 
												
														<webcal>
 <http-params>
 <param name="action" value="ShowWeek"/>
 ...
 <navigation>
 ...
 <actions>
 ...
 <action name="ShowWeek">
 <link type="
 internal" text="New Event" href="ShowNewEvent">
 
 <pass-param name="date"/>
 </link>
 </sub-nav>
 </action>
 ...
 </webcal>
 
 
 | 
				
		
		
		
				清单 26.
				获得当前操作名称和相应 <action> 元素的
XSL
		
		生成子导航的部分将与 
        <sub-nav> 
元素相匹配的模板应用到与当前操作相对应的导航节点的 
        <sub-nav> 子代。对于
WebCal,该模板看起来与生成主导航栏的模板完全相同。
      
		
				主面板
				
主面板由名为 "main" 的、且对每个视图都不相同的模板生成。它从 HTML 
        <div> 
标记中调用,该标记限制主面板的尺寸,并且,如果需要的话,将显示滚动栏。将喜爱的尺寸作为命名的参数传递到
"main" 模板。
      
		在以下部分中,以星期视图为例,演示如何将 XML 应用数据变换成
HTML。
		
		
				图 4.
使用嵌套表格来布置星期视图
		
		
		 清单 27. 调用 "main" 模板
		
		
		
				清单 27. 调用 "main" 模板
		
		
		
				
						
								| 
												
														<xsl:template name="frame">
 ...
 <td class="main" align="left" valign="top"
 width="{$main_width}" height="{$main_height}">
 <div style="...
 width:{$main_width}px; height:{$main_height}px; overflow: auto; ...">
 <xsl:call-template name="main">
 
 <xsl:with-param name="width" select="$main_width"/>
 <xsl:with-param name="height" select="$main_height"/>
 </xsl:call-template>
 </div>
 </td>
 ...
 </xsl:tempate>
 
 
 | 
				
		
		
		在 "frame" 模板中计算变量 
        main_width 和 
        main_height 。宽度取决于标志图像的大小,而高度可以随意按像素大小设置。
      
		在 "main" 模板中,使用与框架中所用的类似的技巧来显示内容。首先,看一下 
        Show...Main.xsl 文件,以了解如何布置页面。虽然有几个特殊的 XSL 特性,但是,我将在下一节中解释。
      
		
				使用数字
				
在星期视图中,将星期分成两行。既然这只用于
HTML(您可能不想在其它设备中使用类似的显示),所以在 XSL
样式表中分成两行。您可能很想如清单 28 那样将 
        <xsl:for-each> 与 
        <xsl:if> 和 
        position() 函数一起使用。从 1
开始, 
        position() 返回 
        for-each 
迭代的顺序号。
      
		
		
				清单 28. 将星期分成两行,尝试 1
		
		
		
				
						
								| 
												
														<xsl:template name="main">
 ...
 <table>
 <tr>
 <!-- title --> ...
 </tr>
 <tr>
 <xsl:for-each select="webcal/week/day">
 <!-- do the day --> ...
 <xsl:if test="position()=4">
 <!-- next row -->
 
 </tr>
 <tr>
 </xsl:if>
 </xsl:for-each>
 </tr>
 </table>
 </xsl:tempate>
 
 
 | 
				
		
		
		这在 XSL 中不管用,因为 XSL
样式表必须有良好的格式。这意味着:每个打开的标记必须有一个相应的结束标记,而且不能部分嵌套元素(错误: 
        
<a><b></a></b> )。红色的 
        </tr> 标记本想用作 
        <xsl:for-each> 之前的 
        <tr> 
标记的结束标记。但是这可能意味着: 
        <xsl:for-each> 
和 
        <xsl:if> 元素与 
        <tr> 
元素部分嵌套。XML 规范强制 XSL 处理器将最后一个 
        </tr> 标记解释成 
        <xsl:for-each> 之前的 
        <tr> 
标记的结束标记,并将两个红色标记解释成错误(结束标记没有开始标记,以及开始标记没有结束标记)。
      
		对这个问题的解决方案是在 XML 中枚举天,然后使用匹配的表达式。在
WebCal 中,一星期中的每一天都有一个名为 "num" 、从 "1" 到
"7"的附加属性。XSL 支持用布尔表达式比较数字,因此,可以使用 
        <xsl:apply-templates> 在两个 
        <tr> 元素内实现两个循环,如清单 29 所示:
      
		
		
				清单 29. 将星期分成两行,尝试 2
		
		
		
				
						
								| 
												
														<xsl:template name="main">
 <table>
 <tr>
 <!-- title --> ...
 </tr>
 <tr valign="top">
 <xsl:apply-templates select="/webcal/week/day
 [@num<=4]"/>
 </tr>
 <tr valign="top">
 <xsl:apply-templates select="/webcal/week/day
 [@num>=5]"/>
 </tr>
 </table>
 </xsl:tempate>
 
 <xsl:tamplate match="day">
 <!-- this matches all day elements -->
 <!-- do the day here --> ...
 </xsl:template>
 
 
 | 
				
		
		
		
				<xsl:apply-templates> 的红色选择标准为 
        @num<=4 和 
        @num>=5 。在 XSL
中,应该始终转义 
        < 和 
        > 
字符。然后,由 "day" 模板处理(也就是匹配)所有的 day
元素,(无论在第一个循环还是第二个循环中)。
      
		另一个问题是为每一天生成标题。XML 的 day 元素有一个名为 "date"
的属性。其格式为 "yyyy-mm-dd"(ISO
标准化的格式)。那么,怎样才能将它变换成 "day mm-dd" 的格式呢?对于
"mm-dd" 部分,XSL 提供字符串操作。通过使用 
        substring-after() 、 
        substring-before() 
或二者的组合,可以分析字符串的语法。
      
		对于星期中天的名称,可以将 "num" 属性与 
        <xsl:choose> 语句一起使用,如清单 30 所示。
      
		
		
				清单 30. 
        <xsl:choose> 和字符串操作
      
		
		
		
				
						
								| 
												
														<xsl:template match="day">
 <table>
 <tr>
 <td class="maininverse"...>
 
 <xsl:choose>
 <xsl:when test="@num=1">Sun
 </xsl:when>
 <xsl:when test="@num=2">Mon</xsl:when>
 ...
 
 </xsl:choose>
 <xsl:value-of select="' '"/><!-- this generates a space -->
 <a class="maininverse" href="/webcal/WebCalServlet?action=ShowDay&date={@date}">
 <xsl:value-of select="
 substring-after(@date,'-')"/>
 </a>
 </td>
 </tr>
 <!-- process events here --> ...
 </table>
 </xsl:template>
 
 
 | 
				
		
		
		该字符串操作还从 
        <event> 元素的 "start" 和 "end" 属性抽取活动开始和结束时间。
      
		
				总结第 2 步
		
		第 2 步下了显示了如何将 XML 变换成
HTML。通过使用上面显示的技巧,还可以将 XML
变换成任何其它有良好形式的标记语言,以支持多种设备。有一个对所有支持的客户机格式都相同的中间格式,对把应用逻辑从显示逻辑分开会有所帮助。
		如需更详细的说明,
        XML Bible(请参阅
        参考资料 )会给您有关 XSL 和 XPath
的极好概述,包括数字转换、比较和字符串操作。
      
		
		
		
		
		
		
				
						第 3 步:使用表单
				
		
		
这一步解释如何在第 1 步和第 2 步所描述的 XSL
框架中使用表单。当然,前提是客户机要支持表单,但是几乎所有可用的基于
HTTP 的浏览器都支持表单。本节显示如何在 WebCal 中使用 XSL 来生成
HTML
表单来编辑和创建日历活动(例如,会议和约会)。示例还显示了该操作如何共享一个公共样式表。
		
				表单元素
				
与所有其它 HTML 元素类似,可以通过 XSL 模板生成表单元素,包括 
        <form> 元素,和说明到应用 Servlet 的哪个点的
"action"
属性。每个表单都应该有一个内部操作,以在用户提交表单之后处理表单。使用一个隐藏字段来调用特定的内部操作,如清单
21 所示:
      
		
		
				清单 31. 
        ShowEventFormMain.xsl - 用来显示 
        new event 或 
        edit event 表单
      
		
		
		
				
						
								| 
												
														<xsl:template name="main">
 ...
 <form method="post"
 action="/webcal/WebCalServlet">
 <!-- 'edit' is an XSL variable. It is set to 'true' if the action was 'ShowEditEvent'.-->
 <xsl:if test="
 $edit='true'">
 
 <input type="hidden" name="action" value="ProcessEditEvent"/>
 <input type="hidden" name="id" value="{/webcal/event/@id}"/>
 </xsl:if>
 <xsl:if test="
 $edit='false'">
 
 <input type="hidden" name="action" value="ProcessNewEvent"/>
 </xsl:if>
 ...
 <!-- build the form -->
 ...
 </form>
 </xsl:template>
 
 
 | 
				
		
		
		
				
						
								|   | 
												
														
																| 表单处理 
 
使用常见 Web
浏览器的“后退”和“重新装入”功能可能会导致某些表单处理混乱。要避免这种问题,WebCal
使用两种类型的操作: 
          ShowActions (请参阅 
          ShowAction.java)使用 
          第 2 步 中所描述的 XSL
变换显示视图。 
          ProcessActions (请参阅
ProcessAction.java)在提交之时被调用,并且不显示任何内容,但使用
Java APIHttpServletResponse.sendRedirect(String)将客户机浏览器 
          重定向 至"ShowAction"。 |  | 
				
		
		在表单中,可以使用客户机格式中的所有输入元素。WebCal
正好在登录、注册和显示/编辑活动表单上使用文本输入字段。输入字段的名称将是在提交表单时调用的 
        POST 操作的 HTTP 参数。Servlet 可以通过 Java API 
        HttpServletRequest.getParameter(String) 获得它们。
      
		
有关如何避免表单处理与浏览器“后退”和“重新装入”按钮冲突这个问题的技巧,请参阅 
        表单处理侧栏。
      
		
				清单 32.
				生成表单元素 显示了 XSL 模板如何生成 HTML
输入字段和提交按钮。
      
		
		
		
		
		
		
				
						结束语
				
		
		
本文显示了使用 XML 和 XSL 变换来创建可扩展的 Web
应用的一种方式。对应用数据使用一种中间格式(从而将应用逻辑与用户界面分开)以及不将链接和其它导航信息硬编码,可以实现高度可维护性。集成新功能性的新视图只需对导航
XML 进行一些更新,以及新建该视图的 "main"
样式表(每种支持的格式都需要一个)即可。
		
		
		
		
		
		
				
						参考资料 
				
		
		
		
		
		
		
		
		
				
						关于作者
				
		
		
				
						
								|   | 
						
								| 
												  |   | Martin Gerlach 目前在 IBM Almaden
研究中心的计算机科学部门进行毕业后的研究工作。他持有汉堡应用科学大学的计算机科学学位。可以通过
        mgerlac@almaden.ibm.com
与 Martin 联系。
       |