XPath命令优先级属性搜索

时间:2009-04-21 21:01:27

标签: xslt search xpath attributes

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

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

//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排名为最佳匹配,因为它匹配第一和第二属性。

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

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

XPath应该返回最佳匹配,即链接C.

如果多个链接被绑定为“最佳匹配”,则XPath应返回它在页面上找到的第一个最佳链接。

4 个答案:

答案 0 :(得分:2)

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

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

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

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

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

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

(
  //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' 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]
)[1]

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

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

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

答案 1 :(得分:2)

前两个答案似乎并不准确

以下是一种可能的解决方案

您希望找到具有以下函数的最大值的第一个节点:

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表达式将无法理解,证明是正确的和/或维护。

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

下面的一个示例是使用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 我七年前解决了这个问题:)

答案 2 :(得分:1)

尝试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,它按顺序执行每个组合的等效操作。我不知道有任何其他简化方法。

答案 3 :(得分:0)

我今天遇到了类似的问题,并找到了一个可以在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。