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》的书,其中列出了这样的内容。
哦,今天是女孩节,祝我的乖老婆和乖乖女儿节日快乐,身体健康啦!
既然这么高兴,就贴一张刚刚照的雯雯百日照好了。