流式处理XML时通过公共ID关联循环外元素的XSLT实现问题
流式XSLT处理嵌套XML的上下文关联实现方案
问题核心
你需要在流式处理多层嵌套XML时,将当前Employee节点下带updated标记的Earning,与同层级Changes/Plan的日期关联,且匹配范围必须限定在当前Employee上下文内,不能全局匹配。用xsl:key会报错(因流式模式下节点不完整存在于文档树),关闭流式后又会全局匹配,不符合需求。
为什么xsl:key不适用
流式XSLT的核心是节点一次性遍历、不完整加载到内存,而xsl:key依赖全局文档节点树构建索引,所以会触发“提供的节点必须位于根为文档节点的树中”的错误。即使关闭流式,xsl:key的索引也是全局范围,无法限定到单个Employee上下文,因此不适用。
基于XSLT 3.0 Map的流式实现方案
利用XSLT 3.0的map类型,在遍历每个Employee时,先把当前上下文内的Changes/Plan数据存入局部map,再处理同层级的Earning时通过公共ID关联,完美适配流式处理和上下文限定的需求。
实现步骤
- 遍历
Employee节点:流式处理每个Employee,确保节点按顺序读取。 - 构建局部Plan映射:在当前
Employee上下文内,遍历Changes/Plan,将公共ID作为键、日期作为值存入map。 - 关联Earning与日期:遍历当前
Employee下带updated标记的Earning,通过公共ID从map中取出对应日期并输出。
示例代码
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <!-- 开启流式处理模式 --> <xsl:mode streamable="yes"/> <!-- 根节点处理 --> <xsl:template match="/"> <EarningsOutput> <xsl:apply-templates select="Employee_Stack_Grouped/Employee_Stack/Employee"/> </EarningsOutput> </xsl:template> <!-- 单个Employee节点处理 --> <xsl:template match="Employee"> <!-- 构建当前Employee下的Plan ID-日期映射 --> <xsl:variable name="local-plan-map" as="map(xs:string, xs:date)"> <xsl:map> <!-- 假设Plan的公共ID是@plan-id,日期字段是@effective-date --> <xsl:for-each select="Changes/Plan"> <xsl:map-entry key="{@plan-id}" select="xs:date(@effective-date)"/> </xsl:for-each> </xsl:map> </xsl:variable> <!-- 筛选并输出带updated标记的Earning,关联对应Plan日期 --> <xsl:for-each select="Earnings/Earning[@updated='true']"> <!-- 假设Earning关联的Plan ID是@ref-plan-id --> <EarningRecord> <EarningID><xsl:value-of select="@id"/></EarningID> <AssociatedPlanDate><xsl:value-of select="$local-plan-map(@ref-plan-id)"/></AssociatedPlanDate> </EarningRecord> </xsl:for-each> </xsl:template> </xsl:stylesheet>
关键注意事项
- 处理器支持:必须使用支持XSLT 3.0流式处理的处理器(如Saxon-EE),开源版Saxon不支持流式特性。
- 节点访问顺序:流式处理中,
Employee下的Changes必须在Earning之前处理(或按XML结构自然顺序),不能反向访问已处理的节点。 - ID唯一性:确保当前
Employee内的Plan公共ID唯一,若存在重复,可将map的值改为xs:date*(日期序列),避免数据覆盖。 - 类型安全:建议明确map的类型(如
as="map(xs:string, xs:date)"),避免类型转换错误。
非流式场景的替代方案(可选)
如果不需要严格流式处理,也可以在Employee上下文内使用xsl:for-each结合current()限定匹配范围,比如:
<xsl:template match="Employee"> <xsl:for-each select="Earnings/Earning[@updated='true']"> <EarningRecord> <EarningID><xsl:value-of select="@id"/></EarningID> <AssociatedPlanDate><xsl:value-of select="../../Changes/Plan[@plan-id=current()/@ref-plan-id]/@effective-date"/></AssociatedPlanDate> </EarningRecord> </xsl:for-each> </xsl:template>
但这种方案会加载整个文档到内存,不适用于超大XML文件。
内容的提问来源于stack exchange,提问作者user2337472
相关产品推荐
相关产品推荐

