級別: 初級
Alan Knox, 軟件工程師, IBM
2001 年 6 月 01 日
XSLT
樣式表可用來動態(tài)地將 XML 變換成復雜的瀏覽器顯示標記 --
但如果顯示復雜,樣式表也復雜。因此需要一種能夠從簡單組件構建復雜樣式表的工具。既然 XSLT 本身就是 XML,因此可以用 XSLT 操縱
XSLT;樣式表也能編寫樣式表。本文演示如何從 XSLT 組件構建一個執(zhí)行某一特定運行時變換的 XSLT 樣式表。
另一篇 developerWorks 文章
讓你的XML適合所有尺寸的屏幕
討論了編寫與管理那些在很多顯示設備上顯示同一 XML
籃球統計信息的樣式表的問題。該解決方案涉及到編寫一個參數化的樣式表,該樣式表生成具有不同等級的數據內容的
HTML,然后使用 WebSphere Transcoding Publisher
將該樣式表產生的輸出轉換成適合于某個特定設備的代碼。雖然這對于很多情況來說是一種有效和簡單的解決方案,但卻喪失了對出現在用戶屏幕上的內容的某些控制。
如果要:
- 完全控制用戶所見的內容
- 調整應用程序的顯示,以便在每一種設備上實現最佳的可能的效果
- 利用設備的特定特性
那么,您必須解決生成無數復雜的樣式表的問題。本文演示了一個使用相同籃球
XML 數據的非折衷解決方案。
|
什么是樣式表?
樣式表描述文檔如何在屏幕上或打印中顯示。通過將樣式表附加到 Web
上的結構化文檔,您可以改變顯示,而無需犧牲設備獨立性或添加新的 HTML
標記。
什么是
XSL?
XSL 是可擴展樣式表語言,一種用于表達樣式表的語言。(CSS
是另一種功效較弱的樣式表語言。)
什么是
XSLT?
XSLT 代表 XSL 變換,或用于變換 XML 文檔的語言。
什么是
XML?
XML 代表用于基于 Web 的文檔的可擴展標記語言??蓪⑺闯?SGML
的縮減版。
|
|
樣式表的難點
樣式表主要有兩類問題:
-
復雜性 -
新的應用程序顯示往往需要新的樣式表。隨著標記語言和新設備的激增,所需的樣式表的數量也變得令人生畏。如果使用
XML/XSLT 以外的方法(例如 JavaServer Pages (JSP) 或 JavaBean
體系結構),仍然存在同樣程度的復雜性。然而,生成 JSP
的可使用的工具支持目前比支持 XSLT 樣式表的工具支持要發(fā)達得多。
-
技能 - 除了理解 XML
數據之外,樣式表作者還必須深入理解多種顯示設備的標記語言。另外,在瀏覽器中生成在外觀吸引人的顯示也需要設計技能。而這種技能組合并非輕易可以獲得。
解決方案
上述問題可以通過采用基于組件的方法開發(fā)樣式表來解決。XSLT
是一種聲明性語言,其中,構成樣式表的模板彼此獨立。XSLT
樣式表可以使用
import
和
include
機制由其它樣式表構成。只要適當留意,您可以分別開發(fā)一些獨立的
組件樣式表,這些組件樣式表可以一起構成將在運行期間應用于
XML
數據的
顯示樣式表。這些組件將大致分為三種類型,用于處理:
- 顯示動態(tài) XML 數據(在我的示例中是籃球數據)
- 可重用的顯示標記部分,例如按鈕欄
- 頁面殘余部分
下例演示了這種解決方案。
用戶界面設計
您可能希望您的 Web 應用程序看上去和感覺起來設計得更專業(yè)。布局美觀的
HTML 頁面往往意味著復雜標記、具有各種嵌入式 HTML
表的安排以及精確的格式化和間距命令。在本文中,我假設已有某個表示下面所示
頁面設計的靜態(tài)
HTML。任務是用一個顯示樣式表重新生成相同的外觀和感覺。
目標外觀和感覺
雖然這只是一個簡單的示例頁面,但它也有一些嵌入的 HTML
表。它將只是多個頁面中的一個,這些頁面顯示的數據來自生成 XML
形式的籃球統計信息的應用程序。我將用
籃球 XML 中的數據替換 "Dynamic
data"(動態(tài)數據),然后使用 "Navigation
bar"(導航欄)列出從當前頁開始的鏈接。
采取的步驟
要生成顯示樣式表,需要:
- 從設計 HTML 制作一個樣式表。
- 制作一個將籃球得分格式化成 HTML 的樣式表。
- 將二者合并。
- 制作一個生成“導航”欄的 XSLT“窗口小部件”。
- 具體化這個窗口小部件,以生成頁面的最終樣式表。
從設計 HTML 制作一個樣式表
我可以只制作一個 XSLT 樣式表,然后將 HTML
粘貼到適當位置。然而,當用戶界面設計更改時,我希望能相應地快速而輕松地更新顯示樣式表。一種比較好的解決方案是發(fā)明一個可以自動將設計
HTML 轉換成樣式表的過程。如果設計 HTML 和 XML
一樣是有效的(如果不是,可以將它清除),那我就可以用 XSLT
樣式表這樣做。
頁面骨架
首先,在設計 HTML 的
<HTML>
標記周圍添加
<layout>
元素。
帶標記的設計 HTML -
skeleton.xml
<layout match="game">
<html>
...
</html>
</layout>
|
這不一定使 HTML 變成樣式表,但確實可以讓我使用
<layout>
元素的屬性來存儲那些有助于推動此過程的信息。例如,
match="game"
指定了籃球 XML 中的某個元素,我將使用該元素來觸發(fā)此頁面 HTML
的生成。
<layout>
元素內的 HTML
本質上是目標頁面的 HTML 模板。因為
模板一詞在 XSLT
的上下文中有意義,所以,我稱它為目標頁面的 HTML
骨架。
預處理樣式表
下面是將
skeleton.xml
中的頁面骨架轉換成樣式表的樣式表。
pre-process.xsl - 第 1 版
<?xml version="1.0"?>
<xsl:style sheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:out="http://www.hursley.ibm.com/hsandt/et/compile">
<xsl:output method="xml" indent="yes"/>
<xsl:namespace-alias stylesheet-prefix="out" result-prefix="xsl"/>
<!-- template 1 -->
<xsl:template match="/layout">
<out:stylesheet version="1.0">
<out:output method="html" indent="yes"/>
<out:template match="{@match}">
<xsl:apply-templates/>
</out:template>
</out:stylesheet>
</xsl:template>
<!-- template 2 -->
<xsl:template match="*|text()">
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:apply-templates select="*|text()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
|
該樣式表有兩個模板:
- template 1 匹配頁面骨架中的
<layout>
元素,并輸出 XSLT 樣式表。
- template 2
匹配任何其它元素或文本節(jié)點,并只將它匹配的內容不加更改地復制到輸出中。
因為我希望此樣式表的輸出是一個樣式表,所以必須輸出 XSLT
語句。但是,XSLT 處理器如何區(qū)分要輸出的 XSLT 語句和要執(zhí)行的 XSLT
語句呢?答案是對要復制到輸出的 XSLT 語句使用另一個 XML
名稱空間。
在上例中,我定義了一個新的名稱空間
(
"http://www.hursley.ibm.com/hsandt/et/compile"
),并將其與名稱空間前綴
out
關聯。因為帶有
out
前綴的 XSLT
語句不在 XSLT (
"http://www.w3.org/1999/XSL/Transform"
)
名稱空間中,所以,XSLT
處理器將按我的需要輸出它們。但這還不夠。我希望輸出是正確的樣式表,XSLT
進程無需作任何進一步更改就可以執(zhí)行它。在示例的這一點處,我將獲得看起來類似于樣式表、而實際上卻不是的東西。
XSLT 用上面所用的
<xsl:namespace-alias>
元素提供了該問題的解決方案。這將獲取
"http://www.hursley.ibm.com/hsandt/et/compile"
(
out
) 名稱空間中的所有內容,并在輸出時將它們移到
"http://www.w3.org/1999/XSL/Transform"
(
xsl
) 名稱空間中。
將
pre-process.xsl
應用到頁面骨架的結果看起來類似于:
從
pre-process.xsl 得到的輸出 -
skeleton.xsl
<?xml version="1.0" encoding="UTF-8"?>
<out:stylesheet xmlns:out="http://www.w3.org/1999/XSL/Transform" version="1.0">
<out:output indent="yes" method="html"/>
<out:template match="game">
<html>
...
</html>
</out:template>
</out:stylesheet>
|
請注意,名稱空間前綴仍然是
out
。這并不重要。重要的是名稱空間前綴
out
現在被映射成 XSLT
名稱空間:
"http://www.w3.org/1999/XSL/Transform"
。(對
XML 語法分析器很重要的是名稱空間,而不是前綴。前綴是使 XML
便于閱讀的工具)。某些 XSLT
處理器可以在輸出時更改名稱空間前綴和名稱空間。本文示例所用的 Xalan
XSLT 處理器不更改。
現在,可以將
skeleton.xsl
樣式表應用到籃球數據。結果由與數據中
<game>
元素的匹配所觸發(fā),它與原始
設計 HTML 文件是同樣的
HTML。目前這還不是很有用,因為在我希望看到比賽得分的地方仍然顯示字符串
"Dynamic data"。我需要讓
skeleton.xsl
樣式表觸發(fā)那些在適當位置插入得分的模板。我通過找到寫著 "Dynamic
data" 的標記,然后將它替換成
<xsl:apply-templates>
調用來實現。
skeleton.xml
文件中的以下標記:
...
<table cellspacing="0" cellpadding="0" border="0" height="100%" width="100%">
<tbody>
<tr><td><img src="spacer.gif" width="1" height="50"/></td></tr>
<tr><td height="100%" align="center" valign="top">
<h2>Dynamic data</h2>
</td></tr>
</tbody>
</table>
...
|
變成
...
<table cellspacing="0" cellpadding="0" border="0" height="100%" width="100%">
<tbody>
<tr><td><img src="spacer.gif" width="1" height="50"/></td></tr>
<tr><td height="100%" align="center" valign="top">
<out:apply-templates select="http://recap"/>
</td></tr>
</tbody>
</table>
...
|
請注意
out
名稱空間前綴;它將導致 XSLT
處理器將元素作為被輸出,而不是被執(zhí)行的事物對待 -- 與上面對
pre-process.xsl
樣式表中的類似元素的描述完全一樣。正如您將在下一部分中看到的,我只對籃球數據中的
<recap>
元素感興趣,因此,我可以在這里指定選擇標準
select="http://recap"
。
現在,我所擁有的 XSLT 代碼能夠生成所需的靜態(tài) HTML
布局,并可以觸發(fā)那些插入動態(tài)數據的模板。接下來,我需要從籃球 XML
數據抽取數據的 XSLT 模板。
籃球數據樣式表
為簡便起見,我只生成一個在籃球數據的
<recap>
元素中找到的得分數據表,如下所示。
recap.xsl
<?xml version='1.0'?>
<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' version='1.0'>
<xsl:output method="HTML"/>
<xsl:template match="/">
<HTML>
<HEAD>
<TITLE>Box Score</TITLE>
</HEAD>
<BODY>
<h1>Testing</h1>
<xsl:apply-templates select="http://recap"/>
</BODY>
</HTML>
</xsl:template>
<xsl:template match="recap">
<TABLE border="0" cellpadding="2" cellspacing="1" width="80%">
<TR>
<TD BGCOLOR="#c80000"></TD>
<TD BGCOLOR="#c80000">
<CENTER><font color="#FFFFFF"><B>1st</B></font></CENTER>
</TD>
<TD BGCOLOR="#c80000">
<CENTER><font color="#FFFFFF"><B>2nd</B></font></CENTER>
</TD>
<TD BGCOLOR="#c80000">
<CENTER><font color="#FFFFFF"><B>Total</B></font></CENTER>
</TD>
</TR>
<xsl:apply-templates/>
</TABLE>
</xsl:template>
<xsl:template match="recapTeam">
<TR>
<TD BGCOLOR="#ffe0e0"><xsl:value-of select="team"/></TD>
<TD BGCOLOR="#ffe0e0">
<CENTER><xsl:value-of select="firstHalfScore"/></CENTER>
</TD>
<TD BGCOLOR="#ffe0e0">
<CENTER><xsl:value-of select="secondHalfScore"/></CENTER>
</TD>
<TD BGCOLOR="#ffe0e0">
<CENTER><xsl:value-of select="score"/></CENTER>
</TD>
</TR>
</xsl:template>
</xsl:stylesheet>
|
下面的樣式表挑選出 XML 片斷。
籃球 XML 中的
<recap> 元素
...
<recap>
<recapTeam>
<team>Georgia Tech</team>
<firstHalfScore> 21 </firstHalfScore>
<secondHalfScore> 39 </secondHalfScore>
<score> 60 </score>
</recapTeam>
<recapTeam>
<team>North Carolina State</team>
<firstHalfScore> 24 </firstHalfScore>
<secondHalfScore> 48 </secondHalfScore>
<score> 72 </score>
</recapTeam>
</recap>
...
|
上面的樣式表產生以下輸出:
Testing
|
1st
|
2nd
|
Total
|
Georgia Tech |
21
|
39
|
60
|
North Carolina State |
24
|
48
|
72
|
合并兩個樣式表
現在,我有兩個樣式表:
skeleton.xsl
和
recap.xsl
。我可以通過將這兩個樣式表導入到名為
runtime.xsl
的新樣式表非常方便地合并它們。結果如下所示。
runtime.xsl
<?xml version='1.0'?>
<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' version='1.0'>
<xsl:import href="recap.xsl"/>
<xsl:import href="skeleton.xsl"/>
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
</xsl:stylesheet>
|
可以在運行期間將該樣式表作為 Web 應用程序的一部分應用到籃球 XML
數據。樣式表中通過不同 XSLT 模板的流程如下所示:
- 首先匹配籃球數據的根節(jié)點。
runtime.xsl
中具有
match="/"
的模板具有較高的導入優(yōu)先權,并覆蓋
recap.xsl
中的等價模板。后者只是用于
recap.xsl
的獨立測試中觸發(fā)其它模板,因此,這就是我想要的行為。XSLT
處理器應用這些模板。
- 要匹配的下一個模板是
skeleton.xsl
中具有
match="game"
的模板。它將持續(xù)輸出
HTML,直到到達要輸出得分數據的
<xsl:apply-templates>
元素為止。
- 接下來要觸發(fā)的模板是
recap.xsl
中具有
match="recap"
的模板。它構建得分表標題行,然后應用模板來匹配用于構建表數據行的
<recapTeam>
元素。
- 構建得分表之后,就沒有更多要匹配的輸入 XML
節(jié)點了。此時,控制返回到
match="game"
模板,該模板輸出
HTML 的其余部分。
現在,我已經實現了具有目標視覺和感覺效果的動態(tài)數據部分。下一步是實現導航欄。
導航欄
假設在實際站點上,導航欄在站點上的所有頁面中出現,并具有特定于當前頁面的標題。在導航欄上有一些從該頁出發(fā)的固定鏈接,對于該站點的不同頁面,這些鏈接可能相同,也可能不同。這些鏈接只取決于當前頁面的內容。導航欄不依賴于籃球
XML 數據。
這一功能的實現與我剛剛對動態(tài)數據所執(zhí)行的操作類似:將
skeleton.xml
文件中 HTML 標記的 "Navigation bar" 部分替換成觸發(fā) XSLT 代碼的某個標記。然而這一次,XSLT 將不由籃球數據觸發(fā),并且我希望用于構建導航欄的所有 XSLT 代碼在預處理階段執(zhí)行,而不要保留到運行期間。
導航窗口小部件
首先,我將在
skeleton.xml
文件中希望出現導航欄的位置上放一個標記。我采用以下片斷:
<layout match="game">
...
<table cellspacing="0" cellpadding="0" border="0" height="100%" width="145">
<tr><td><img src="spacer.gif" width="100" height="100"/></td></tr>
<tr>
<td align="center" valign="top">
<font color="white"><h2>Navigation bar</h2></font>
</td>
</tr>
</table>
...
|
并將它編輯成:
<layout id="Recap" match="game"
xmlns:widget="http://www.hursley.ibm.com/widget" >
...
<table cellspacing="0" cellpadding="0" border="0" height="100%" width="145">
<tr><td><img src="spacer.gif" width="100" height="100"/></td></tr>
<tr>
<td align="center" valign="top">
<widget:navbar/>
</td>
</tr>
</table>
...
|
我在這里發(fā)明了
navbar
標記,并在新的
widget
名稱空間中指定它。通常,當發(fā)明標記來以某種新方式標記已有 XML
時,最好將新標記放在新的名稱空間中,以防止可能發(fā)生的名稱沖突。這樣做的更有利的原因是:可以通過在
XSLT 模板中指定
match="widget:*"
來以類的形式查找和操縱頁面骨架中的“窗口小部件”。
還要注意添加到
<layout>
元素的
id="Recap"
屬性。它標識這個頁面骨架應用于哪個特定頁面。此信息用于確定在那個頁面的導航欄上出現的內容。
接下來,我編寫了一個 XSLT 樣式表,該樣式表將由
<widget:navbar/>
標記觸發(fā),并將該標記替換成正確的 HTML,以形成所期望的頁面導航欄。這樣的樣式表看起來可能類似于下例。
navbar.xsl
<?xml version='1.0'?>
<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' version='1.0'
xmlns:widget="http://www.hursley.ibm.com/widget">
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="widget:navbar">
<!-- Get the "id" attribute from the page skeleton <layout> tag -->
<xsl:variable name="page" select="ancestor::layout[1]/@id" />
<!-- Navigation bar title: -->
<tr>
<td align="center">
<h2><font color="white"><xsl:value-of select="$page"/></font></h2>
</td>
</tr>
<!-- Navigation bar links: -->
<xsl:apply-templates select="document('')//widget:page[@id=$page]"/>
</xsl:template>
<xsl:template match="widget:page">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="widget:link">
<tr>
<td align="center">
<a href="{@to}"><xsl:value-of select="@title"/></a>
</td>
</tr>
</xsl:template>
<!--
A lookup table of navbar links out from each page:
-->
<widget:linkData>
<widget:page id="Recap">
<widget:link to="nowhere1" title="Team stats"/>
<widget:link to="nowhere2" title="Player stats"/>
<widget:link to="nowhere3" title="etc."/>
</widget:page>
<widget:page id="someOther">
<!-- and so on for the other pages in the application -->
</widget:page>
</widget:linkData>
</xsl:stylesheet>
|
此樣式表獲取在其中找到
<widget:navbar/>
標記的
<layout>
元素的
id
屬性值,并在查表期間使用該屬性作為鍵,以發(fā)現導航欄應該包含哪些源自該頁面的鏈接。然后,構造并返回導航欄的
HTML。
最后一步是更新
pre-process.xsl
樣式表,以利用 navbar 窗口小部件。我需要添加 XSLT 代碼,如下所示:
pre-process.xsl - 第 2 版
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:out="http://www.hursley.ibm.com/hsandt/et/compile"
xmlns:widget="http://www.hursley.ibm.com/widget">
<xsl:output method="xml" indent="yes"/>
<xsl:namespace-alias stylesheet-prefix="out" result-prefix="xsl"/>
<xsl:import href="navbar.xsl"/>
<xsl:template match="/layout">
<out:stylesheet version="1.0">
<out:output method="html" indent="yes"/>
<out:template match="{@match}">
<xsl:apply-templates/>
</out:template>
</out:stylesheet>
</xsl:template>
<xsl:template match="*|text()">
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:apply-templates select="*|text()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="widget:*">
<xsl:apply-imports/>
</xsl:template>
</xsl:stylesheet>
|
現在,可以對
skeleton.xml
文件重新運行
pre-process.xsl
樣式表,以創(chuàng)建新版的
skeleton.xsl
。
runtime.xsl
中的
<xsl:import>
獲得新的
skeleton.xsl
。對籃球數據應用
runtime.xsl
后產生:
最后的結果
結束語
runtime.xsl 樣式表由其它三個樣式表
skeleton.xsl
、
recap.xsl
和
navbar.xsl
得到。構建這三個樣式表所需的技能如下:
- 通過對
skeleton.xml
文件應用自動過程來構建
skeleton.xsl
。從靜態(tài) HTML 構建
skeleton.xml
文件非常簡單,無需任何特殊的 XML 或 XSLT
技能。HTML 需要 Web 設計技能。
-
recap.xsl
需要有關的 XSLT 和應用 XML
的知識。它輸出一些 HTML 標記,但不需要太多 HTML 技能,因為靜態(tài) HTML
可能包括一個樣本數據表(它將全面指定表單元顏色、邊框寬度等),而不是
"Dynamic data" 字符串。
-
navbar.xsl
需要 XSLT
知識,并且在更現實的示例中,可能還需要深入了解目標標記語言。
可以仔細地說明、開發(fā)和單獨地測試樣式表。生成動態(tài)數據的樣式表和生成顯示的樣式表之間是有區(qū)別的。雖然 XSLT 程序員們需要同時理解顯示標記和基本的商業(yè) XML,但對于單獨的 XSLT 程序員來說,無需同時理解二者。在本例的三個樣式表中,技術要求最高的是
navbar.xsl
,但不管怎樣,編寫可重用的 XSLT 組件正是您施展才華的所在。
更進一步
pre-process.xsl
樣式表獨立于它所處理的頁面骨架文件中的標記語言。在生成其它設備(例如,用于
WAP 電話的 WML
deck)的標記時,也可以應用完全相同的技術。創(chuàng)建新的窗口小部件只需編寫一個適當的
XSLT 樣式表,然后將一個
<import>
元素添加到
pre-process.xsl
即可。
隨著要支持的頁面骨架和窗口小部件數目的增加,管理和操縱所有這些組件的問題也隨之成倍增加,因此需要一些工具來管理這種復雜的局面。但是,因為每個組件都是 XML,所以,這些工具本身也可以使用 XML。可以將頁面骨架和窗口小部件存儲在關系數據庫中,并使用
document()
函數從 XSLT 訪問它們。在構建這種開發(fā)工具方面,具有 Java API 的 XSLT 處理器(例如 Xalan)將特別有用。
參考資料
關于作者
|
|
|
Alan Knox 是英國漢普郡 Hursley
Park 的一名 IBM 軟件工程師。Alan 于 1997 年從 Civil Service 跳槽到
IBM。從那時起,他參與了一些使現有 Web 應用程序適用于交互式電視和
WAP 電話的項目??梢酝ㄟ^
knox@uk.ibm.com 與 Alan
聯系
|