XSLT过滤XML重复pos属性值元素结果不符预期,求排查
问题:过滤XML中重复属性值的元素
需求
过滤所有具有重复pos属性值的<element>元素,仅保留每个属性值对应的一个元素。
原始XML
<?xml version="1.0" encoding="UTF-8"?> <elements> <element pos="1"/> <element pos="2"/> <element pos="2"/> <element pos="3"/> </elements>
当前使用的XSLT
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="2.0"> <xsl:output indent="yes"/> <xsl:template match="/elements/element"> <xsl:if test="@pos != preceding-sibling::element/@pos"> <xsl:copy-of select="."/> </xsl:if> </xsl:template> <xsl:template match="text() | @*"/> </xsl:stylesheet>
当前错误输出
<?xml version="1.0" encoding="UTF-8"?> <element pos="2"/> <element pos="2"/> <element pos="3"/>
预期输出
<?xml version="1.0" encoding="UTF-8"?> <element pos="1"/> <element pos="2"/> <element pos="3"/>
错误原因
你的<xsl:if>判断逻辑存在问题:@pos != preceding-sibling::element/@pos 是单个值与节点集合的比较,只要集合里存在任意一个元素不等于当前@pos,条件就会返回true:
- 第一个
<element pos="1">没有前置兄弟,preceding-sibling::element/@pos是空集合,@pos != 空集合返回false,导致该元素被过滤。 - 第三个
<element pos="2">的前置兄弟包含pos="1","2" != "1"为true,所以这个重复元素也被错误保留。
修正方案
方案1:修正判断条件
将<xsl:if>的条件改为检查当前元素是否是该属性值的第一个出现:
<xsl:if test="not(preceding-sibling::element[@pos = current()/@pos])">
这个条件的意思是:不存在任何前置兄弟元素的pos属性等于当前元素的pos属性,确保只保留每个属性值的第一个实例。
完整修正后的XSLT:
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="2.0"> <xsl:output indent="yes"/> <xsl:template match="/elements/element"> <xsl:if test="not(preceding-sibling::element[@pos = current()/@pos])"> <xsl:copy-of select="."/> </xsl:if> </xsl:template> <xsl:template match="text() | @*"/> </xsl:stylesheet>
方案2:使用XSLT 2.0分组功能(更简洁)
由于你使用的是XSLT 2.0,推荐用<xsl:for-each-group>按@pos分组,直接取每组的第一个元素,逻辑更清晰:
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="2.0"> <xsl:output indent="yes"/> <xsl:template match="/elements"> <xsl:for-each-group select="element" group-by="@pos"> <xsl:copy-of select="current-group()[1]"/> </xsl:for-each-group> </xsl:template> </xsl:stylesheet>
内容的提问来源于stack exchange,提问作者Michael
相关产品推荐
相关产品推荐

