本节摘要:XSLT 是变换语言:用模板匹配节点,用 select 取出要输出的值。match 与 select 里填的就是 XPath。当前节点随
xsl:for-each和xsl:apply-templates移动,相对路径的含义跟着变。XSLT 1.0 配 XPath 1.0;XSLT 2.0/3.0 配对应 XPath。把书店变成一份 HTML 书单,是理解二者关系最快的办法。
阅读完本节,你应当能够:
/bookstore/book/title概念上的模板(不是要你保存文件,只看 select):
match 书店根 for-each select 路径 book value-of select 路径 title
select="book" 相对当前匹配的 bookstore,等于第 1 章的相对路径。select="title" 相对当前 book。你已经会的谓词可以直接写:book[number(price)>35]。
xsl:value-of 取字符串值,多节点时 1.0 只要第一个——又是 STRING 偏心问题。要每个书名一行,必须 for-each 或 apply-templates 到每个 title,而不是对 book/title 一次 value-of。
| 指令 | 对上下文 | 路径怎么写 |
|---|---|---|
| template match | 匹配到的节点成为当前 | match 常用绝对模式 |
| apply-templates select | 处理选中的孩子 | 相对当前 |
| for-each select | 循环内当前变成每一项 | 循环内路径相对项 |
| value-of select | 不改上下文,取字符串 | 相对当前 |
| copy-of select | 复制节点树 | 要整段 XML 时 |
$ 变量在 XSLT 里常见。select="$web/title" 从变量里的节点集再走。for-each 内 . 是当前书,$bookstore 仍指向外面绑的根。混用时写错 . 最常见,第 3 章埋过这个坑。
current() 出现在谓词里需要「回到 for-each 的当前项」时。谓词会改变上下文,外层那本书要用 current() 抓住。1.0 XPath 单独求值没有 current(),这是 XSLT 加的。
⚠️ 常见坑:match="book" 写了,select 又从
/bookstore/book绝对走一遍,等于丢掉当前节点。匹配已经把你放在书上,select 用相对title。
💡 关键直觉:XSLT 是「走到节点再决定输出什么」。走路的语言还是 XPath。变换策略是模板,不是更长的路径。

XSLT 1.0 ↔ XPath 1.0。浏览器内置变换、不少老 Java 栈停在这里。xsl:for-each 加 1.0 路径是你能立刻用上的组合。
XSLT 2.0 ↔ XPath 2.0:分组 xsl:for-each-group、正则、日期、用户函数。Saxon 是常见引擎。
XSLT 3.0 加流式、包、JSON 相关指令,配 XPath 3.1。
不要在 1.0 样式表里写 tokenize 或 current-date()。处理器报未知函数时,先看样式表 version 属性,再看路径。
模式 match 也是 XPath,但只允许特定形态(模式语法)。不能把任意 FLWOR 塞进 match。复杂过滤放在 select 或谓词。
都能产出新 XML。XSLT 以模板规则见长,适合「这类节点那样输出」的文档变换;XQuery 以 FLWOR 见长,适合查询和组装。团队已有样式表就用 XSLT,已有查询模块就用 XQuery。路径技能共用。
XSLT 输出 HTML 常设 output method。输入若是 XHTML 命名空间,match 仍要护照。输出是否带空间看 result 声明。两边别混成一套裸名幻想。
value-of 在 XSLT 1.0 取字符串值,多节点只要第一个。对 book/title 一次 value-of 只会输出 Everyday Italian。要每个书名一行,for-each 或 apply-templates 到每个 title。这和 Java STRING 返回类型是同一类偏心,换了层皮。copy-of 复制节点树,要整段 XML 时用它,不要用 value-of 再幻想能保住标签。
match 已经把你放在 book 上,select 再用绝对路径从书店根走一遍,等于丢掉当前节点。匹配到谁,相对路径就从谁起步。这是第 1 章上下文规则在模板里的再现。apply-templates 不写 select 时处理所有孩子,空白文本节点也会进模板,输出里多出空白。需要干净书单就显式 select 元素名。
模式 match 只允许特定形态,不能把任意 FLWOR 塞进去。复杂过滤放 select 或谓词。XSLT 1.0 配 XPath 1.0,浏览器内置变换和不少老 Java 栈停在这里。2.0 才有 for-each-group、正则、日期、用户函数。3.0 加流式和 JSON 相关指令。在 1.0 样式表写 tokenize 或 current-date,未知函数。先看样式表 version 属性。
current 出现在谓词里需要抓住 for-each 当前项时。谓词会改上下文。纯路径求值没有 current。美元变量在模板之间传递节点集,是 XSLT 的复用方式,对应第 3 章变量,但声明语法是 xsl:variable。输出 HTML 常设 output method。输入若是 XHTML 空间,match 仍要护照。输出是否带空间看 result 声明。两边别混成一套裸名幻想。
XSLT 与 XQuery 都能产出新 XML。模板规则适合「这类节点那样输出」;FLWOR 适合查询组装。团队已有样式表用 XSLT,已有查询模块用 XQuery。路径技能共用。不要为了「更现代」把能用的 1.0 样式表整表升级,除非你需要分组或正则这些 2.0 能力缺口。
模板匹配书店根,当前节点是 bookstore。for-each 的 select 写 book,相对当前,四本书循环四次。第一次当前变成 cooking 那本,value-of select title 输出 Everyday Italian。不要在循环里写 /bookstore/book/title,那会从根再取全部书名,每一轮都把三本书名串进同一处,清单会重复得像坏掉的打印机。这是上下文规则最血淋淋的现场。
value-of 若写在循环外、select 为 book/title,1.0 只要第一个书名。有人以为「select 写了 title 就会全部输出」,那是把 apply-templates 的行为安到了 value-of 上。要全部书名:循环,或 apply-templates select book/title 再给 title 写一个模板。copy-of select book 会把整本包括作者价格复制进输出,常常不是你要的书单。
价格过滤写在 for-each 的 select:book[number(price)>35]。循环次数变成两次。把过滤写在 title 模板的谓词里也行,但当前节点已经是 title 时,价格在叔叔节点上,路径要写 ../price。相对路径随当前节点变,这是第 1 章两点轴的应用。写绝对路径能跑,但丢掉了「我已经在这本书里」的信息,改版时更脆。
XSLT 1.0 样式表 version 写 1.0,却在 select 里写 tokenize,处理器报未知函数。先看 version。current 用在谓词里抓循环项,抄进纯 lxml 路径会未知。输出 method 设 html 时,空标签和命名空间的处理与 xml 不同,路径写在输入树上,不要拿输出 HTML 的形状去反推输入路径。
练习:先写一个只输出第一本书名的错误模板,再改成循环。对照两次输出。再把过滤加上,确认 Kick Start 与 Learning XML 都在,Everyday Italian 不在。这比阅读指令表更接近以后改样式表的工作。
匹配已经把你放在一本书上,选取再从根走一遍等于丢掉当前项,每一轮都会把全部书名重复打出来。取值指令在低版本多节点只要第一个,要每人一行必须循环或另写模板。复制指令才保住标签。循环改变当前项,价格在叔叔上要往上走一层。当前项函数是样式表补丁。版本属性写一点零就不要抄高版本函数。模式语法不能塞任意查询语句,复杂过滤放选取。输出方式改超文本时,空标签和空间处理与标记语言不同,路径仍写在输入树上。模板规则适合按类型输出,查询语句适合组装。能用的低版本样式表不要为了看起来现代整表升级,除非真有分组或正则缺口。
匹配和选取里填的就是路径。当前节点随循环和套用模板移动,相对走法的含义跟着变。低版本变换配低版本路径,浏览器内置变换和不少老栈停在这里。中版本才有分组、正则、日期、用户函数。高版本加流式和对象记法相关指令。不要在低版本样式表里写拆分词或当前日期。模式也是路径,但只允许特定形态,不能把任意查询语句塞进匹配。复杂过滤放选取或筛选。输出超文本常设输出方式,输入若在另一套空间,匹配仍要护照。两边别混成一套裸名幻想。路径技能共用,不要为了更现代把能用的低版本样式表整表升级,除非需要分组或正则这些能力缺口。
把书店变成一份清单,最容易写坏的一步是在循环外对书名做一次取值。低版本取值只肯吐第一本,清单看起来像只有烹饪那本上架。改成对每一本书循环,每一次当前项换人,再相对取书名,四行才会出现。过滤贵书写在循环的选取里,循环次数变成两次,便宜的两本根本不进循环,这比进了循环再判断更干净。当前项已经是书,价格就在孩子里,不要再从总馆绝对走一遍,否则每一轮都把全部贵书名重复打出来,像坏掉的打印机。复制指令才会把标签一起带走,取值只带走字。样式表版本写着低版本,却在选取里写高版本函数,处理器报未知,先看版本属性再改路径。当前项函数是样式表补丁,谓词会改上下文时才请它抓住循环项,抄进普通求值器会未知。输出方式改成超文本,空标签和空间处理与标记语言不同,但路径仍写在输入树上,拿输出形状反推输入路径会走偏。模板规则适合「这类节点那样输出」,查询语句适合组装,团队已有样式表就用变换,已有查询模块就用查询,路径技能共用。能用的低版本样式表不要为了看起来现代整表升级,除非真出现分组或正则这些能力缺口。匹配语法不能塞任意查询语句,复杂过滤放选取。输入落在另一套空间时匹配仍要护照,输出是否带空间看结果声明,两边别混成一套裸名幻想。
循环外取值和循环内相对取值,输出行数会差三倍。先写出错误模板再改对,比直接抄正确模板印象深。当前项是书时选书名用相对,选价格用孩子名,从总馆再绝对走一遍会重复输出。复制才保住标签,取值只带走字。低版本样式表不要抄拆分词和当前日期。模式不能塞任意查询。输出超文本时路径仍写在输入树上。分组和正则是中版本缺口,没有缺口就不要整表升级。匹配已经站在书上就用相对。空白文字孩子也会进不写选取的套用模板,书单里多出空白就显式选取元素名。当前项函数用在谓词里抓住循环项,抄进普通求值未知。输入空间和输出空间分开看,不要裸名幻想两头通。
循环外取值只吐第一本,循环内相对取值才有清单。匹配已经站在书上就不要再从总馆绝对走一遍,否则像坏掉的打印机。
空白文字孩子会进不写选取的套用模板,书单里多出空白时显式选取元素名。这不是变换引擎坏了,是你让任意孩子都有机会输出。
打印机式重复输出,十次有九次是循环里写了从总馆出发的绝对选取。当前项已经是书,相对取孩子即可,绝对走法会把全集每轮再打一遍。
下一节把 1.0、2.0、3.0、3.1 摊开:你该跟哪一版走,3.0 的箭头和 JSON 值不值得换引擎。