【问题标题】:XPath - Get nodes with distinct text values from a non repetitive listXPath - 从非重复列表中获取具有不同文本值的节点
【发布时间】:2015-09-02 07:01:03
【问题描述】:

我有一个如下的 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]] : 从上面的列表中过滤所有非空文本值的元素,并返回下面的元素。

    <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 中的任何一个之前。但这会在&lt;cd2&gt;D&lt;/cd2&gt; 重复的位置下方返回。

    <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* ,因为真实姓名不匹配。我在这里使用了一个示例。

【问题讨论】:

  • 你能改变元素的命名吗?我建议将每个 cdX 元素更改为 cd 元素。
  • 我无法控制它。如果是这样,那就很简单了。

标签: xml xpath xpath-1.0


【解决方案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()]

【讨论】:

  • 我稍微修改了这个 xpath 以验证所有先前类别之间的唯一性,如下所示。 //cd1[not(text() = preceding::cd1/text())][not(text() = preceding::cd2/text())][not(text() = preceding::cd3/text())][normalize-space()]|//cd2[not(text() = preceding::cd1/text())][not(text() = preceding::cd2/text())][not(text() = preceding::cd3/text())][normalize-space()]|//cd3[not(text() = preceding::cd1/text())][not(text() = preceding::cd2/text())][not(text() = preceding::cd3/text())][normalize-space()]
【解决方案2】:

我找到的一种方法是使用以下模板:

<?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 内的节点)文本进行比较)。

希望这会有所帮助。

【讨论】:

  • 虽然这可以完成这项工作,但它的效率将远低于 Munechian 方法 - 仅供参考。
  • cd* 是不可能的,因为真实姓名是相当不同的。我在这里发布了一个示例。
【解决方案3】:

这实际上是非常简单的 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,用于测试空节点并在这种情况下输出单个节点(如果存在)。

【讨论】:

  • 我实际上正在使用这个 xpath 并与 axiom 一起使用。因此,是否有一个 xpath 可以用来选择元素?我无法控制公理逻辑。我只需要使用单个 xpath 表达式获取正确的元素并将其传递。我无法像在 xslt 中那样使用任何结构化流程。更新了问题
  • 我认为仅使用 XPath 是不可能的。也许 Axiom 有一些其他的功能呢?我不熟悉 Axiom。
猜你喜欢
  • 2018-02-21
  • 1970-01-01
  • 2013-04-23
  • 1970-01-01
  • 1970-01-01
  • 2012-12-30
  • 1970-01-01
  • 1970-01-01
  • 2014-01-14
相关资源
最近更新 更多