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
相关产品推荐
相关产品推荐

