有一种方法给 XSLT 中变量进行赋值,是保持状态的那种

在 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 保留一切权利。本站原创及译作未经本人许可,不得用于商业用途及传统媒体。网络媒体可随意转载,或以此为基础进行演译,但务必以链接形式注明原始出处和作者信息,否则属于侵权行为。另对本站转载他处文章,俱有说明,如有侵权请联系本人,本人将会在第一时间删除侵权文章。及此说明,重之之重。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值