You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

XSLT递归合并稀疏数据集元素:如何将c/b按优先级转为a?

问题描述

我有一个稀疏数据集,需要将三个相关元素a、b、c合并为一个元素,优先级顺序为a > b > c,若元素缺失或内容为空则跳过。

源数据

<?xml version="1.0" encoding="utf-8"?>
<root>
  <row>
    <a>foo1</a>
    <b>foo1.1</b>
    <c>foo1.1</c>
  </row>
  <row>
    <b>foo2.1</b>
    <c>foo2.2</c>
  </row>
  <row>
    <a></a>
    <b>foo3.2</b>
  </row>
  <row>
    <c>foo4.3</c>
  </row>
</root>

理想输出

<?xml version="1.0" encoding="UTF-8"?>
<root>
   <row>
      <a>foo1</a>
   </row>
   <row>
      <a>foo2.1</a>
   </row>
   <row>
      <a>foo3.2</a>
   </row>
   <row>
      <a>foo4.3</a> <!-- 当前样式表此处输出'b' -->
   </row>
</root>

可接受输出

文档中可保留冗余值,后续会被忽略

<?xml version="1.0" encoding="UTF-8"?>
<root>
   <row>
      <a>foo1</a>
      <b>foo1.1</b>
      <c>foo1.1</c>
   </row>
   <row>
      <a>foo2.1</a>
      <c>foo2.2</c>
   </row>
   <row>
      <a>foo3.2</a>
   </row>
   <row>
      <a>foo4.3</a>
   </row>
</root>

当前使用的样式表

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >

    <xsl:output method="xml" indent="yes"/>
    
    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
    </xsl:template>
    
    <!-- 移除空元素 -->
    <xsl:template match="b[normalize-space(text()) = '']" />
    <xsl:template match="a[normalize-space(text()) = '']" />

  <!-- 将c提升为b -->
  <xsl:template match="c[normalize-space(../b) = '']">
        <b>
            <xsl:apply-templates />
        </b>
    </xsl:template>
    

  <!-- 将b提升为a -->
    <xsl:template match="b[normalize-space(../a) = '']">
        <a>
            <xsl:apply-templates />
        </a>
    </xsl:template>
    
</xsl:stylesheet>

问题说明

原样式表无法将c直接提升为a,原因是XSLT的模板匹配基于原始节点树,新生成的b元素不会触发后续的模板递归。我原本以为函数式特性会让新元素触发模板,但实际并非如此,导致第四行输出的是b而非a。现寻求正确的实现方案,递归深度固定为3,也可以直接将c提升为a。

环境:Saxon-HE v10(支持XSLT 3.0)


解决方案

方案1:生成理想输出(仅保留优先级最高的a元素)

直接为每个row生成唯一的a元素,取值为a、b、c中第一个非空的内容:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:output method="xml" indent="yes" encoding="UTF-8"/>
    
    <!-- 复制基础节点结构 -->
    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
    </xsl:template>
    
    <!-- 处理row节点,生成优先级最高的a元素 -->
    <xsl:template match="row">
        <row>
            <a>
                <!-- 按a>b>c顺序取第一个非空值 -->
                <xsl:value-of select="(a[normalize-space()], b[normalize-space()], c[normalize-space()])[1]"/>
            </a>
        </row>
    </xsl:template>
    
    <!-- 移除原有的a/b/c元素 -->
    <xsl:template match="row/a | row/b | row/c"/>
    
</xsl:stylesheet>

方案2:生成可接受输出(保留冗余元素,确保a存在)

如果需要保留原有的非空b/c元素,同时确保每个row都有正确的a元素:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:output method="xml" indent="yes" encoding="UTF-8"/>
    
    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
    </xsl:template>
    
    <!-- 移除空的a/b/c元素 -->
    <xsl:template match="a[not(normalize-space())] | b[not(normalize-space())] | c[not(normalize-space())]"/>
    
    <!-- 为没有非空a的row添加a元素 -->
    <xsl:template match="row[not(a[normalize-space()])]">
        <row>
            <a>
                <!-- 取b或c的第一个非空值 -->
                <xsl:value-of select="(b[normalize-space()], c[normalize-space()])[1]"/>
            </a>
            <!-- 复制原有非a元素 -->
            <xsl:apply-templates select="node()[not(self::a)]"/>
        </row>
    </xsl:template>
    
</xsl:stylesheet>

方案3:修复原模板逻辑(直接处理c转a)

如果坚持用模板提升的思路,可以添加直接将c转为a的模板,跳过中间的b:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >

    <xsl:output method="xml" indent="yes" encoding="UTF-8"/>
    
    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
    </xsl:template>
    
    <!-- 移除空元素 -->
    <xsl:template match="a[not(normalize-space())] | b[not(normalize-space())] | c[not(normalize-space())]"/>

    <!-- c直接转为a:当a和b都为空时 -->
    <xsl:template match="c[not(../a[normalize-space()]) and not(../b[normalize-space()])]">
        <a>
            <xsl:apply-templates />
        </a>
    </xsl:template>
    
    <!-- b转为a:当a为空时 -->
    <xsl:template match="b[not(../a[normalize-space()])]">
        <a>
            <xsl:apply-templates />
        </a>
    </xsl:template>
    
    <!-- 移除已经被提升的b/c -->
    <xsl:template match="row[b[normalize-space()] and not(../a[normalize-space()])]/c"/>
    <xsl:template match="row[a[normalize-space()]]/b | row[a[normalize-space()]]/c"/>
    
</xsl:stylesheet>

内容的提问来源于Stack Exchange,提问作者David Peters

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 02:20:26