马晨 , 软件开发专家
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扩展的神秘面纱,至于怎么去发掘它的潜力,就留给富有创造力的读者去完成吧。
参考资料
- 关于saxon 的相关资料,请参阅Saxonic(http://www.saxonica.com)
- 关于xalan 的相关资料,请参阅 Apache.org (http://xml.apache.org/xalan-j)
- 关于在xalan中增加对于javascript的支持,请参阅Bean Scripting Framework (http://jakarta.apache.org/bsf/index.html) 和 Rhino (http://www.mozilla.org/rhino)
- 关于XSLT的相关资料,请参阅W3C.org(http://www.w3.org/Style/XSL/)
- 程序清单下载:samplecode.rar
关于作者
|
| | 马晨是上海浦东发展银行温州支行的软件开发专家。自 2000 年来已经在多个平台上开发过不同的应用。目前的工作方向主要侧重于基于 xml 和 javascript 的应用开发。在业余时间喜欢各种运动,尤其喜欢和朋友在周末打打羽毛球。你可以通过电子邮件(pearma@gmail.com)和他联系。 |