Możliwe, że XPath bez rozróżniania wielkości liter zawiera ()?

94

Przeglądam wszystkie węzły tekstowe mojego DOM i sprawdzam, czy nodeValue zawiera określony ciąg.

/html/body//text()[contains(.,'test')]

Rozróżniana jest wielkość liter. Jednak chcę też złapać Test, TESTlub TesT. Czy jest to możliwe z XPath (w JavaScript)?

Aron Woost
źródło

Odpowiedzi:

112

Dotyczy to XPath 1.0. Jeśli Twoje środowisko obsługuje XPath 2.0, zobacz tutaj .


Tak. Możliwe, ale nie piękne.

/html/body//text()[
  contains(
    translate(., 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'),
    'test'
  )
]

To zadziała w przypadku ciągów wyszukiwania, w przypadku których alfabet jest wcześniej znany. Dodaj znaki akcentowane, które spodziewasz się zobaczyć.


Jeśli możesz, oznacz tekst, który Cię interesuje, innymi sposobami, na przykład umieszczając go w elemencie, <span>który ma określoną klasę podczas tworzenia kodu HTML. Takie rzeczy są znacznie łatwiejsze do zlokalizowania za pomocą XPath niż podciągi w tekście elementu.

Jeśli to nie jest możliwe, możesz pozwolić JavaScript (lub jakiemukolwiek innemu językowi hosta, którego używasz do wykonywania XPath), aby pomóc Ci w tworzeniu dynamicznego wyrażenia XPath:

function xpathPrepare(xpath, searchString) {
  return xpath.replace("$u", searchString.toUpperCase())
              .replace("$l", searchString.toLowerCase())
              .replace("$s", searchString.toLowerCase());
}

xp = xpathPrepare("//text()[contains(translate(., '$u', '$l'), '$s')]", "Test");
// -> "//text()[contains(translate(., 'TEST', 'test'), 'test')]"

(Wskazówka dotycząca kapelusza do odpowiedzi @ KirillPolishchuk - oczywiście wystarczy przetłumaczyć tylko te znaki, których faktycznie szukasz ).

Takie podejście zadziałałoby dla dowolnego ciągu wyszukiwania, bez konieczności wcześniejszej znajomości alfabetu, co jest dużym plusem.

Obie powyższe metody zawodzą, gdy ciągi wyszukiwania mogą zawierać pojedyncze cudzysłowy, w takim przypadku sprawy stają się bardziej skomplikowane .

Tomalak
źródło
Dzięki! Również dodatek jest fajny, tłumacząc tylko potrzebne znaki. Byłbym ciekawy, czym jest zwycięstwo w wykonaniu. Zauważ, że xpathPrepare () może obsługiwać więcej niż raz pojawiające się znaki inaczej (np. Otrzymujesz TEEEEEST i teeeeest).
Aron Woost,
@ AronWoost: Cóż, może być pewien zysk, po prostu sprawdź go, jeśli chcesz się dowiedzieć. translate()sam nie dba o to, jak często powtarzasz każdy znak - translate(., 'EE', 'ee')jest absolutnie równoważny translate(., 'E', 'e'). PS: Nie zapomnij zagłosować za @KirillPolishchuk, pomysł był jego.
Tomalak
2
System.Xml.XmlNodeList x = mydoc.SelectNodes ("// * [zawiera (translate (text (), 'ABCDEFGHIJKLMNOPQRSTUVWXYZĘÖÜÉÈÀÁÂÒÓÔÙÚÛÇÅÏÕÑŒ', 'abcdefghijklmnopqrstuvéwéxyz)')
Stefan Steiger
1
Nie. Zobacz część „oczywiście, musisz przetłumaczyć tylko te znaki, których faktycznie szukasz” .
Tomalak
62

Piękniejsza:

/html/body//text()[contains(translate(., 'TES', 'tes'), 'test')]
Kirill Polishchuk
źródło
4
+1 Absolutnie. To jest coś, o czym nie pomyślałem. (Wykorzystam to w mojej odpowiedzi, jest to znacznie lepsze niż oryginalna procedura JavaScript, którą napisałem)
Tomalak
4
czy po prostu nie przekształci TESTsię testi nie pozostawi Testtak, jak jest?
Muhammad Adeel Zahid
7
@MuhammadAdeelZahid - Nie, to zamienia „T” na „t”, „E” na „e” itd. To dopasowanie 1 do 1.
Daniel Haley
To mogłoby być bardziej oczywiste translate(., 'TES', 'tes'). W ten sposób ludzie zdadzą sobie sprawę, że to nie jest tłumaczenie słów, tylko tłumaczenie liter.
mlissner
lub „EST”, „est”, chociaż wygląda fajnie (choć trochę tajemniczo), że część wyszukiwanego hasła pojawia się w mapowaniu (powtórzone litery zostały usunięte)
George Birbilis
56

Rozwiązania XPath 2.0

  1. Posługiwać się małych liter () :

    /html/body//text()[contains(lower-case(.),'test')]

  2. Użyj dopasowania () wyrażenia regularnego z flagą bez rozróżniania wielkości liter:

    /html/body//text()[matches(.,'test', 'i')]

kjhughes
źródło
1
Czy ta składnia nie jest obsługiwana w przeglądarkach Firefox i Chrome? Właśnie wypróbowałem to w konsoli i oba zwracają błąd składni.
db
1
Firefox i Chrome implementują tylko XPath 1.0.
kjhughes
gdzie mogę sprawdzić, czy to zadziała zgodnie z oczekiwaniami?
Ankit Gupta
@AnkitGupta: Oczywiście do weryfikacji tej odpowiedzi można użyć dowolnego narzędzia online lub offline, które obsługuje XPath 2.0, ale (1) zalecenia dotyczące narzędzi są tutaj poza tematem w SO i (2) biorąc pod uwagę 56 głosów za, 0 głosów przeciw i nie odrębne komentarze w ciągu ponad sześciu lat, możesz być całkiem pewien, że ta odpowiedź jest poprawna. ;-)
kjhughes
8

Tak. Możesz użyć translatedo konwersji tekstu, który chcesz dopasować, na małe litery w następujący sposób:

/html/body//text()[contains(translate(., 
                                      'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
                                      'abcdefghijklmnopqrstuvwxyz'),
                   'test')]
Andy
źródło
6

Jeśli używasz XPath 2.0, możesz określić sortowanie jako trzeci argument funkcji zawiera (). Jednak identyfikatory URI sortowania nie są ustandaryzowane, więc szczegóły zależą od używanego produktu.

Zwróć uwagę, że wszystkie rozwiązania podane wcześniej przy użyciu translate () zakładają, że używasz tylko 26-literowego alfabetu angielskiego.

AKTUALIZACJA: XPath 3.1 definiuje standardowy identyfikator URI sortowania dla dopasowywania z rozróżnianiem wielkości liter.

Michael Kay
źródło
3

Sposób, w jaki zawsze to robiłem, polegał na użyciu funkcji „translate” w XPath. Nie powiem, że jest bardzo ładny, ale działa poprawnie.

/html/body//text()[contains(translate(.,'abcdefghijklmnopqrstuvwxyz',
                                        'ABCDEFGHIJKLOMNOPQRSTUVWXYZ'),'TEST')]

mam nadzieję że to pomoże,

Marvin Smit
źródło