XPath - 从非重复列表中获取具有不同文本值的节点

XPath - Get nodes with distinct text values from a non repetitive list

我有一个 XML 如下:

<object>
    <codes>
        <cd1>A</cd1>
        <cd2>B</cd2>
        <cd3>C</cd3>
    </codes>
    <codes>
        <cd1>A</cd1>
        <cd2>D</cd2>
        <cd3></cd3>
    </codes>
    <codes>
        <cd1>E</cd1>
        <cd2>D</cd2>
        <cd3></cd3>
    </codes>
</object>

到目前为止,我的 XPath 演变如下:

  1. //cd1|//cd2|//cd3 :获取所有cd1、cd2和cd3元素

  2. (//cd1|//cd2|//cd3)[text()[1]] : 从上面的列表中过滤所有具有非空文本值的元素和 returns 下面的元素。

    <cd1>A</cd1> <cd2>B</cd2> <cd3>C</cd3> <cd1>A</cd1> <cd2>D</cd2> <cd1>E</cd1> <cd2>D</cd2>

  3. 现在我需要删除具有重复文本值的元素。我试过 xpath 作为 (//cd1|//cd2|//cd3)[text()[1]][(preceding::cd1)|(preceding::cd2)|(preceding::cd3)] 。我是什么 希望实现的是检查该值是否在上面的cd1或cd2或cd3中的任何一个之前。但是下面 <cd2>D</cd2> 重复的 returns。

    <cd2>B</cd2> <cd3>C</cd3> <cd1>A</cd1> <cd2>D</cd2> <cd1>E</cd1> <cd2>D</cd2>

如何编写 xpath 来解决上面的 (3)?

请注意,我必须使用 Xpath 1.0,因此不同值函数不是一个选项。此外,我需要获取匹配的节点列表,而不是来自 xpath 的文本值,因为我必须使用 AXIOM 对这些节点进行更多处理。

更新:我正在使用此 xpath 获取匹配元素,然后使用 AXIOM 对其进行处理。因此,我需要编写单个 xpath 表达式来一次性获取匹配元素(我不能在 AXIOM 或使用 XSLT 中编写自定义流)。也不能使用 cd*,因为真实姓名不匹配。我在这里使用了一个示例。

我发现的一种方法是使用以下模板:

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
    <xsl:for-each select="//*[starts-with(node-name(.), 'cd')]">
        <xsl:variable name="content"><xsl:value-of select="text()"/></xsl:variable>
        <xsl:if test="count(preceding::*[starts-with(node-name(.), 'cd') and text() = $content]) = 0 and text()">
               <xsl:copy-of select="."/> 
            </xsl:if>
     </xsl:for-each>
</xsl:template>
</xsl:stylesheet>

这会获取所有 cd* 元素,并获取每个元素的内容,用于计算前面有多少个内容相同 - 如果是 0 -> 则使用它。

据我所知,这是可以在 xslt-1 中完成的唯一方法(通过使用变量)。这是因为您不能在 xpath 中反向引用 - 除非您在变量中有值(并且您需要将 "current"(外部)文本与 "current"(xpath 中的节点)文本进行比较).

希望对您有所帮助。

这实际上是非常简单的 Muenchian 分组,只有三个键:

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="yes" method="xml" />
<xsl:key name="cd1" match="//cd1" use="text()" />
<xsl:key name="cd2" match="//cd2" use="text()" />
<xsl:key name="cd3" match="//cd3" use="text()" />

<xsl:template match="/">    
    <xsl:for-each select="/object/codes/cd1[./text() != '' and count(. | key('cd1', .)[1]) = 1]">
        <xsl:copy-of select="." />
    </xsl:for-each>

    <xsl:for-each select="/object/codes/cd2[./text() != '' and count(. | key('cd2', .)[1]) = 1]">
        <xsl:copy-of select="." />
    </xsl:for-each>
    <xsl:for-each select="/object/codes/cd3[./text() != '' and count(. | key('cd3', .)[1]) = 1]">
        <xsl:copy-of select="." />
    </xsl:for-each>

</xsl:template>
</xsl:stylesheet>

输出:

<?xml version="1.0" encoding="UTF-8"?>
<cd1>A</cd1>
<cd1>E</cd1>
<cd2>B</cd2>
<cd2>D</cd2>
<cd3>C</cd3>

或者,如果您想将它们分组而不考虑节点名称(即,如果 cd1cd2 都具有 A 作为文本值),则不太直接。

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="yes" method="xml" />
<xsl:key name="cd" match="//cd1 | //cd2 | //cd3" use="text()" />

<xsl:template match="/">    
    <xsl:for-each select="/object/codes/cd1[./text() != '' and count(. | key('cd', .)[1]) = 1] | /object/codes/cd2[./text() != '' and count(. | key('cd', .)[1]) = 1] | /object/codes/cd3[./text() != '' and count(. | key('cd', .)[1]) = 1]">
        <xsl:copy-of select="." />
    </xsl:for-each>


</xsl:template>
</xsl:stylesheet>

这会给出与上面相同的输出(但按照您当前模板输出的方式排序),但会消除 cd1cd2cd3 之间的重复相同的文本(只取第一个有它的文本)。

另请注意,我忽略了空节点 - 这可能是不需要的(并且可以通过从选择器中删除 ./text() != '' 轻松修复 - 但是,必须使用不同的方法来消除重复项如果需要空节点(可能只是一系列模板或 xsl:ifs,用于测试空节点并在这种情况下输出单个节点)。

试试这个:

//cd1[not(text() = preceding::cd1/text())][normalize-space()]|
//cd2[not(text() = preceding::cd2/text())][normalize-space()]|
//cd3[not(text() = preceding::cd3/text())][normalize-space()]