面向对象的 XSLT编程

面向对象的 XSLT编程
内容:
样本应用程序的概述
样式表组件的设计
XSLT文档组装器的实现
实例
总结
参考资料
关于作者
在 XML & Web 服务专区还有:
教学
工具与产品
所有的文章

谢强 (richard_xieq@yahoo.com.cn)
西安交通大学系统工程所的硕士研究生
2004 年 3 月

现在,许多应用程序利用 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> 

清单5

 <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文档组装器来完成复杂,动态的样式表的生成。

参考资料

关于作者

谢强,西安交通大学系统工程所的硕士研究生,主要从事Linux的集群技术的研究,并对XML,J2EE技术有着浓厚的兴趣,喜欢钻研技术。您可以通过richard_xieq@yahoo.com.cn和他联系。



posted on 2005-01-17 10:07  笨笨  阅读(915)  评论(0编辑  收藏  举报

导航