1. 問題
昨天遇到了這樣一個xml文檔,如下:bookObject.xml
<?xml version="1.0" encoding="ASCII"?>
<?xml-stylesheet type="text/xsl" href="Book2PubXSLT.xsl"?>
<xmi:XMI xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI">
<Book title="WxbBook">
<chapters nbPages="12" title="chapter1" author="author1"/>
<chapters nbPages="15" title="chapter2" author="author2"/>
<chapters nbPages="20" title="chapter3" author="author3"/>
</Book>
<Book title="nextBook">
<chapters nbPages="10" title="chapter1" author="aaa"/>
<chapters nbPages="20" title="chapter2" author="bbb"/>
<chapters nbPages="30" title="chapter3" author="ccc"/>
</Book>
</xmi:XMI>
希望能夠使用XSLT轉換為這樣的一個xml文檔(目的是為了做一個很簡單的模型轉換實例,當然這點和本文主題幾乎無關):publicationObject.xml
<?xml version="1.0" encoding="ASCII"?>
<xmi:XMI xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns="Publication">
<Publication title="nextBook" nbPages="60" authors="ccc and aaa and bbb"/>
<Publication title="WxbBook" nbPages="47" authors="author3 and author1 and author2"/>
</xmi:XMI>
分析這兩個文檔可以看出,希望的轉換規則如下:
Book節點有一個屬性title,它等于Publication的節點屬性title。
Book節點包含chapters子節點,每個子節點都有三個屬性。其中一個Book節點的所有chapters子節點的nbPages屬性相加等于Publication的nbPages屬性;
一個Book節點的所有chapters子節點的author相連,并在中間插入and則等于Publication的authors屬性。
由于之前我并沒有接觸過XSLT的變量、參數和xsl:call-template等概念,所以還是頗費了一點時間來解決此問題。
2. 問題的解決
值得注意的有幾點:
1. XSLT中的變量一次賦值后是不能改變的,所以這里的變量幾乎等于其它語言中的常量。
2. template有兩種,常見的是通過<xsl:template match="/">這樣的方式定義的,使用match屬性。調用的時候是使用<xsl:apply-templates select=""/>。另一種template類似與其它語言中的函數調用,使用<xsl:template name=”t_name”>來定義,調用的時候使用<xsl:call-template name=" t_name "/>來調用。
3. 在第二種template的定義中可以加入參數,類似于其它編程語言中的參數列表。在調用時也可以傳入具體的值做為實參。
4. 由于XSLT不包含for、while等循環語句。當簡單的for-each不能滿足要求時。則需要使用遞歸template調用來完成其它語言中的循環。從functional programming的理論中我們知道這二者是等價的。
下面是進行轉換的XSL文檔:Book2PubXSLT.xsl
<?xml version="1.0" encoding="ASCII"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore" xmlns:xmi="http://www.omg.org/XMI">
<xsl:template name="left">
<xsl:text disable-output-escaping="yes"><</xsl:text>
</xsl:template>
<xsl:template name="right">
<xsl:text disable-output-escaping="yes">></xsl:text>
</xsl:template>
<xsl:template match="/">
<xsl:call-template name="left"/>xmi:XMI xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns="Publication"<xsl:call-template name="right"/>
<xsl:apply-templates select="xmi:XMI"/>
<xsl:call-template name="left"/>/xmi:XMI<xsl:call-template name="right"/>
</xsl:template>
<xsl:template match="xmi:XMI">
<xsl:for-each select="Book">
<xsl:call-template name="left"/>Publication title="<xsl:value-of select="@title"/>"
nbPages ="<xsl:value-of select="sum(chapters/@nbPages)"/>"
authors ="
<xsl:call-template name="concatAuthor">
<xsl:with-param name="str" select="chapters/@author"/>
<xsl:with-param name="index" select="1"/>
<xsl:with-param name="nodenum" select="last()+1"/>
</xsl:call-template>"/<xsl:call-template name="right"/>
</xsl:for-each>
</xsl:template>
<xsl:template name="concatAuthor">
<xsl:param name="str"/>
<xsl:param name="index"/>
<xsl:param name="nodenum"/>
<xsl:choose>
<xsl:when test="$index != $nodenum">
<xsl:call-template name="concatAuthor">
<xsl:with-param name="str" select="concat($str,' and ',chapters[$index+1]/@author)"/>
<xsl:with-param name="index" select="$index + 1"/>
<xsl:with-param name="nodenum" select="$nodenum"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$str"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
從上面可以看出,解決問題最主要的一點就是使用了一個遞歸模板concatAuthor,它帶有三個參數str,index 和nodenum,分別表示連接的字符串、迭代子和chapters的節點數。通過這種方法,熟悉遞歸的人可以很快的寫出幾乎和javascript等價的種種數據操作。當然,由于變量不能賦值、參數只能以遞歸的方式來改動,還是很不方便的。
其優點也是明顯的,不依賴其它腳本語言完成了數據操作。可以在所有xsl引擎上面運行。
3. 小結
深入講解XSLT的中文文獻不多,大多數是入門級的。當我半猜半試的解決了這個問題后。發現了在O’Reilly有一本《XSLT》的書,其中列出了這樣的內容。
哦,今天是女孩節,祝我的乖老婆和乖乖女兒節日快樂,身體健康啦!
既然這么高興,就貼一張剛剛照的雯雯百日照好了。
