【问题标题】:XPath: Select following siblings until certain classXPath:选择以下兄弟姐妹,直到某个类
【发布时间】:2015-09-13 21:24:01
【问题描述】:

我有以下html sn-p:

<table>
    <tr>
        <td class="foo">a</td>
            <td class="bar">1</td>
            <td class="bar">2</td>
        <td class="foo">b</td>
            <td class="bar">3</td>
            <td class="bar">4</td>
            <td class="bar">5</td>
        <td class="foo">c</td>
            <td class="bar">6</td>
            <td class="bar">7</td>
    </tr>
</table>

我正在寻找从 .foo 元素开始并在下一个 .foo 元素之前选择所有后续 .bar 元素的 XPath 1.0 表达式。
例如:我从a 开始,只想选择12
或者我从b开始,想选择345

背景:我必须为这个方法找到一个 XPath 表达式(使用 Java 和 Selenium):

public List<WebElement> bar(WebElement foo) {
    return foo.findElements(By.xpath("./following-sibling::td[@class='bar']..."));
}

有没有办法解决这个问题?
该表达式应适用于所有 .foo 元素,而无需使用任何外部变量。

感谢您的帮助!

更新:对于这些特殊情况显然没有解决方案。但是,如果您的限制较少,则提供的表达式可以完美运行。

【问题讨论】:

  • 你想要的是你给出的第二个表达式匹配的内容?这意味着:给我第一个.bar 孩子及其所有后续.bar 兄弟姐妹,直到第一个.foo。是这样吗?
  • 是的,这正是我要找的。​​span>
  • @nwellnhof 我相信这不是重复的,因为那里的所有解决方案都依赖于节点的空文本、某个 id、XSLT 上下文和函数或 XPath 2.0。这些都不适用于这里。

标签: xpath


【解决方案1】:

好问题!

以下表达式将为您提供 1..23..56..7,具体取决于输入 X + 1,其中 X 是您想要的集合(2 给出 1-2,3 给出 3-.5 ETC)。在示例中,我选择了第三组,因此它有[4]

/table/tr[1]
  /td[not(@class = 'foo')]
  [
     generate-id(../td[@class='foo'][4]) 
     = generate-id(
         preceding-sibling::td[@class='foo'][1]
        /following-sibling::td[@class='foo'][1])
  ]

这个表达式 (imnsho) 的美妙之处在于您可以按给定的集合进行索引(而不是按相对位置进行索引),并且只有一个地方需要更新表达式。如果你想要第六组,只需输入[7]

此表达式适用于您有兄弟姐妹的任何情况,您需要在具有相同要求的任意两个节点之间使用兄弟姐妹 (@class = 'foo')。我会更新解释。

将表达式中的 [4] 替换为您需要的任何集合,再加上 1。在 oXygen 中,上面的表达式显示了以下选择:

说明

/table/tr[1]

选择第一个tr

/td[not(@class = 'foo')]

选择任何td 而不是foo

generate-id(../td[@class='foo'][4])

获取第 x 个foo 的标识,在这种情况下,这选择空,并返回空。在所有其他情况下,它将返回我们感兴趣的下一个foo 的身份

generate-id(
    preceding-sibling::td[@class='foo'][1]
    /following-sibling::td[@class='foo'][1])

获取 first 之前的 foo 的标识(从任何非 foo 元素倒数)以及从那里,first 之后的 foo。在节点7 的情况下,这将返回虚无的身份,在我们的示例[4] 中产生true。在节点3的情况下,这会导致c,不等于虚无,导致false

如果示例的值为[2],则最后一位将为节点12 返回节点b,这等于../td[@class='foo'][2] 的标识,返回true。对于节点47 等,这将返回false

更新,替代#1

我们可以用 count-preceding-sibling 函数替换 generate-id 函数。由于两个 foo 节点之前的兄弟姐妹的数量对于每个节点都不同,因此这可以作为 generate-id 的替代方案。

不过,现在它开始变得和 GSerg 的回答一样实用:

/table/tr[1]
  /td[not(@class = 'foo')]
  [
     count(../td[@class='foo'][4]/preceding-sibling::*) 
     = count(
         preceding-sibling::td[@class='foo'][1]
        /following-sibling::td[@class='foo'][1]/preceding-sibling::*)
  ]

同样的“索引”方法适用。上面我写[4]的地方,换成你感兴趣的路口位置的nth + 1

【讨论】:

  • 我相信这只适用于 XSLT 上下文。它不能用作独立的 XPath 表达式(例如,对于 XmlDocument.SelectNodes)。
  • @GSerg,Selenium 使用browser's XPath capabilities。据我所知,浏览器支持 generate-id 功能。虽然我承认,我还没有用硒尝试过。
  • @JonasStaudenmeir,看起来他们已经决定只在您调用内部 XSLT 处理器时使该函数可用,MDN shows 'XSLT specific' 用于该函数,这是不幸的。他们有它,但决定限制它的范围......
  • @JonasStaudenmeir 这意味着您需要能够get the context of the outer predicate from the inner predicate。有能力的人说你cannot do that with an XPath 1.0 expression。顺便说一句,如果这是您的情况,为什么不能针对该上下文节点进行多个 xpath 查询?第一个得到下一个foo,如果有的话,另外两个得到bar的两组,然后使用参照相等手动减去这两组?
  • @JonasStaudenmeir,正如 GSerg 所说。除非您设法将 Selenium 与支持 current()generate-id() 的浏览器一起使用,否则这将(实际上?)不可能。 solution by Lingamurthy solves the issue 与我演示的技术类似,但随后将上下文节点设置为 foo td 之一。这需要这两个函数。
【解决方案2】:

如果当前节点是 td[@class'foo'] 元素之一,您可以使用以下 xpath 获取以下 td[@class='bar'] 元素,它们位于下一个 td of foo 之前:

following-sibling::td[@class='bar'][generate-id(preceding-sibling::td[@class='foo'][1]) = generate-id(current())]

在这里,您只选择那些td[@class='bar'],其前面的第一个td[@class='foo'] 与您正在迭代的当前节点相同(使用generate-id() 确认)。

【讨论】:

  • 嘿,你使用了我使用的相同的 generate-id 方法。独立? ;)
  • @Abel 是的,我做到了 :) 我认为我让它更通用,可供任何td[@class='foo'] 使用。
  • 这里的所有解决方案都适用于任何起点。不同之处在于焦点方法(它允许您使用current() 并使表达式更简单)。但我认为在 Selenium 中,焦点通常是整个文档。
  • @Abel 毫无疑问,先生 :) 正如他所说,我觉得 OP 想要从 td 元素之一开始 我正在寻找一个 XPath 1.0 表达式在 .foo.
  • 不幸的是,该表达式不适用于 Selenium(“无效选择器”)。显然 Firefox 不知道 generate-id() 函数。
【解决方案3】:

所以你想要一个intersection of two sets:

  • following-sibling::td[@class='bar'] 跟随你的起始 td[@class='foo'] 节点
  • preceding-sibling::td[@class='bar'] 在下一个 td[@class='foo'] 节点之前

鉴于链接问题的公式,不难得到:

//td[1]/following-sibling::td[@class='bar'][count(. | (//td[1]/following-sibling::td[@class='foo'])[1]/preceding-sibling::td[@class='bar']) = count((//td[1]/following-sibling::td[@class='foo'])[1]/preceding-sibling::td[@class='bar'])]

但是,这将为最后一个 foo 节点返回一个空集,因为没有下一个 foo 节点可以从中获取前面的内容。

所以你想要一个difference of two sets:

  • following-sibling::td[@class='bar'] 跟随你的起始 td[@class='foo'] 节点
  • following-sibling::td[@class='bar'] 跟随下一个 td[@class='foo'] 节点

鉴于链接问题的公式,不难得到:

//td[1]/following-sibling::td[@class='bar'][
    count(. | (//td[1]/following-sibling::td[@class='foo'])[1]/following-sibling::td[@class='bar'])
    !=
    count((//td[1]/following-sibling::td[@class='foo'])[1]/following-sibling::td[@class='bar'])
]

唯一可以修改的是起点//td[1](三遍)。

现在这将正确返回 bar 节点,即使是最后一个 foo 节点。


上面写的印象是您需要一个 XPath 查询,仅此而已。现在很清楚 you don't,您可以通过多个 XPath 查询和一些手动列表过滤来轻松解决您的问题,就像我已经 mentioned in a comment 一样。

在 C# 中是这样的:

XmlNode context = xmlDocument.SelectSingleNode("//td[8]");
XmlNode nextFoo = context.SelectSingleNode("(./following-sibling::td[@class='foo'])[1]");

IEnumerable<XmlNode> result = context.SelectNodes("./following-sibling::td[@class='bar']").Cast<XmlNode>();

if (nextFoo != null)
{
    // Intersect filters using referential equality by default
    result = result.Intersect(nextFoo.SelectNodes("./preceding-sibling::td[@class='bar']").Cast<XmlNode>());
}

我确信转换为 Java 很简单。

【讨论】:

  • 哦,对不起,我偶然发现了“仅...”,并将“三次”阅读为示例中的三种情况,即三组。
  • 是的,对我也有用:)。把它分解成碎片是值得的,但很难辨认出表达方式......
  • @Abel 我不确定在哪里放置换行符,无论哪种方式看起来都令人困惑。表达式的要点很好地显示在that question$set1[count(. | $set2) != count($set2)],然后将$set1 替换为//td[1]/following-sibling::td[@class='bar'](一次)和$set2(//td[1]/following-sibling::td[@class='foo'])[1]/following-sibling::td[@class='bar'](两次)。
  • 是的,我知道这项技术(虽然我更喜欢 XPath 2.0 和 3.0,这只是用 &lt;&lt; 运算符表示),但我不会是这里唯一的读者.无论如何,这取决于你;)
  • @Abel 是的,我也喜欢 XPath 2.0,但 OP 专门寻找 XPath 1.0 解决方案。
【解决方案4】:

非常简单('a' td 的示例)但不是非常理想:

//td[
    @class='bar' and 
    preceding-sibling::td[@class='foo'][1][text() = 'a'] and 
    (
       not(following-sibling::td[@class='foo']) or 
       following-sibling::td[@class='foo'][1][preceding-sibling::td[@class='foo'][1][text() = 'a']]
    )
]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多