在XML数据转换场景中,经常遇到同一份源文档需要根据不同业务参数输出结构各异的目标XML。XSLT作为声明式样式表语言,虽然不支持命令式循环赋值,但完全可以通过变量、条件指令和动态节点构造实现灵活的内容生成。理解这些机制,可以让转换逻辑集中维护,避免为每种输出写独立模板。

一、使用xsl:variable定义动态值
xsl:variable是XSLT中最基础的赋值手段。它不仅可以保存字符串或数字,还能保存节点集、布尔结果甚至一段临时生成的结构。变量在定义后只读,这种不可变性反而让动态生成过程更可控,不会因为后续逻辑意外篡改中间状态。
下面示例中,我们根据传入的参数docType决定根元素应该叫什么名字,并把这个结果存入变量,后续统一引用:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="docType" select="'order'"/>
<xsl:variable name="rootName">
<xsl:choose>
<xsl:when test="$docType = 'order'">OrderList</xsl:when>
<xsl:when test="$docType = 'refund'">RefundList</xsl:when>
<xsl:otherwise>GenericList</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:template match="/">
<xsl:element name="{$rootName}">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
上述写法的优势在于,根节点名称完全由变量驱动,如果未来新增一种单据类型,只需在xsl:choose中补充分支,而不必复制整个模板。需要注意的是,变量值如果是节点内容,引用时应使用select="$rootName"而非直接输出文本,以避免多余的文本节点。
在实际项目中,还可以把多个关联变量打包成局部计算块。例如在过滤订单时,先定义金额阈值变量,再定义是否包含作废单的布尔变量,二者共同决定后续遍历逻辑,这样主模板看起来非常干净。
二、通过xsl:element与xsl:attribute动态构造节点
当元素名或属性名不能在编写时固定,就必须使用xsl:element和xsl:attribute指令。它们允许把表达式结果作为名称,从而实现真正动态的XML生成。相比写死标签名,这种方式在对接多方接口时格外有用。
以下代码根据源数据的level字段动态生成不同优先级的节点,并附上由变量计算出的weight属性:
<xsl:template match="item">
<xsl:variable name="nodeName">
<xsl:choose>
<xsl:when test="@level = 'high'">Critical</xsl:when>
<xsl:otherwise>Normal</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="weight" select="@qty * 2"/>
<xsl:element name="{$nodeName}">
<xsl:attribute name="weight">
<xsl:value-of select="$weight"/>
</xsl:attribute>
<xsl:value-of select="title"/>
</xsl:element>
</xsl:template>
这里xsl:attribute必须出现在xsl:element的子内容之前,否则部分处理器会报属性位置错误。动态属性值通过变量weight计算,避免了在模板里重复写乘法表达式。
如果属性集合较多,可以改用xsl:attribute-set提升复用性。先定义一组带变量的属性集,再在xsl:element中用use-attribute-sets引用,既减少重复,也方便统一调整命名空间。
三、结合参数与条件模板过滤内容
动态生成不仅指改标签名,也包含按变量决定是否输出某些片段。借助xsl:param从外部传入开关,再用xsl:if或xsl:choose控制,就能在同一份样式表里服务多种场景。
假设调用方传入includeHistory参数,决定是否保留历史轨迹节点:
<xsl:param name="includeHistory" select="'no'"/>
<xsl:template match="order">
<order>
<id><xsl:value-of select="@id"/></id>
<xsl:if test="$includeHistory = 'yes'">
<history>
<xsl:copy-of select="log"/>
</history>
</xsl:if>
</order>
</xsl:template>
这种模式把变化点收敛到参数层,运维人员调整输出内容时无需触碰模板结构。但要注意,xsl:copy-of会连同源节点属性一起复制,若只需文本可用xsl:value-of替代,以防目标 schema 出现不兼容属性。
从架构角度看,把分支判断下沉到XSLT内部,比在调用程序里先筛选XML再套模板更省事,尤其当过滤规则随业务频繁变动时,样式表热更新即可生效,不必重新编译主应用。
四、常见误区与调试建议
初学者常误以为变量可以后续重新绑定,于是在模板里写多遍同名xsl:variable期待覆盖,结果只会得到作用域不同的新变量。正确做法是在需要重新计算处使用不同名字,或通过xsl:call-template传参拿到返回值。
另一个易错点是在xsl:element的name属性里直接写变量却不加花括号,导致元素名被当成字面量。记住名称类属性属于属性值模板,必须用{$var}语法才会解析。调试时可先用固定名称跑通结构,再替换为动态表达式,能快速定位是否是名称非法导致转换中断。
当输出不符合预期,建议把中间变量用xsl:comment临时打印到结果里,确认每步计算值。等逻辑稳定后再删除注释,既不影响生产结果,也保留排查痕迹。