XSLT 3.0按eop标签分页时奇偶页方向切换异常求解
可行实现方案
核心问题是之前的实现把页面规则的绑定范围局限在了<article>标签上,没有覆盖跨层级的页面边界,XSLT 3.0/XPath 3.1原生支持跨层级按文档顺序查找前置节点,配合分组逻辑可以彻底解决这个问题,不需要逐节点绑定复杂的判断逻辑。
1. 最优方案:按<eop>做全局跨层级分页分组
不要用仅在<article>节点读取累加器的方案,直接用xsl:for-each-group的group-starting-with规则,从文档根节点遍历所有后代节点——不管<eop>嵌套在<p>、<span>还是任意深度的层级下,都以它作为页面分界点,把两个相邻<eop>之间的所有内容归为同一页,从页面容器的根节点就绑定对应方向和布局样式,从根本上避免样式沿用问题。
基础实现代码如下:
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs"> <xsl:output method="html" indent="yes"/> <xsl:template match="/"> <html> <head> <style> .page {padding: 2rem; min-height: 100vh; box-sizing: border-box;} .two-col {display: grid; grid-template-columns: 1fr 1fr; gap: 2rem;} </style> </head> <body> <!-- 全局遍历所有后代节点,按eop做跨层级分页,完全忽略eop的父级层级 --> <xsl:for-each-group select="descendant::node()" group-starting-with="eop"> <xsl:variable name="page-num" as="xs:integer" select=" if (self::eop) then count(preceding::eop) + 2 else 1 "/> <xsl:variable name="dir-val" as="xs:string" select=" if ($page-num mod 2 = 1) then 'ltr' else 'rtl' "/> <!-- 页面容器直接绑定方向和两栏布局,从页首就生效 --> <section class="page two-col" dir="{$dir-val}"> <!-- 跳过eop标记本身,渲染当前页所有内容 --> <xsl:apply-templates select="current-group()[not(self::eop)]"/> </section> </xsl:for-each-group> </body> </html> </xsl:template> <!-- 其余标题、段落、列表等元素的渲染模板按正常规则编写即可 --> </xsl:stylesheet>
2. 逐节点判断方案(不使用全量分组时使用)
如果业务场景不允许做全量分组,要让任意节点都能拿到文档顺序上最近的前置<eop>,直接用XPath的preceding轴即可——这个轴本身就是跨层级、按文档顺序返回前置节点的,对任意当前节点,下面的表达式就能拿到离它最近的前置<eop>,完全不受XML树层级限制:
(: 当前节点最近的前置eop,第一页没有前置eop时返回空序列 :) let $latest-eop := preceding::eop[1]
配合全局生效的累加器可以直接拿到当前节点所属页码,不需要把逻辑绑定在<article>模板里:
<!-- 全局累加器,只要遍历到eop就触发页码+1,不管eop在哪个层级 --> <xsl:accumulator name="current-page" as="xs:integer" initial-value="1"> <xsl:accumulator-rule match="eop" select="$value + 1"/> </xsl:accumulator> <xsl:mode use-accumulators="current-page"/>
任意节点都可以直接调用accumulator-before('current-page')拿到所属页码,再对应设置方向和布局样式即可。
3. 注意事项
如果<eop>出现在行内元素(比如<p>、<span>)内部,分组会把原本的父元素拆成跨页的两部分,只需要在对应块级元素的模板里补充分页拆分逻辑,遇到页面边界时主动闭合当前标签、在新页重开标签即可,避免生成不合法的HTML结构。
内容的提问来源于stack exchange,提问作者OrYo

