xsl:number的核心用途是结构感知编号,即基于源文档树结构的路径计数器,而非简单递增;它按XPath节点关系动态计算同级或祖先层级的匹配节点位置,支持single/multiple层级、count过滤、from重置起点及value自定义值。

xsl:number 的核心用途:不是“自动编号”,而是“结构感知编号”
很多人以为 xsl:number 是个傻瓜式递增计数器(像 position() 那样),其实它本质是**基于源文档树结构的路径计数器**。它不依赖模板调用顺序,而是按 XPath 节点关系动态计算——比如“当前 section 在其父 chapter 下排第几个”,或“从最近的 book 开始累计所有 page”。用错场景时,常出现序号跳变、重置失效、跨层级混乱等问题。
最常用组合:count + level="single" 实现同级元素编号
这是新手上手最快、也最容易踩坑的用法。默认 level="single" 表示:只统计与当前节点同级、且匹配 count 表达式的节点中,位于它前面的个数(+1)。
-
count="itemA"→ 只数同级的itemA元素(忽略itemB) -
count="*"→ 数所有同级子元素(但要注意命名空间影响) - 若没写
count,默认等价于count="name()",即只数同名同级节点 - 不加
from时,计数起点是最近的祖先节点(可能跨多层),容易误触重置逻辑
输出类似:1. a、2. a、3. a……但若中间插了 ,序号不会断,因为 count="itemA" 明确过滤了它。
多级编号必须用 level="multiple",别硬套 for-each 嵌套
想实现 “2.3.1” 这类章节编号?不能靠在 xsl:for-each 里嵌套多个 xsl:number,而应一条 xsl:number 统一声明路径:
-
level="multiple"表示:沿祖先链向上,对每个匹配count的节点分别编号,再用format拼接 -
count="chapter | section | subsection"中的管道符是“或”关系,不是层级分隔符 -
format="1.1.1"的点数必须与count中的元素种类数一致(3 个类型 → 3 级) - 若某层祖先不匹配
count,该级编号为 0(如subsection直接在root下,中间无section,则输出可能是2.0.1)
value 和 from 是破局关键,但浏览器兼容性差
当你要格式化一个纯数字(比如数据库 ID)、或强制在某个节点重置计数时,value 和 from 很有用:
-
value="number(@id)"→ 把属性值转成编号并按format渲染(支持format="001"补零) -
from="book"→ 计数只在每个book内部独立进行,跨book自动重置 - ⚠️ 注意:
format="01"、"a"、"I"在旧版 IE 和部分 xml 工具链中不支持(Netscape 6 明确不支持) - 现代 XSLT 2.0/3.0 支持更好,但若目标环境是 IE 或老旧 java XSLT 处理器(如 Xalan),建议用
concat(format-number(...), ".")替代补零需求
真正难的从来不是写对语法,而是理解:编号逻辑绑定的是源 XML 结构,不是 XSLT 执行流。一旦数据结构调整(比如把 itemA 包进新容器),xsl:number 的行为可能完全改变——这点比任何属性都重要。