使用XSLT从XML文件中删除重复元素

4

以下是一个例子,我想在ID相同时删除重复的记录。 我正在从系统“A”和系统“B”中获取数据。 我希望系统“A”具有优先权(即,如果ID重复,则从系统“B”中删除该元素)。这是我的示例:

我得到了这个结果:

<HitList>
   <Hit System="A" ID="1"/>
   <Hit System="A" ID="2"/>
   <Hit System="A" ID="2"/>
   <Hit System="B" ID="1"/>
   <Hit System="B" ID="2"/>
   <Hit System="B" ID="3"/>
   <Hit System="B" ID="4"/>
</HitList>

I want this result (with the duplicates removed):

<HitList>
   <Hit System="A" ID="1"/>
   <Hit System="A" ID="2"/>
   <Hit System="B" ID="3"/>
   <Hit System="B" ID="4"/>
</HitList>

当前代码:

        <xsl:template match="/RetrievePersonSearchDataRequest">
                    <HitList>
                                <xsl:if test="string(RetrievePersonSearchDataRequest/SystemA/NamecheckResponse/@Status) = string(Succeeded)">
                                            <xsl:for-each select="SystemA/NamecheckResponse/BATCH/ITEMLIST/ITEM/VISQST/NCHITLIST/NCHIT">
                                                        <Hit>
                                                                    <xsl:attribute name="System"><xsl:text>A</xsl:text></xsl:attribute>
                                                                    <xsl:attribute name="PersonID"><xsl:value-of select="number(
                                                        REFUSAL/@UID)"/></xsl:attribute>
                                                        </Hit>
                                            </xsl:for-each>
                                </xsl:if>
                                <xsl:if test="string(RetrievePersonSearchDataRequest/SystemB/NamecheckResponse/@Status) = string(Succeeded)">
                                            <xsl:for-each select="SystemB/NamecheckResponse/PersonIDSearchResponse/personID">
                                                        <Hit>
                                                                    <xsl:attribute name="System"><xsl:text>B</xsl:text></xsl:attribute>
                                                                    <xsl:attribute name="PersonID"><xsl:value-of select="number(.)"/></xsl:attribute>
                                                        </Hit>
                                            </xsl:for-each>
                                </xsl:if>
                    </HitList>
        </xsl:template>


我在调用三个提议的解决方案时遇到了麻烦。目前,我正在检查系统'A'的“状态”属性并填充所有命中结果。然后,我会检查系统'B'的另一个“状态”属性,并为该系统填充所有命中结果。我应该在哪里调用模板?我应该使用apply-templates还是call-template? - jmac
我认为这里有一个误解。上面的HitList与我的输入无关。我的输入XML有许多层,我从两个不同的地方获取信息(换句话说,是两个不同的列表)。上面的HitList是我的当前输出,而具有四个项目的HitList是我期望的输出。我将编辑问题并展示我的XSLT,以产生上述结果。 - jmac
3个回答

3
这可以通过单个身份模板的重写来完成... XML输入
<HitList>
    <Hit System="A" ID="1"/>
    <Hit System="A" ID="2"/>
    <Hit System="A" ID="2"/>
    <Hit System="B" ID="1"/>
    <Hit System="B" ID="2"/>
    <Hit System="B" ID="3"/>
    <Hit System="B" ID="4"/>
</HitList>

XSLT 1.0

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="Hit[(@System='B' and @ID=../Hit[@System='A']/@ID) or 
        @ID = preceding-sibling::Hit[@System='A']/@ID]"/>

</xsl:stylesheet>

输出

<HitList>
   <Hit System="A" ID="1"/>
   <Hit System="A" ID="2"/>
   <Hit System="B" ID="3"/>
   <Hit System="B" ID="4"/>
</HitList>

3

XSLT 2.0解决方案:

<xsl:template match="HitList">
<HitList>
  <xsl:for-each-group select="*" group-by="@ID">
    <xsl:copy-of select="current-group()[1]"/>
  </xsl:for-each-group>
</HitList>
</xsl:template>

假设A始终在B之前。如果不是这种情况,您可以将内部指令替换为:
<xsl:copy-of select="(current-group()[@System='A'], current-group[@System='B'])[1]"/>

3

这里提供一种使用键的高效XSLT 1.0解决方案:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:key name="kHitById" match="Hit" use="@ID"/>
 <xsl:key name="kHitAById" match="Hit[@System = 'A']" use="@ID"/>

 <xsl:template match=
  "Hit[generate-id() = generate-id(key('kHitById',@ID)[1])]">

  <xsl:copy-of select=
  "key('kHitAById', @ID)[1]|current()[not(key('kHitAById', @ID))]"/>
 </xsl:template>
</xsl:stylesheet>

当将以下XML文档应用于此转换(故意从提供的文档中适应,通过在相应的A之前放置一些B使其更加有趣):

<HitList>
   <Hit System="B" ID="1"/>
   <Hit System="A" ID="1"/>
   <Hit System="B" ID="2"/>
   <Hit System="A" ID="2"/>
   <Hit System="A" ID="2"/>
   <Hit System="B" ID="3"/>
   <Hit System="B" ID="4"/>
</HitList>

需要得到的正确结果被生成:

<Hit System="A" ID="1"/>
<Hit System="A" ID="2"/>
<Hit System="B" ID="3"/>
<Hit System="B" ID="4"/>

使用Java 7 / Groovy时,我遇到了这个神秘的异常:错误:'在验证表达式'type of expression 'filter-expr(funcall(current, []), [pred(funcall(not, [funcall(key, [literal-expr(kHitAById), step("attribute", 15)])]))])'时出错。'主线程中的异常"javax.xml.transform.TransformerConfigurationException"。我甚至无法用英语翻译!我的感觉是current()[not(key('kHitAById', @ID))]不起作用,我不知道为什么。 - Istao
@Istao,请勿使用不兼容/有缺陷的XSLT处理器。此解决方案适用于任何符合标准的XSLT处理器--在我每天在计算机上使用的11种不同的XSLT处理器中都可以使用:MSXML(3,4,6),Saxon(6.5.4和9.x),Altova(XMLSpy)用于XSLT 1.0和2.0,XQSharp(XMLPrime)。 - Dimitre Novatchev
@Dimitre - 如果我将其用作辅助转换器,如何保留“HitList”根元素? - jmac
@jmac,只需添加:<xsl:template match =“ / *”> <xsl:copy> <xsl:apply-templates/> </ xsl:copy> </ xsl:template>。"次要转换"是什么意思? - Dimitre Novatchev
@jmac,有很多“两遍转换”或“多遍”转换的例子。您可以在xsl:variable的主体中捕获第一次转换的结果,然后对结果应用模板(通常在它们自己的模式中)。在XSLT 1.0中,必须先通过对其应用xxx:node-set()函数将RTF结果转换为常规树。请在SO上搜索此类问题/答案。例如,请参见:https://dev59.com/KE7Sa4cB1Zd3GeqP7uzI#3200026 - Dimitre Novatchev
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接