首页 文章

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

提问于
浏览
1

我有一个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演变如下:

  • //cd1|//cd2|//cd3 :获取所有cd1,cd2和cd3元素

  • (//cd1|//cd2|//cd3)[text()[1]] :从上面的列表中过滤所有具有非空文本值的元素,并返回以下元素 .

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

  • 现在我需要删除具有重复文本值的元素 . 我试过xpath为 (//cd1|//cd2|//cd3)[text()[1]][(preceding::cd1)|(preceding::cd2)|(preceding::cd3)] . 我希望实现的是检查值是否在上面的任何cd1或cd2或cd3之前 . 但是这会在 <cd2>D</cd2> 重复的地方返回 .

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

我怎样才能编写一个xpath来解决上面的问题(3)?

请注意我必须使用Xpath 1.0,因此不能使用distinct-values函数 . 此外,我需要获取匹配的节点列表,而不是xpath中的文本值,因为我必须使用AXIOM在这些节点上进行更多处理 .

更新:我正在使用此xpath来获取匹配的元素,然后使用AXIOM进行处理 . 因此,我需要编写一个单独的xpath表达式来一次性获取匹配元素(我无法在AXIOM中编写自定义流或使用XSLT) . 另外cd *也不能使用,因为实名不匹配 . 我在这里使用了一个样本 .

3 回答

  • 1

    试试这个:

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

    这实际上是非常简单的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:if s测试空节点并输出单个节点(如果有的话) .

  • 1

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

    <?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中反向引用 - 除非你在变量中有值(并且你需要将“当前”(外部)文本与“当前”(xpath中的节点)文本进行比较) .

    希望这可以帮助 .

相关问题