Vincent.Chan‘s Blog

常用链接

统计

积分与排名

网站

最新评论

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

马晨 , 软件开发专家

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)和他联系。

posted on 2006-03-18 20:45 Vincent.Chen 阅读(371) 评论(0)  编辑  收藏 所属分类: AJAX


只有注册用户登录后才能发表评论。


网站导航: