【问题标题】:XPath ordered priority attribute searchXPath 有序优先属性搜索
【发布时间】:2009-04-21 21:01:26
【问题描述】:

我想编写一个 XPath,它可以在 HTML DOM 上返回一些链接元素。

语法错误,但这是我想要的要点:

//web:link[@text='Login' THEN_TRY @href='login.php' THEN_TRY @index=0]

THEN_TRY 是一个虚构的运算符,因为我找不到要使用的运算符。如果对于给定的一组 [attribute=name] 对,页面上存在许多链接,则应返回与最左侧属性匹配的链接,而不是其他任何链接。

例如,考虑以上示例 XPath 找到 3 个与给定属性匹配的链接的情况:

link A: text='Sign In', href='Login.php', index=0
link B: text='Login', href='Signin.php', index=15
link C: text='Login', href='Login.php', index=22

链接 C 被评为最佳匹配,因为它匹配 First 和 Second 属性。

链接 B 排名第二,因为它只匹配 First 属性。

链接 A 排名最后,因为它与 First 属性不匹配;它只匹配第二个和第三个属性。

XPath 应该返回最佳匹配,Link C。

如果多个链接与“最佳匹配”相关联,则 XPath 应返回它在页面上找到的第一个最佳链接。

【问题讨论】:

  • 我已经发布了一个正确的解决方案,希望对您有所帮助。 (+1) 的问题。

标签: xslt search xpath attributes


【解决方案1】:

前面两个答案好像不太准确

这是一种可能的解决方案

您想找到以下函数的最大值的第一个节点:

100*number(@text='Login') 
+10*number(@href='Login.php') 
+ 1*number(@index=0)

在 XPath 2.0 中,这可以用以下方式表示为单个 XPath 表达式:

  /*/link[
           100*number(@text='Login') 
           +10*number(@href='Login.php') 
           + 1*number(@index=0)

          eq
             max(/*/link
                     /(100*number(@text='Login') 
                       +10*number(@href='Login.php') 
                       + 1*number(@index='0')
                       )
                )

          ]

在 XPath 1.0 中构建这样一个单一的表达式将非常困难,如果可能的话,即使可能,这样的 XPath 表达式也无法理解、证明是正确的和/或维护。

但是,选择最匹配的 link 元素是可能的,在 XPath 1.0 宿主的任何语言中都是可能的

下面的一个示例是使用 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:template match="/">
      <xsl:for-each select="*/link">
        <xsl:sort data-type="number" order="descending" select=
        "100*(@text='Login') 
         +10*(@href='Login.php') 
         + 1*(@index=0)
        "/>
        <xsl:if test="position() = 1">
          <xsl:copy-of select="."/>
        </xsl:if>
      </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

当上述转换应用于此 XML 文档时

<links>
  <link name="A" text="Sign in" href="Login.php" 
        index="0"/>
  <link name="B" text="Login" href="SignIn.php" 
        index="15"/>
  <link name="C" text="Login" href="Login.php" 
        index="22"/>
</links>

产生正确的结果

<link name="C" text="Login" href="Login.php" index="22" />

这让我想起了another "Single XPath expression finding the best matches" problem 我在七年前解决了一些问题:)

【讨论】:

  • 公平地说,我没有指定 XPath 1 或 XPath 2。XPath 的简单性也很重要。随着属性数量的增加,您的 XPath 2 解决方案远没有 Rob 和 Jeff 的解决方案复杂。
  • 感谢您接受解决方案。公平地说,其他两个答案是 not 解决方案。杰夫自己说这是他的回答。 Bob 虽然提供了一个正确的想法,但最后他说:“但现在我已经全部输入了,我不太明白它会导致 XPath 中的任何简洁解决方案。”
  • 我的 XPath 1 答案不会非常困难,只是非常麻烦。我证明了这是可能的,我认为我证明它是正确的。不过,我不想维护它。我认为我建议的两个解决方案都会给出正确的答案,尽管两者都不是很好。感谢您展示了我的最后一个想法确实可以用于 XPath 解决方案。请不要叫我鲍勃。
  • @Rob-kennedy 很抱歉打电话给你 Bob。至于您的解决方案,它们似乎太短了,并且似乎不太可能构建 XML 文档作为反例。当然,我没有时间构建这样的示例或证明您的解决方案的正确性。我们现在都知道,只有您认为您的解决方案是正确的这一事实并不一定意味着它确实如此。这是更简单、更容易证明正确的解决方案的首选原因之一。
  • @Rob-kennedy 至于“演示我的最后一个想法如何确实可以用于 XPath 解决方案”,我在七年前就这样做了:)请参阅答案末尾的链接。跨度>
【解决方案2】:

有一个蛮力解决方案。我将演示两个属性而不是三个。

( //web:link[@text != 'Login' 和 @href != 'Login.php' 而不是(//web:link[@text = 'Login' or @href = 'Login.php'])] | //web:link[@text != 'Login' 和 @href = 'Login.php' 而不是(//web:link[@text = 'Login'])] | //web:link[@text = 'Login' 和 @href != 'Login.php' 而不是(//web:link[@text = 'Login' and @href = 'Login.php'])] | //web:link[@text = 'Login' 和 @href = 'Login.php'] )[1]

也就是说,选择两个属性都不匹配的所有链接,但前提是没有更好匹配的链接。然后选择所有具有较小属性匹配的链接,但仅当没有与高级属性匹配的链接时。只有第一个属性匹配的选择链接,但前提是没有两个属性都匹配的链接。然后选择两个属性都匹配的链接。这四个连词中只有一个是非空的,因此“|”运算符实际上从未组合任何内容。最后,选择文档顺序中的第一个链接,以防其中任何一个节点集包含多个元素。

我只做两个属性而不是三个的原因是因为我不想输入所有八个案例。如果您对任何链接不感兴趣,则可以省略第一种情况,除非至少有一个属性匹配。

在这种情况下,您最好选择所有 the much simpler query Jeff showed 中的候选者,然后使用其他代码对结果进行排名,这样您可以更轻松地使用迭代和变量选择最佳候选人。

如果您可以使用 XPath 2,那么您可以使用 the comma operator(或 the concat function)加入节点序列(取代节点集)。试试这个,例如:

( //web:link[@text = 'Login' 和 @href = 'Login.php' 和 @index = 0] , //web:link[@text = 'Login' and @href = 'Login.php' and @index != 0] , //web:link[@text = 'Login' and @href != 'Login.php' and @index = 0] , //web:link[@text = 'Login' and @href != 'Login.php' and @index != 0] , //web:link[@text != 'Login' and @href = 'Login.php' and @index = 0] , //web:link[@text != 'Login' and @href = 'Login.php' and @index != 0] , //web:link[@text != 'Login' 和 @href != 'Login.php' 和 @index = 0] , //web:link[@text != 'Login' and @href != 'Login.php' and @index != 0] )[1]

顺便说一句,这里有一种为每个链接分配排名的简单方法,这使得比较它们变得非常简单。想象一个位字段,您要检查的每个属性一个位。如果第一个属性匹配,则设置最左边的位,否则不设置。如果第二个属性匹配,则设置下一个最高有效位等。因此,对于您的示例,您将获得以下位值:

011链接A:text='登录',href='Login.php',索引=0 100 链接 B: text='Login', href='Signin.php', index=15 110 链接 C: text='Login', href='Login.php', index=22

要选择最佳匹配,请将位字段视为二进制数。链接 A 的得分为 3,链接 B 的得分为 4,链接 C 的得分为 6。(这有点让人想起 specificity of CSS selectors 的确定方式。)这是一种对排序标准进行建模的方法,但是现在我已经全部输入了,我不太明白它会导致 XPath 中的任何简明解决方案。

【讨论】:

    【解决方案3】:

    试试or 运算符,如:

    web:link[@text='Login' or @href='login.php' or @index=0]
    

    但是,这可能会为您提供所有这些节点,而不仅仅是指定优先级中的一个。

    更新
    所以,我尝试了这个并且它有效。它很长,但它应该可以满足您的需求(对您的架构进行适当的更改)。

    //link[@text='Login'] | //link[not(//link[@text='Login']) and @href='Login.php'] | //link[not(//link[@text='Login']) and not(//link[@href='Login.php']) and @index='0']
    

    我在以下测试 XML 上运行它,注释掉每一行以测试不同的部分,它按预期工作。

    <?xml version="1.0" encoding="utf-8"?>
    <Test>
      <link text='Sign In' href='Login2.php' index="0"></link>
      <link  text='Login' href='Signin.php' index="15"></link>
      <link  text='LoginBlah' href='Login.php' index="22"></link>
    </Test>
    

    更新 2
    我注意到我还没有完全解决这个问题,因为你想要最好的匹配而不是按优先顺序排列的匹配。这可以完成,但需要相当长的 XPath 来按顺序执行每个组合的等效操作。我不知道有什么其他方法可以简化它。

    【讨论】:

      【解决方案4】:

      我今天遇到了类似的问题,并找到了一个可以在 XSLT 上下文中工作的解决方案。对于纯 XPath 解决方案,您将需要其他方法之一。

      <xsl:variable name="first" select="/web:link[@text='Login']"/>
      <xsl:variable name="second" select="/web:link[@href='login.php']"/>
      <xsl:variable name="third" select="/web:link[@index=0]"/>
      <xsl:variable name="theAnswer" 
       select="$first | $second[not($first)] | $third[not($first or $second)]"/>
      

      当然,这里的诀窍是空节点集的计算结果为 false。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-06-04
        • 2019-11-21
        • 2011-01-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-02-16
        相关资源
        最近更新 更多