XSLT(可扩展样式表转换语言)把XML转换成HTML、文本或其他XML时,核心思想是为不同类型的节点编写独立模板,再决定何时、以什么顺序去执行这些模板。所谓“调用模板”,就是指在转换过程中激活某个已定义的xsl:template规则。XSLT主要提供两条调用路径:一条是数据驱动的xsl:apply-templates,它会选中一组节点并自动寻找匹配模板;另一条是过程式的xsl:call-template,它像函数调用一样按名字直接执行模板。掌握二者的语义差异,才能避免嵌套混乱和重复输出。

一、用xsl:template定义可被调用的模板
在XSLT中,任何模板都必须先通过xsl:template元素声明。它有两个最常用的属性:match和name。match用来绑定某个XPath模式,当apply-templates选中的节点符合该模式时,模板被自动调用;name则给模板起一个标识符,供call-template按名调用。一个模板内部可以包含字面结果元素、XPath取值表达式以及其他调用指令。
下面示例定义了两个模板:第一个匹配book节点,输出书名;第二个按名字定义,用来统一输出价格格式。注意带name的模板本身不会自动执行,必须被其他模板显式或隐式调用。
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- 匹配型模板 -->
<xsl:template match="book">
<div>
<h3><xsl:value-of select="title"/></h3>
<xsl:call-template name="show-price">
<xsl:with-param name="val" select="price"/>
</xsl:call-template>
</div>
</xsl:template>
<!-- 命名模板,可被call-template调用 -->
<xsl:template name="show-price">
<xsl:param name="val" select="0"/>
<p>价格:<xsl:value-of select="$val"/>元</p>
</xsl:template>
</xsl:stylesheet>
上述代码中,book模板在处理每本书时,使用call-template指向show-price,并通过with-param传递当前节点的price值。xsl:param在命名模板中声明参数,select="0"表示若调用方未传值则使用默认值。这种结构让价格展示逻辑只写一次,达到复用效果。
二、xsl:apply-templates的数据驱动调用
xsl:apply-templates是XSLT中最具特色的调用方式。它不改变XML节点顺序,而是对当前上下文中的被选节点依次查找最佳匹配的模板。如果不写select属性,默认处理当前节点的所有子节点。这种方式适合树状结构的递归转换,例如把目录树转成嵌套列表。
以下例子展示如何用apply-templates遍历员工列表,并为每个employee节点匹配不同模板。根模板先选中所有employee,处理器会为每个节点寻找match="employee"的模板;若该模板内部再写apply-templates,就会继续向下层展开,形成自动递归。
<xsl:template match="/">
<ul>
<xsl:apply-templates select="company/employee"/>
</ul>
</xsl:template>
<xsl:template match="employee">
<li>
<xsl:value-of select="name"/>
<xsl:apply-templates select="skill"/>
</li>
</xsl:template>
<xsl:template match="skill">
<em>(擅长:<xsl:value-of select="."/>)</em>
</xsl:template>
使用apply-templates时,若多个模板都能匹配同一节点,XSLT会按优先级和导入顺序选择最具体的那一个。它的优势在于松耦合:新增节点类型只需补一个match模板,调用方无需修改。缺点是执行顺序依赖文档结构,不太适合需要严格步骤控制的场景。
三、xsl:call-template的显式调用
与apply-templates相反,call-template完全按编写者指定的名字调用,不会去做节点匹配。它更像传统编程语言里的函数调用,因此执行顺序清晰、可控。当某段逻辑与具体节点无关,或者需要在不同位置反复插入相同输出时,命名模板加call-template是最直接的选择。
下面的示例定义一个格式化日期的命名模板,在表头和详情两处分别调用,保证格式统一。由于call-template不依赖上下文节点,我们可以在任意模板中传入不同参数来复用它。
<xsl:template name="format-date">
<xsl:param name="d"/>
<xsl:value-of select="substring($d,1,4)"/>年
<xsl:value-of select="substring($d,6,2)"/>月
</xsl:template>
<xsl:template match="report">
<header>
<xsl:call-template name="format-date">
<xsl:with-param name="d" select="@gen"/>
</xsl:call-template>
</header>
<xsl:apply-templates select="item"/>
</xsl:template>
call-template的局限在于它把调用关系硬编码在样式表里,一旦命名模板改名,所有调用点都要同步修改。另外它不会自动遍历子节点,若需要递归处理树结构,仍要配合apply-templates或自身递归调用。
四、两种调用方式对比与选用建议
从设计意图看,apply-templates强调“数据说什么就处理什么”,适合把未知或易变的XML结构映射成目标格式;call-template强调“我要做什么”,适合固定流程与公共片段。实际项目中二者常常混用:用apply-templates搭建主干遍历,用call-template抽取公共展示块。
下表归纳了主要差异,方便在编码时快速判断:
| 对比维度 | xsl:apply-templates | xsl:call-template |
|---|---|---|
| 调用依据 | 节点匹配match模式 | 模板name名字 |
| 执行顺序 | 由源文档节点顺序决定 | 由样式表编写位置决定 |
| 参数传递 | 通过当前节点上下文隐式提供 | 用with-param显式传参 |
| 适用场景 | 树状递归、结构映射 | 公共片段、固定步骤 |
建议初学者先写几个match模板体会apply-templates的自动分发,再把重复代码抽成name模板用call-template调用。当转换规则超过两百行时,这种混合模式能显著降低维护成本。最后提醒,模板中若同时出现两种调用,注意避免无限递归,例如一个命名模板内部call自身却无退出条件,会导致转换器堆栈溢出。
XSLTtemplatexsl_apply_templates修改时间:2026-08-06 22:51:58