Zapytanie XPath, aby pobrać n-tą instancję elementu

134

Istnieje plik HTML (którego zawartość nie kontroluję), który zawiera kilka inputelementów z tym samym ustalonym idatrybutem "search_query". Zawartość pliku może się zmienić, ale wiem, że zawsze chcę uzyskać drugi inputelement z atrybutem id "search_query".

Aby to zrobić, potrzebuję wyrażenia XPath. Próbowałem, //input[@id="search_query"][2]ale to nie działa. Oto przykładowy ciąg XML, w którym to zapytanie nie powiodło się:

<div>
  <form>
    <input id="search_query" />
   </form>
</div>

<div>
  <form>
    <input id="search_query" />
  </form>
</div>

<div>
  <form>
    <input id="search_query" />
  </form>
</div>

Należy pamiętać, że powyższy kod jest jedynie przykładem, a inny kod HTML może być całkiem inny, a inputelementy mogą pojawić się w dowolnym miejscu bez spójnej struktury dokumentu (poza tym, że mam gwarancję, że zawsze będą co najmniej dwa inputelementy z atrybutem id o wartości "search_query").

Jakie jest prawidłowe wyrażenie XPath?

rlandster
źródło
Dobre pytanie, +1. Zobacz moją odpowiedź, aby uzyskać pełne wyjaśnienie problemu i poszukiwane rozwiązanie.
Dimitre Novatchev
7
Drobna uwaga: nigdy nie powinieneś mieć więcej niż jednego elementu o podanym identyfikatorze (a więc HTML w pytaniu jest faktycznie nieprawidłowy). W praktyce przeglądarki i tak pozwolą ci to zrobić, ale jeśli to zrobisz, tracisz jedyną korzyść z używania identyfikatorów, którą jest to, że sygnalizują „Jestem unikalny” (podczas gdy klasy są przeznaczone do używania unikalne znaczące).
machineghost

Odpowiedzi:

244

To jest FAQ :

//somexpression[$N]

oznacza „Znajdź każdy wybrany węzeł, //somexpressionktóry jest $Ndzieckiem swojego rodzica”.

Chcesz :

(//input[@id="search_query"])[2]

Pamiętaj : []operator ma wyższy priorytet (priorytet) niż //skrót.

Dimitre Novatchev
źródło
6
Podoba mi się ta odpowiedź. Nie brałem pod uwagę kwestii pierwszeństwa (po prostu założyłem proste pierwszeństwo od lewej do prawej).
rlandster
10
@rlandster: Słowo „pierwszeństwo” może być mylące. Nieskrócona forma //input[@id='search_query'][2]to:/descendat-or-self::node()/child::input[attribute::id='search_query'][position()=2]
21
Dla tych, którzy trafili tutaj z Google - numeracja zaczyna się od 1 - [1] jest pierwszym elementem i tak dalej
Jan Mares
Dziwne, że w tych zapytaniach XPath tego rodzaju tablice zaczynają się od 1, pomyliłem się.
Ivotje50
@ Ivotje50 Tak Sekwencje i tablice XPath są oparte na 1
Dimitre Novatchev
21

To wydaje się działać:

/descendant::input[@id="search_query"][2]

Wziąłem to z „XSLT 2.0 and XPath 2.0 Programmer's Reference, 4th Edition” Michaela Kaya.

Istnieje również uwaga w sekcji „Abbreviated Syntax” specyfikacji XML Path Language http://www.w3.org/TR/xpath/#path-abbrev, która zawiera wskazówkę.

rlandster
źródło
Wielkie dzięki za tę odpowiedź. W moim przypadku zaakceptowane rozwiązanie nie zadziałałoby, ponieważ używam xpath w ramach robota, który nie akceptuje ścieżek zaczynających się od nawiasów. Ten jednak powinien załatwić
sprawę