Упорядоченный XPath поиск атрибутов приоритета - PullRequest
4 голосов
/ 22 апреля 2009

Я хочу написать 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 занимает второе место, поскольку соответствует только первому атрибуту.

Ссылка A занимает последнее место, поскольку не соответствует первому атрибуту; он соответствует только второму и третьему атрибутам.

XPath должен вернуть лучшее совпадение, ссылка С.

Если для «лучшего соответствия» было привязано более одной ссылки, XPath должен вернуть первую лучшую ссылку, найденную на странице.

Ответы [ 4 ]

2 голосов
/ 22 апреля 2009

Есть решение для перебора. Я продемонстрирую два атрибута вместо трех.

(
  //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]

То есть, выберите все ссылки, где ни один атрибут не соответствует, но только если нет ссылки, которая имеет лучшее соответствие. Затем выберите все ссылки, которые имеют меньшее соответствие атрибута, но только в том случае, если нет ссылок с более высоким соответствием атрибута. Ссылки выбора, где только первый атрибут соответствует, но только если нет ссылок, где оба атрибута совпадают. Затем выберите ссылки, где оба атрибута совпадают. Только одна из этих четырех конъюнктов будет непустой, поэтому оператор "|" фактически никогда ничего не объединяет. Наконец, выберите первую ссылку в порядке документа, если какой-либо из этих наборов узлов содержал более одного элемента.

Причина, по которой я сделал только два атрибута вместо трех, заключается в том, что я не хотел печатать все восемь случаев. Вы можете пропустить первый случай, если вас не интересуют какие-либо ссылки, если хотя бы один из атрибутов не соответствует.

В такой ситуации вам может быть лучше просто выбрать все кандидатов в гораздо более простой запрос, который Джефф показал , а затем использовать другой код для ранжирования результатов впоследствии, где Вы можете более легко использовать итерацию и переменные, чтобы выбрать лучшего кандидата.

Если вы можете использовать XPath 2 , то вы можете использовать оператор запятой (или concat функция ) для объединения последовательностей узлов (которые заменяют собой узел-множество). Попробуйте это, например:

(
  //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. (Это немного напоминает, как определяется специфичность CSS-селекторов .) Это способ моделирования критериев упорядочения, но теперь, когда я все это напечатал, я не совсем понимаю, что это приводит к какому-либо краткому решению в XPath.

2 голосов
/ 22 апреля 2009

Два предыдущих ответа кажутся не точными .

Вот одно из возможных решений :

Вы хотите найти первый узел с максимальным значением для следующей функции:

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" />

Это напоминает мне о еще одной проблеме «Одиночное выражение XPath для поиска лучших совпадений» Я решил около семи лет назад:)

1 голос
/ 22 апреля 2009

Попробуйте оператор 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, который делает эквивалент каждой комбинации по порядку. Я не знаю другого способа упростить его.

0 голосов
/ 22 апреля 2009

У меня была похожая проблема сегодня, и я нашел решение, которое будет работать в контексте 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)]"/>

Конечно, хитрость в том, что пустой набор узлов оценивается как ложный.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...