XSLT通用移除子元素重复的ITEM段方案咨询及代码排查
XSLT实现通用去重重复ITEM节点及现有代码排查
问题描述
需要编写XSLT删除BXYI下子节点值完全相同的重复ITEM段。当前ITEM包含6个字段,但后续可能新增空标签或带值字段,希望以通用方式实现去重逻辑。同时有两个疑问:
- 是否可以用
preceding-sibling函数查找并移除重复ITEM? - 是否必须定义所有可能的字段才能实现去重?
另外需要排查当前使用的XSLT代码未生效的原因。
输入示例
<?xml version="1.0" encoding="UTF-8"?> <D02X001> <DOC BEGIN="1"> <DC40 SEGMENT="1"> <NAM>DC40</NAM> </DC40> <BXYH SEGMENT="1"> <LDAT>date</LDAT> <UDAT>date1</UDAT> <BXYI SEGMENT="1"> <TNR>123453</TNR> <ORT>1000</ORT> <ITEM SEGMENT="1"> <N11>6789</N11> <AR>03</AR> <PQC>NU</PQC> <QTY>90909</QTY> <NUM/> <ASCD/> </ITEM> <ITEM SEGMENT="1"> <N11>6789</N11> <AR>03</AR> <PQC>NU</PQC> <QTY>3456</QTY> <NUM/> <ASCD/> </ITEM> <ITEM SEGMENT="1"> <N11>6789</N11> <AR>03</AR> <PQC>NU</PQC> <QTY>3456</QTY> <NUM/> <ASCD/> </ITEM> <ITEM SEGMENT="1"> <N11>6789</N11> <AR>03</AR> <PQC>NU</PQC> <QTY>3456</QTY> <NUM/> <ASCD/> </ITEM> </BXYI> </BXYH> </DOC> </D02X001>
输出示例
<?xml version="1.0" encoding="UTF-8"?> <D02X001> <DOC BEGIN="1"> <DC40 SEGMENT="1"> <NAM>DC40</NAM> </DC40> <BXYH SEGMENT="1"> <LDAT>date</LDAT> <UDAT>date1</UDAT> <BXYI SEGMENT="1"> <TNR>123453</TNR> <ORT>1000</ORT> <ITEM SEGMENT="1"> <N11>6789</N11> <AR>03</AR> <PQC>NU</PQC> <QTY>90909</QTY> <NUM/> <ASCD/> </ITEM> <ITEM SEGMENT="1"> <N11>6789</N11> <AR>03</AR> <PQC>NU</PQC> <QTY>3456</QTY> <NUM/> <ASCD/> </ITEM> </BXYI> </BXYH> </DOC> </D02X001>
现有XSLT代码
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:key name="uniqueItems" match="ITEM" use="concat(N11, '-', AR, '-', PQC, '-', QTY)"/> <xsl:output method="xml" indent="yes"/> <xsl:template match="/"> <xsl:copy-of select="."/> </xsl:template> <xsl:template match="BXYH"> <xsl:copy> <xsl:apply-templates select="*"/> <BXYI SEGMENT="1"> <xsl:for-each select="ITEM"> <xsl:if test="generate-id() = generate-id(key('uniqueItems', concat(N11, '-', AR, '-', PQC, '-', QTY))[1])"> <xsl:copy-of select="."/> </xsl:if> </xsl:for-each> </BXYI> </xsl:copy> </xsl:template> </xsl:stylesheet>
现有代码未生效原因排查
你的XSLT代码存在三个关键问题:
- 根模板优先级冲突:匹配
/的模板直接用xsl:copy-of复制整个文档,后续匹配BXYH的模板根本不会执行,因为根模板已经处理了所有内容。 - 节点路径错误:
BXYH模板中select="ITEM"是直接查找BXYH下的ITEM,但实际ITEM嵌套在BXYI内部,正确路径应为BXYI/ITEM。 - 错误重建节点:代码在
BXYH模板中重新生成了BXYI节点,会丢失原BXYI下的TNR、ORT等节点,同时导致BXYI重复。
通用去重方案
无需提前定义所有字段,以下两种方式均可实现通用去重:
方法1:使用xsl:key(推荐,性能更优)
利用xsl:key将ITEM的所有子节点内容拼接为键值,自动适配新增字段。XSLT 1.0实现如下:
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <!-- 以ITEM所有子节点的规范化文本拼接作为去重键 --> <xsl:key name="uniqueItemKey" match="ITEM" use="concat(*/normalize-space(), '|')"/> <xsl:output method="xml" indent="yes" encoding="UTF-8"/> <!-- 身份模板:默认复制所有节点和属性 --> <xsl:template match="@*|node()"> <xsl:copy> <xsl:apply-templates select="@*|node()"/> </xsl:copy> </xsl:template> <!-- 移除重复的ITEM:仅保留每个唯一ITEM的第一个实例 --> <xsl:template match="ITEM[generate-id() != generate-id(key('uniqueItemKey', concat(*/normalize-space(), '|'))[1])]"/> </xsl:stylesheet>
concat(*/normalize-space(), '|')会自动将ITEM下所有子节点的文本(去除多余空格后)拼接为键,新增字段无需修改代码。
方法2:使用preceding-sibling
通过preceding-sibling检查当前ITEM之前是否存在完全相同的节点,适合小文档场景:
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.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> <!-- 移除有完全相同前置兄弟的ITEM --> <xsl:template match="ITEM[preceding-sibling::ITEM[ count(*) = current()/count(*) and every $child in * satisfies exists(current()/*[name()=name($child) and normalize-space(.)=normalize-space($child)]) ]]"/> </xsl:stylesheet>
逻辑说明:如果当前ITEM的前置兄弟中存在一个ITEM,两者子节点数量相同,且每个子节点的名称和规范化文本完全一致,则移除当前ITEM。
关键说明
- 无需定义所有字段:两种方案都能自动适配
ITEM的新增字段,无需修改代码。 - 性能差异:
xsl:key方式预构建键值,处理大量重复节点时性能更优;preceding-sibling需逐个节点比较,大文档场景效率较低。
内容的提问来源于stack exchange,提问作者Padd T
相关产品推荐
相关产品推荐

