在 XSLT 中声明变量可以用 <xsl:variable> 和 <xsl:param>,它们的区别是可以通过 <xsl:param> 从外部向 XSLT 文件传参数,除此之外,在 xslt 内部使用时这两者的用法基本是一样的。下面只以 <xsl:variable> 为例子,例子中的 xsl:variable 替换成 xsl:param 也是能 run 的。
<xsl:variable> 的基本用法是:
<xsl:variable name="username" select="'Initial'"/> <!-- 不写 select 则默认为 '' -->
<xsl:variable name="username" select="'New Value'"/> <!-- 赋值 -->
<xsl:value-of select="$username"/> <!-- 显示变量值,变量名前加上 $ 符号 -->
上面三行同时写在一个 <xsl:template/> 里是没问题的,最后显示出新的值为 'New Value',但是跨了多次模板调用就有问题了,即使是把第一行写在最外层看起来像个全局变量。等会例子会揭示出现像来,先记住一点,在 XSLT 中每次应用模板就像是一次方法调用一样,那好,看个例子吧,有三部分组成,XML、XSLT、XsltTransformer 类,这三个文件都放在 cc/unmi/xslt 包中:
1. test.xml 文件:
<?xml version="1.0" encoding="UTF-8"?>
<users>
<user>
<name>Unmi</name>
<email>fantasia@sina.com</email>
</user>
<user>
<name>Any</name>
<email>master@unmi.cc</email>
</user>
</users>
2. XsltTransformer.java 文件:
package cc.unmi.xslt;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
/**
*
* @author Unmi
*
*/
public class XsltTransformer {
/**
* @param args
* @throws TransformerException
*/
public static void main(String[] args) throws TransformerException {
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Source xsltSource = new StreamSource(ClassLoader.getSystemResourceAsStream("cc/unmi/xslt/test_vars.xslt"));
Transformer transformer = transformerFactory.newTransformer(xsltSource);
Source xmlSource = new StreamSource(ClassLoader.getSystemResourceAsStream("cc/unmi/xslt/test.xml"));
Result outputResult = new StreamResult(System.out);
transformer.transform(xmlSource,outputResult);
}
}
3. test_vars.xml 文件:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output encoding="utf-8" method="text"/>
<!-- define global variable -->
<xsl:variable name="username" select="'Initial'"/>
<xsl:template match="/users" >
<xsl:apply-templates select="user"/>
<!-- show the last username after traversing user nodes -->
1, Last username: <xsl:value-of select="$username"/>
</xsl:template>
<xsl:template match="user">
<!-- show the last username -->
2, Last username: <xsl:value-of select="$username"/>
<!-- assign name to username -->
<xsl:variable name="username" select="name"/>
3, Current username: <xsl:value-of select="$username"/>
</xsl:template>
</xsl:stylesheet>
执行 XsltTransformer 的输出结果是:
2, Last username: Initial
3, Current username: Unmi
2, Last username: Initial
3, Current username: Any
1, Last username: Initial
分析结果想原因:从 <xsl:apply-templates select="user"/> 到应用模板 <xsl:template match="user">,相当于是一次方法调用,username 像是方法参数,在模板中可以改变 username 的值,但是再次进入时又是初始值,当然在全部遍历完之后,username 还是它的初始值。也就是,尽管 username 声明在最外层,看起来像个全局变量,但是却无法在模板中改变它的值。所以你不能像写通常的 Java 程序那样,设个全局标志,在某个方法中进行赋值,最后作为条件来判断。
看著名的 W3 Schooll 中关于 XSLT <xsl:variable> 元素 的注释: 一旦您设置了变量的值,就无法改变或修改该值!
比如我们有碰到下面应用场景的时候:
<!-- 标识 'Unmi' 是否存在来做些什么 -->
<xsl:variable name="exists" select="false()"/>
<xsl:template match="/users" >
<xsl:apply-templates select="user"/>
<xsl:if test="$exists">
<!-- do something here -->
</xsl:if>
</xsl:template>
<xsl:template match="user">
<xsl:if test="name='Unmi' and not($exists)">
<xsl:variable name="exists" select="true()"/>
</xsl:if>
</xsl:template>
显然,通过 <xsl:variable> 是做不到的,因为从模板中出来之后变量又变回去了。
要实现上面的功能,我们需要走别的路子了,试图找到一个真正全局的地方,能保存执行过程中的状态。想啊想啊,我们知道在 XSLT 中可以调用 JS/C#/Java 的方法,最简单的是调用静态方法,那么是否可以调用实例方法呢?如果可行的话,就可以在每次执行 XSLT 时绑定一个 Java 实例(JS/C# 的情况用到时再研究),以此 Java 实例作为数据容器,那么对它其中变量的改变就能够记录下来了。
这种方法确实是可行的,关于 XSLT 中调用 Java 方法的方式有好几种,具体步骤请参考:简单的 Xalan 扩展函数,这里直接用例子说明,如何用 Java 实例来保存 XSLT 所需的变量。
这里我们仍然使用前面的 test.xml 和 XsltTransformer.java 文件,但是需要修改 test_vars.xstl 文件,以及创建一个 StatusHolder 来存放 XSLT 中的变量值。
StatusHolder.java:
package cc.unmi.xslt;
/**
* a variabe container
* @author Unmi
*/
public class StatusHolder {
private String param1;
private String param2;
public String getParam1() {
return param1;
}
public void setParam1(String param1) {
this.param1 = param1;
}
public String getParam2() {
return param2;
}
public void setParam2(String param2) {
this.param2 = param2;
}
}
修改后的 test_vars.xslt 文件:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:holder="xalan://cc.unmi.xslt.StatusHolder"
extension-element-prefixes="holder">
<xsl:output encoding="utf-8" method="text"/>
<xsl:variable name="statusHolder" select="holder:new()"/>
<!-- define global variable -->
<xsl:variable name="username" select="holder:getParam1($statusHolder)"/>
<xsl:template match="/users" >
<xsl:apply-templates select="user"/>
<!-- show the last username after traversing user nodes -->
1, Last username: <xsl:value-of select="holder:getParam1($statusHolder)"/>
</xsl:template>
<xsl:template match="user">
<!-- show the last username -->
2, Last username: <xsl:value-of select="holder:getParam1($statusHolder)"/>
<!-- use xsl:value-of to call method setParam1() -->
<xsl:value-of select="holder:setParam1($statusHolder,name)"/>
3, Current username: <xsl:value-of select="holder:getParam1($statusHolder)"/>
</xsl:template>
</xsl:stylesheet>
再次执行 XsltTransformer,控制台输出是:
2, Last username:
3, Current username: Unmi
2, Last username: Unmi
3, Current username: Any
1, Last username: Any
我想这应该是我们所期待的结局。
关键性说明:
1. TransformerFactory.newInstance().newTransformer() 默认使用的就是 Xalan 的转换器
2. 调用 new() 来创建实例的,也可以 new('a', 'b') 来调用相应的有参构造函数
3. 调用实例方法时,第一个参数是实例本身,像调用谢方法一样
4. 可用 <xsl:value-of select="holder:setParam1($statusHolder,'abc')"/> 来调用 setParam1() 方法,尽管该方法的输出为 void。
5. 参数支持 4 种基本类型:数字(Java 双精度)、字符串、布尔和节点集(node-set);如 foo(2) 时,Xalan 倾向于调用方法的顺序是 foo(double)、foo(float)、foo(long) foo(int)、foo(short)、foo(char) 和 foo(byte)。
6. 当被调用方法产生异常时,直接引起 Xalan 的关闭,而且异常栈里有时很难发现问题,所以应该在被调用方法中处理好异常,如有异常时输出信息,不要往外抛。
7. 最后,可以设计一个更好的状态容器,或叫做变量容器,比如用 Map 来保存状态值,而不是一味的罗列 param1 ... paramN。
参考:1. 简单的 Xalan 扩展函数
本文链接 http://unmi.cc/xslt-assign-variables, 来自 隔叶黄莺 Unmi Blog
[版权声明]本站内文章,如未特别注明,均系原创或翻译之作,本人 Unmi 保留一切权利。本站原创及译作未经本人许可,不得用于商业用途及传统媒体。网络媒体可随意转载,或以此为基础进行演译,但务必以链接形式注明原始出处和作者信息,否则属于侵权行为。另对本站转载他处文章,俱有说明,如有侵权请联系本人,本人将会在第一时间删除侵权文章。及此说明,重之之重。