在 XSLT 中声明变量可以用 <xsl:variable> 和 <xsl:param>,它们的区别是可以通过 <xsl:param> 从外部向 XSLT 文件传参数,除此之外,在 xslt 内部使用时这两者的用法基本是一样的。下面只以 <xsl:variable> 为例子,例子中的 xsl:variable 替换成 xsl:param 也是能 run 的。
<xsl:variable> 的基本用法是:
1 2 3 4 5 |
<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 文件:
1 2 3 4 5 6 7 8 9 10 11 |
<?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 文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
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 文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<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> 元素 的注释: 一旦您设置了变量的值,就无法改变或修改该值!
比如我们有碰到下面应用场景的时候:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<!-- 标识 '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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
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 文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
<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 扩展函数
本文链接 https://yanbin.blog/xslt-assign-variables/, 来自 隔叶黄莺 Yanbin Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。
求文章里的英文字体!!不是代码里的。
浏览器 Inspect 一下就能看到,字体用的是 Comic Sans MS,宋体,Arial,所以英文优先用的是 Comic Sans MS 字体,这个字体好看不适于阅读。