XPath: создать относительное выражение от корневого узла к указанному узлу?

Как я могу сгенерировать необходимое выражение XPath для перехода от заданного корневого узла к указанному узлу по структуре xml?

Я получу HTML-фрагмент таблицы во время выполнения. Я должен найти нужный узел на основе некоторых критериев и сформировать строку XPath от корневого узла таблицы к этому узлу и вернуть его.

Структура таблицы HTML заранее неизвестна. Есть ли какой-либо API в Java, который возвращает строку XPath с учетом корневого узла и дочернего узла?


person Navneet    schedule 05.01.2011    source источник
comment
Хороший вопрос, +1. См. мой ответ для одного выражения XPath 2.0, которое создает желаемое выражение XPath. :)   -  person Dimitre Novatchev    schedule 05.01.2011


Ответы (4)


Я бы рекомендовал делать это в Groovy, который предоставляет GPATH (по сути, реализация xpath для языка groovy). Синтаксис Groovy очень краткий и мощный, как описано в моем blog и легко смешивается с языком Java (groovy компилируется в файлы классов Java).

Что касается того, чего вы пытаетесь достичь... следующее должно пройти через всю структуру HTML DOM и найти "тег" (например, div) с определенным атрибутом id (например, unique_id_for_tag) с каждой записью, которая будет обработана закрытием .

HTML.body.'**'.findAll {  it.name() == 'tag' && it["@id"] == 'tag_name' }.each { 
//"it" is the return value
if(it.td[0].text().toString().trim().contains('Hello')){
   var x = it.td[0].text().toString().trim();
}
person Eric Warriner    schedule 05.01.2011

Ниже приведен один из способов (который я знаю) для достижения этого

  1. Создайте DOM из XML
  2. Получить узел указанного узла, используя "//" XPATH
  3. Когда у вас есть объект Node из шага 2, вам нужно просто пройти вверх по иерархии с помощью getParentNode() и построить xpath.
person Aravind Yarram    schedule 05.01.2011

Этого нельзя сделать (только) в чистом XPath 1.0.

Решение XPath 2.0:

if(not($vStart intersect $vTarget/ancestor::*))
  then ()
  else
   for $vPath in
      string-join
          ((for $x in
                $vTarget
                  /ancestor-or-self::*[. >> $vStart]
                    /concat(name(.),
                            for $n in name(.),
                                $cn in count(../*[name(.) eq $n])
                             return
                               if($cn ge 2)
                                 then concat('[', 
                                               count((preceding-sibling::*
                                                              [name() eq $n]) +1, 
                                             ']')
                                 else (),
                            '/'
                               )
               return $x),
              ''
           )
           return string-join((concat(name($vStart), '/'),$vPath), '')

Когда это выражение XPath 2.0 оценивается по отношению к следующему XML-документу:

<table>
  <tr>
    <td><b>11</b></td>
    <td><i>12</i></td>
  </tr>
  <tr>
    <td><p><b>21</b></p></td>
    <td><p><b>221</b></p><p><b><i>222</i></b></p></td>
  </tr>
  <tr>
    <td><b>31</b></td>
    <td><i>32</i></td>
  </tr>
</table>

и если два параметра определены как:

  <xsl:variable name="vStart" select="/*"/>
  <xsl:variable name="vTarget" select="/*/tr[2]/td[2]/p[2]/b/i"/>

тогда результат оценки выражения XPath 2.0 выше:

table/tr[2]/td[2]/p[2]/b/i/
person Dimitre Novatchev    schedule 05.01.2011
comment
+1 Хороший ответ. Я бы не стал делать необязательным позиционный предикат: думать в цели без предшествующих, но с последующими - person ; 06.01.2011
comment
@Alejandro: Спасибо, я исправил выражение, и результат все еще упрощается, когда это единственный ребенок с таким именем. - person Dimitre Novatchev; 06.01.2011

Если вы знаете имена корневого элемента и дочернего элемента, который вы пытаетесь выбрать, и если есть только один дочерний элемент с таким именем, вы можете использовать просто "/root//child". Но, возможно, я неправильно понял, чего вы пытались достичь. Не могли бы вы привести пример?

person Damien    schedule 05.01.2011
comment
Нет, это не единственный ребенок. Это может быть ребенок, внук или еще много уровней вниз по иерархии. Поиск будет основываться на содержимом узла. Как только узел идентифицирован, мне нужно получить выражение xpath для этого узла. - person Navneet; 05.01.2011
comment
Вы можете использовать что-то вроде /root//*[contains(.,'test')] для проверки содержимого, но если оно возвращает более одного узла, может быть неправильно создавать выражение, подобное /root/a/b/c. /child с первым, так как /root/d/e/child тоже может быть решением. В этом случае единственным правильным XPath будет использование //... - person Damien; 05.01.2011