現在,許多應用程序利用 XML 來格式化業務數據,而且這些數據可能分布在不同的地方。在實際運用時,常需要將這些分布的XML數據用不同的視圖表示出來。而樣式表提供了業務數據與表示層的分離的方法,作為一種XML數據轉換的工具,通過它可以XML數據表示出來。一個樣式表可將分布的XML數據以某種視圖表現出來,而在本文所要解決的問題是如何產生多個樣式表作用於分布的XML數據來產生不同的視圖。本文介紹了基於 樣式表組件的XSLT組裝方法。它的核心是通過運用OO思想來構造一系列的XSLT的模板文件(XSLT文檔組件),通過一個XSLT的組裝器根據客戶的請求來動態組裝一個完整的XSLT文檔,並將之運用到分布的XML文檔上。從而,提供不同的用戶以不同的表示層。
樣本應用程序的概述
為了演示面向對象的XSLT編程,我將以產品目錄系統為例,在產品目錄系統中,不同的用戶可以獲得不同的表示層。在本例中,我們設想了三種用戶,一般用戶,高級用戶及內部用戶。各種用戶具有不同的表示視圖。例如,各等級的用戶的折扣率不同,使得產品的零售價格不一,而內部用戶可能需要得到產品供應商的信息。產品信息,供應商信息以及折扣率信息分別存儲在不同的XML文檔中。 列表1是產品列表信息, 列表2是供應商信息, 列表3是折扣率信息。系統根據用戶的類別動態的生成樣式表。系統的原理就是首先生成多個XSLT文檔組件,各個文檔組件負責部分轉換功能,就好比OOP中的各個類。同時,我們將生成一個XSLT文檔組裝器,它的核心實際上也是一個XSLT文檔,但是它能接收應用程序傳給它的參數並根據參數來將多個XSLT文檔組裝成一個完整的XSLT文檔。
樣式表組件的設計
XSLT實際是由一系列的匹配模板,命名模板,以及一組屬性,參數和變量組成。它作用於XML數據源時,可根據定義的模板執行某些操作。從某種程度上講,可視樣式表為OO中的類。因此,某些OO的思想可以引入到樣式表的設計中。下面分別介紹樣式表組件的設計以在設計中可以引入的OO思想。
基本樣式表組件設計
首先,我們設計基本的樣式表組件,根據系統的要求,我們設計了三個基本的樣式表,分別是產品樣式表,供應商樣式表及折扣率樣式表。 列表4是產品樣式表,它是整個產品目錄系統的主樣式表,以後對樣式表的擴展都是在它的基礎上進行。它就好比系統的基類。供應商樣式表功能是轉換供應商的信息,由於所需信息位於不同的XML文檔中,而系統的主樣式表是產品樣式表,產品列表XML文檔是主源文檔,所以,供應商樣式表所使用的供應商XML文檔作為輔助源文檔,必需通過document()來加載。清單1顯示了如果通過document函數來獲得所需的節點集。 列表5是供應商樣式表的完整源代碼。
清單1
<xsl:template name="suplIErShow">
<xsl:param name="sId"/>
<xsl:variable name="supplier" select="document('supplIErs.XML',.)//供應商[@id=$sId]"/>
……
</xsl:template>
折扣率樣式表的功能是根據用戶的等級來確定產品的零售價格。與供應商樣式表一樣,它也需要從折扣率XML文檔中獲得折扣率信息,並根據用戶的等級計算出價格。清單2顯示了零售價格的計算方法。 列表6是折扣率樣式的完整源代碼。
清單2
<xsl:template name="discount">
<xsl:param name="catalog"/>
<xsl:param name="price"/>
<xsl:param name="customerType"/>
<xsl:variable name="discountPrice">
<xsl:choose >
<xsl:when test="$customerType='一般用戶'">
<xsl:variable name="discountRate"
select="document('discounts.XML',.)//一般用戶/折扣[@類別=$catalog]/@折扣率"/>
<xsl:value-of select="$price*$discountRate"/>
</xsl:when>
……
</xsl:template>
現在,我們已經設計好了樣式表基本的組件了,下面就引入OO的概念到樣式表的設計中。
樣式表中的繼承
在XSLT有兩種重用樣式表的方法:導入(import)和包含(include)。這兩種方法的區別在於:包含只是簡單地將樣式表添加到主樣式表中包含它們的位置。包含的樣式表將插入主文檔中,就好象被包含的文件直接從數據流中讀取一樣。而導入與包含的不同之處在於只有在調用文檔中不具有相同的命名參數或模板時,才使用導入模板。包含方法雖然編譯度快,但它不能顯式重載模板。所以,在本樣例中,采用的導入方法。在本例中,內部用戶的產品樣式表在主產品樣式表上,擴展了供應商的信息。內部用戶產品樣式表就好比繼承了主產品樣式表,並擴展了自己的功能。清單3顯示了如向利用導入機制來擴展樣式表。 列表7是內部用戶產品樣式表的完整源代碼,它是通過XSLT文檔組裝器生成的。
清單3
<xsl:stylesheet XMLns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<!-- 導入樣式表-->
<xsl:import href="supplIEr.xslt"/>
……
<!-- 顯示重載模板 -->
<xsl:template match="供應商">
<xsl:apply-imports/>
</xsl:template>
</xsl:stylesheet>
樣式表中的重載
在樣式表中的<xsl:template>與<xsl:apply-temples>指令元素具有模式(mode)屬性。擁有mode屬性的<xsl:apply-temples>元素將尋找擁有同樣mode的一個<xsl:template>元素,並處理它的模板內容,忽略沒有匹配模式的模板規則。利用模板的模式屬性,我們可為相同的XML文檔元素定義多個模板,然後,在運行時,根據模式屬性決定到底調用那個模板。清單4顯示了模式屬性的使用方法。同樣,<xsl:apply-imports>指令元素也可用於樣式表的重載,由於導入的樣式表中的模板比被導入的樣式表中的相同模板的優先級低,甚至導入的模板具在比樣式表中原有的模板具有更高的優先級屬性(priority)也如此。而<xsl:apply-imports>元素具有類似OOP中在super()方法的相似的功能,它可以通知XSLT處理程序在被導入文檔中運用導入模板。同時,被調用的導入模板要匹配當前的環境節點。清單5顯示如何運用導入模板。在清單5中有一個問題是導入的模板(discount.xslt)具有mode屬性,而主模板卻沒有mode屬性,因此,在主模板並不能正確的導入相應的匹配模板。所以需要利用XSLT文檔組裝器來完成給主模板增加相應的mode屬性。在下一部分中將解決這個問題。
清單4
<xsl:template match="價格" mode="normal" >
<br/>
折扣價格:
<xsl:call-template name="discount">
<xsl:with-param name="catalog" select="../類別"/>
<xsl:with-param name="price" select="."/>
<xsl:with-param name="customerType" select="'一般用戶'"/>
</xsl:call-template>
</xsl:template>
<!-- 具有模式屬性的模板,它與<apply-templates>的模式屬性協同工作-->
<xsl:template match="價格" mode="advanced" >
<br/>
折扣價格:
<xsl:call-template name="discount">
<xsl:with-param name="catalog" select="../類別"/>
<xsl:with-param name="price" select="."/>
<xsl:with-param name="customerType" select="'高級用戶'"/>
</xsl:call-template>
</xsl:template>
<xsl:stylesheet version="1.0" XMLns:xsl="http://www.w3.org/1999/XSL/Transform">
<!--導入的樣式表組件-->
<xsl:import href="discount.xslt"/>
……
<!--原有的價格匹配模板-->
<xsl:template match="價格">
<br/>
價格: <xsl:value-of select="."/>
<!-- 導入的價格匹配模板-->
<xsl:apply-imports />
</xsl:template>
</xsl:stylesheet>
XSLT文檔組裝器的實現
XSLT文檔組裝器實際上也是一個樣式表,它根據要求組裝不同的樣式表。在XSLT組件設計中所提到的OO思想是通過文檔組裝器動態生成的。文檔組裝樣式表的思想就是根據客戶的請求通過樣式表導入來擴展系統的主樣式表(產品列表樣式表),同時,還要對主樣式表進行適當的修改來滿足客戶的需求。清單6顯示了如向根據請求的參數來組裝樣式表。
清單6
<xsl:param name="customerType" />
<!--傳入的請求參數-->
<xsl:template match="xsl:stylesheet " >
<xsl:copy >
<xsl:apply-templates select="@*"/>
<xsl:choose >
<!-根據請求參數來正確導入樣式表組件 -->
<xsl:when test="$customerType='一般用戶' or $customerType='高級用戶'">
<xsl:element name="xsl:import">
<xsl:attribute name="href" >
<xsl:text >discount.xslt</xsl:text>
</xsl:attribute>
</xsl:element>
</xsl:when>
<xsl:when test="$customerType='內部用戶'">
<xsl:element name="xsl:import">
<xsl:attribute name="href" >
<xsl:text >supplIEr.xslt</xsl:text>
</xsl:attribute>
</xsl:element>
</xsl:when>
</xsl:choose >
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="xsl:template">
……
<!-- 在匹配模板中運用導入的模板-'
<xsl:if test="$customerType='內部用戶'">
<xsl:if test="@match='供應商'">
<xsl:element name="xsl:apply-imports"/>
</xsl:if>
</xsl:if>
…….
</xsl:template>
清單6顯示了如何組裝樣式表文檔,但正如我們在上一部分討論的,我們必須解決模板模式(mode)屬性匹配的問題。清單7顯示了如何解決mode屬性匹配。在主樣式表模板中動態增加了模式(mode)屬性後,就能正確調用導入樣式表中的匹配模板。
清單7
<xsl:template match="xsl:template">
<xsl:copy>
<xsl:apply-templates select="@*"/>
<!-- 根據請求參數來增加mode屬性 -->
<xsl:if test="$customerType='一般用戶' or $customerType='高級用戶' ">
<xsl:if test="not(@match='/')">
<xsl:attribute name="mode">
<xsl:if test="$customerType='一般用戶'">
<xsl:text >normal</xsl:text>
</xsl:if>
<xsl:if test="$customerType='高級用戶'">
<xsl:text >advanced</xsl:text>
</xsl:if>
</xsl:attribute>
</xsl:if>
<!-- 調用導入樣式表中的匹配模板,由於主樣式表中模板增加了相應的模式(mode)屬性,它可以正確的調用導入樣式表的相應模板 -->
<xsl:if test="@match='價格'">
<xsl:element name="xsl:apply-imports"/>
</xsl:if>
</xsl:if>
<xsl:if test="$customerType='內部用戶'">
<xsl:if test="@match='供應商'">
<xsl:element name="xsl:apply-imports"/>
</xsl:if>
</xsl:if>
<xsl:apply-templates select="xsl:param"/>
<xsl:apply-templates select="node()[not(name()='xsl:param')]"/>
</xsl:copy>
</xsl:template>
至此,我們已完成了樣式表的設計,我們設計了一個簡單的來驗證系統。清單8是測試工具的源代碼。 列表7是內部用戶產品樣式表的完整源代碼, 列表8是一般用戶產品樣式表的完整源代碼, 列表9是高級用戶產品樣式表的完整源代碼。它們都是通過XSLT文檔組裝器生成的。
清單8
import Javax.XML.transform.*;
import Java.io.*;
import Javax.XML.transform.stream.*;
public class test{
public static void main(String[] args){
try{
StringWriter sw=new StringWriter();
StringWriter sw2=new StringWriter();
TransformerFactory tf=TransformerFactory.newInstance();
// XSLTCreator.xslt是樣式表組裝器樣式表//
Transformer trans=tf.newTransformer(new StreamSource(new File("XSLTCreator.xslt")));
//應用程序傳遞參數給組裝器樣式表,以生成所需的樣式表//
trans.setParameter("customerType","一般用戶");
//product_skeleton.XML是系統的主樣式表//
trans.transform(new StreamSource(new File("product_skeleton.XML")),new StreamResult(sw));
Transformer trans2=tf.newTransformer(new StreamSource(new StringReader(sw.toString())));
trans2.transform(new StreamSource(new File("products.XML")),new StreamResult(sw2));
FileOutputStream fo=new FileOutputStream(new File("test"));
System.out.println(sw2.toString());
fo.write(sw.toString().getBytes());
fo.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
實例
為了說明XSLT編程中的面向對象思想,特提供如下的實例以供參考。在實例是一個簡單的基於WEB的內容分布系統。它根據客戶的請求動態生成樣式表,進而提供相應的響應頁面。本實例只是簡單的實現了面向對象的XSLT編程的思想。系統的本身並沒有進行很好的功能的優化。圖一顯示了系統的時序圖
圖一 系統的時序圖
XSLTServlet類是用戶請求的集中控制點和服務的起始點,XSLTCreator類是XSLT組裝器的核心。清單9是XSLTCreator的實現的主要部分,它根據用戶請求動態調用或生成樣式表來生成相應的響應頁面。完整的代碼見 列表10.
清單9
public void xsltCreator(HttpServletRequest req,HttpServletResponse resp) throws IOException, SAXException{
StringWriter sw=new StringWriter();
StringWriter sw2=new StringWriter();
String userType;
try{
TransformerFactory tf=TransformerFactory.newInstance();
tf.setURIResolver(new URIResolver(){
public Source resolve(String href,String base) {
StringBuffer path = new StringBuffer(base_path);
path.append(File.separator).append(href);
File file = new File(path.toString());
if(file.exists()) return new StreamSource(file);
return null;
}
});
Transformer trans=
tf.newTransformer(new StreamSource
(locator.getCachFile("XSLTCreator.xslt")));
HttpSession session=req.getSession();
if(req.getParameter( "User")!=null)
session.setAttribute( "userType",req.getParameter("User" ));
userType=(String)session.getAttribute( "userType");
trans.setParameter("customerType",userType);
trans.transform(new StreamSource
(locator.getCachFile("product_skeleton.XML")),new StreamResult(sw));
Transformer trans2=
tf.newTransformer(new StreamSource
(locator.getCachFile("productFilter.xslt"))) ;
String type=(String)req.getParameter("productType");
trans2.setParameter("type",type);
trans2.transform(new StreamSource
(locator.getCachFile("products.XML")),new StreamResult(sw2) );
Transformer trans3=
tf.newTransformer(new StreamSource(
new StringReader(sw.toString())));
trans3.transform( new StreamSource(new StringReader(sw2.toString()))
new StreamResult(resp.getWriter()));
} catch (TransformerConfigurationException e) {
PrintWriter pw=resp.getWriter() ;
pw.println("<Html><body><h2>tansformer error<h2><pre>");
e.printStackTrace( pw);
pw.println("</pre></body></Html>" );
} catch (TransformerFactoryConfigurationError e) {
…
} catch (TransformerException e) {
…
}
}
系統的入口如下圖二所示
圖二 實例的系統入口頁面
系統根據用戶的請求來動態生成不同的頁面。圖三就是以高級用戶登錄後所顯示的內容。
圖三 動態生成的產品列表
總結
本文通過設計多個XSLT文檔組件來減少樣式表設計的復雜程度,同時,利用XSLT文檔組裝器來完成復雜,動態的樣式表的生成。