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

developerWorks 中国  >  XML  >

让你的 XSLT 如虎添翼 -- 浅谈 XSLT 扩展

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 初级

晨 马 (pearma@gmail.com), 软件开发专家

2006 年 1 月 16 日

其实 XSLT 能够做的事情很多,绝对超乎你的想象。除了格式转换,XSLT 还能完成一些看起来和格式转换完全无关的工作。比如说文件访问或者是数据库查询等等。而这一切都要归功于 XSLT 扩展(XSLT Extension)。

XSLT 是一种基于规则的格式转换语言。在许多人眼里,它的功能就是将一种格式的 xml 文件转换成另外一种格式的 xml 文件,仅此而已。不过,事实真是这样吗?

其实 XSLT 能够做的事情很多,绝对超乎你的想象。除了格式转换,XSLT 还能完成一些看起来和格式转换完全无关的工作。比如说文件访问或者是数据库查询等等。而这一切都要归功于 XSLT 扩展(XSLT Extension)。

根据 XSLT 1.0 的规范,符合标准的 XSLT 引擎应该支持 XSLT 扩展。也就是允许用户自定义 XSLT 的扩展元素(extension elements)和函数(extension functions)。今天我们所看到的主流 XSLT 引擎都按照国际标准,提供了自己的扩展方式。而开源软件中的 saxaon 和 xalan,在这方面走得更远。

Saxon 和 xalan 都是基于 java 开发的 XSLT 引擎,为它们编写扩展自然也基于 java。一般只要以下 3 步就可以完成一个扩展了。

1. 编写一个 java 类,在这个类里面设计好扩展功能,并以静态方法的形式提供给XSLT 引擎调用。

2. 在 XSLT 文件中,声明一个自定义的命名空间(namespace),该命名空间指出了类的位置

3. 在 XSLT 文件中,在适当的地方,调用扩展即可。

接下来让我们看个具体的例子。

foo_txt.xml 是一个待处理的 XML 文件,其中包含了<filename>和<content>两个元素。现在希望通过 XSLT 处理后,能将 <content> 的内容写入名称为 <filename> 的文件中。


图表 1:foo_txt.xml
<?xml version="1.0"?>
<document>
	<filename>foo.txt</filename>
	<content>Hello,World!</content>
</document>

由于这里牵涉到针对文件的操作,因此这个任务必须通过功能扩展来完成。让我们对照着前文提到的 3 步法,来看看 saxon 是怎么来做的。


图表 2:foo_txt_saxon.xsl
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
	xmlns:xsl="http://www.w3.org/1999/XSL/Transform"	
	xmlns:user="java:com.pear.utils.FileUtil">    
     
  	<xsl:template match="document">
		<xsl:value-of select="user:output(string(filename),string(content))"/>
  	</xsl:template>
</xsl:stylesheet>

图表 2

第一步,应该是提供用户编写的自定义java类。由于篇幅关系,这里不再给出源码,请看本文的参考部分,在那里提供了源码下载。

第二步,在XSLT文件开始,通过"xmlns:user='java:com.pear.utils.FileUtil'"命令,我们定义了一个命名空间。

最后,在处理XML节点的过程中,我们通过"user:output"成功地调用了用户自定义扩展函数。从而在XSLT中实现了文件写入功能。

看了saxon的做法之后,如果依样画葫芦的对xalan也来一遍,那么就太没意思了。幸亏xalan提供了一套更有趣的方法。

先直接看看xalan版本的处理文件吧。


图表 3:foo_txt_xalan.xsl
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
	xmlns:xsl="http://www.w3.org/1999/XSL/Transform"        
	xmlns:xalan="http://xml.apache.org/xalan"
	xmlns:user="http://www.mac.home">
    
	<xalan:component prefix="user" functions="output">
	<xalan:script lang="javascript">      
	
		function output(filename,content)
		{
			var a=new java.io.PrintWriter(filename);
			a.print(content);
			a.close();
			return "Finished!";
      		}
      		
    	</xalan:script>
  	</xalan:component>
      
  	<xsl:template match="document">
		<xsl:value-of select="user:output(string(filename),string(content))"/>
  	</xsl:template>
</xsl:stylesheet>

注意到它和saxon的版本有什么不同吗?对了,很明显,用户自定义的函数直接在处理文件中就实现了。而且是用javascript来完成的。那么好处在哪儿呢?答案很简单,就是开发人员可以抛开java的编译环境,直接设计自己的 XSLT 功能扩展了。

除了开发语言换成了javascript 外,其它流程和 saxon 的版本还是挺像的。所以就不再详细解释了。

不过值得一提的是,光有javascript还是不够的。在 xalan 版本中,细心的人一定会发现,真正起作用的部分,实际上是一个名字为 PrintWriter 的 java 类。也就是说 javascript 实际上只是一个流程控制者,正是依靠着 java sdk 强大的类库,XSLT 才能如虎添翼,去完成不可能的任务。

那么是不是只有自己写扩展才能解决问题呢?答案当然是否定的。saxon和xalan早就为我们预制了很多公用的扩展功能。我们只要采用拿来主义就可以了。下面我以数据库扩展为例,进一步向你展示XSLT扩展的魅力。

采用 saxon 引擎时,我们引入了几个新的 XSLT 扩展元素,例如 sql:connect,sql:query。通过这些扩展元素,我们可以连接数据库,并执行查询。

比如在下例中,我们可以利用 saxon 提供的 sql 扩展去访问 Informix 数据库。步骤如下:首先我们利用sql:connect建立和数据库的连接,连接使用的参数预先已经定义好了。

其次,我们用sql:query进行查询。查询的字段和查询的条件,均以参数的形式出现。

查询成功之后,利用标准的XSLT元素进行格式解析,并生成HTML格式的表格。

最后,通过sql:close关闭连接。至此整个处理结束。


foo_sql_query.xml
<?xml version="1.0"?>
<query>
	<table>FOO</table>
	<columns>username,birthdate</columns>
	<condition/>
</query>
foo_sql_saxon_query.xsl
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                version="1.0"
                xmlns:sql="java:/net.sf.saxon.sql.SQLElementFactory"
		xmlns:saxon="http://saxon.sf.net/"
                extension-element-prefixes="saxon sql">  
<xsl:param name="driver" select="'com.informix.jdbc.IfxDriver'"/>
<xsl:param name="database"
select="'jdbc:informix-sqli://192.168.0.1:5000/testDB:
INFORMIXSERVER=pcsnet;user=pcs;password=abc'"/>  
<xsl:param name="user" select="'pcs'"/>
<xsl:param name="password" select="'abc'"/>
<xsl:template match="/">
	<xsl:variable name="connection" as="java:java.sql.Connection"
	xmlns:java="http://saxon.sf.net/java-type">
	<sql:connect driver="{$driver}" database="{$database}" user="{$user}" 
	password="{$password}"/>
	</xsl:variable>
        <HTML>
        <HEAD>
		Table of <xsl:value-of select="/query/table"/>
        </HEAD>
        <BODY>
		<TABLE border="1">
			<xsl:variable name="dbtable">
				<sql:query connection="$connection" table="{/query/table}"
				column="{/query/columns}"/>
			</xsl:variable>
			<TR>
				<xsl:if test="string-length(/query/columns)>0">
	  			<xsl:call-template name="getcolumns">
					<xsl:with-param name="columns" select="/query/columns"/>
				</xsl:call-template>
				</xsl:if>
          		</TR>
			<xsl:apply-templates select="$dbtable/row"/>
          		<xsl:text>
</xsl:text>
        	</TABLE>
      	</BODY>
    	</HTML>
	<sql:close connection="$connection"/> 
</xsl:template>
  <xsl:template name="getcolumns">
	<xsl:param name="columns"/>
		<xsl:if test="string-length($columns)>0">
		<TH>
			<xsl:choose>
				<xsl:when test="contains($columns,',')">
					<xsl:value-of select="substring-before($columns,',')"/>
				</xsl:when>
				<xsl:otherwise>
					<xsl:value-of select="$columns"/>
				</xsl:otherwise>
			</xsl:choose>
		</TH>
		<xsl:call-template name="getcolumns">
			<xsl:with-param name="columns" select="substring-after($columns,',')"/>
		</xsl:call-template>
		</xsl:if>
  </xsl:template>
  <xsl:template match="row-set">
	<xsl:apply-templates select="row"/>
  </xsl:template>
  <xsl:template match="row">
        <TR>
          <xsl:apply-templates select="col"/>
        </TR>
  </xsl:template>
  <xsl:template match="col">
    	<TD>
      	 <xsl:value-of select="text()"/>
    	</TD>
  </xsl:template>
</xsl:stylesheet>

采用xalan引擎时,流程和saxon差不多,不过它还是使用扩展函数来完成数据连接和查询的功能。


foo_sql_xalan_query.xsl
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                version="1.0"
                xmlns:sql="org.apache.xalan.lib.sql.XConnection"
                extension-element-prefixes="sql">  
<xsl:param name="driver" select="'com.informix.jdbc.IfxDriver'"/>
<xsl:param name="database" select=
"'jdbc:informix-sqli://192.168.0.1:5000/testDB:INFORMIXSERVER=pcsnet;user=pcs;password=abc'"/>  
<xsl:variable name="query">
	select <xsl:value-of select="/query/columns"/> from <xsl:value-of select="/query/table"/>
</xsl:variable>
<xsl:template match="/">
  	<xsl:variable name="connection"  select="sql:new($driver,$database)"/>
        <HTML>
        <HEAD>
		Table of <xsl:value-of select="/query/table"/>
        </HEAD>
        <BODY>
		<TABLE border="1">
			<xsl:variable name="table" select='sql:query($connection, $query)'/>
			<TR>
		        	<xsl:for-each select="$table/sql/metadata/column-header">
            			<TH><xsl:value-of select="@column-label"/></TH>
          			</xsl:for-each>
          		</TR>
  			<xsl:apply-templates select="$table/sql/row-set"/>
          		<xsl:text>
</xsl:text>
        	</TABLE>
      	</BODY>
    	</HTML>
	<xsl:variable name="close" select="sql:close($connection)"/> 
</xsl:template>
  <xsl:template match="row-set">
	<xsl:apply-templates select="row"/>
  </xsl:template>
  <xsl:template match="row">
        <TR>
          <xsl:apply-templates select="col"/>
        </TR>
  </xsl:template>
  <xsl:template match="col">
    	<TD>
      	 <xsl:value-of select="text()"/>
    	</TD>
  </xsl:template>
</xsl:stylesheet>

saxon和xalan都是通过jdbc连接数据库的,所以读者如果手头没有informix,只要更换不同的数据库驱动,以及对应的数据库连接参数,就可以在自己的机器上检验效果了。

以上的这些案例只是揭开了XSLT扩展的神秘面纱,至于怎么去发掘它的潜力,就留给富有创造力的读者去完成吧。



参考资料

  1. 关于saxon 的相关资料,请参阅Saxonic(http://www.saxonica.com
  2. 关于xalan 的相关资料,请参阅 Apache.org (http://xml.apache.org/xalan-j)
  3. 关于在xalan中增加对于javascript的支持,请参阅Bean Scripting Framework (http://jakarta.apache.org/bsf/index.html) 和 Rhino (http://www.mozilla.org/rhino
  4. 关于XSLT的相关资料,请参阅W3C.org(http://www.w3.org/Style/XSL/)
  5. 程序清单下载:samplecode.rar


关于作者

马晨是上海浦东发展银行温州支行的软件开发专家。自 2000 年来已经在多个平台上开发过不同的应用。目前的工作方向主要侧重于基于 xml 和 javascript 的应用开发。在业余时间喜欢各种运动,尤其喜欢和朋友在周末打打羽毛球。你可以通过电子邮件(pearma@gmail.com)和他联系。




对本文的评价

太差! (1)
需提高 (2)
一般;尚可 (3)
好文章 (4)
真棒!(5)

建议?




回页首


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