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 閱讀(391) 評論(0)  編輯  收藏 所屬分類: AJAX

          主站蜘蛛池模板: 龙胜| 留坝县| 曲麻莱县| 临朐县| 遂平县| 柘城县| 共和县| 东台市| 色达县| 石渠县| 香格里拉县| 灵山县| 海南省| 临高县| 奉新县| 四川省| 高安市| 偃师市| 双辽市| 长子县| 固原市| 察雅县| 永新县| 石楼县| 贺州市| 朝阳县| 太白县| 土默特右旗| 克山县| 谢通门县| 揭东县| 金门县| 大理市| 万全县| 建始县| 仁化县| 德钦县| 葵青区| 邯郸县| 龙江县| 河间市|