Hans, wezmę przynętę i wypoleruję moją wcześniejszą odpowiedź. Powiedziałeś, że chcesz „czegoś bardziej kompletnego”, więc mam nadzieję, że nie masz nic przeciwko długiej odpowiedzi - po prostu próbując zadowolić. Zacznijmy od pewnego tła.
Po pierwsze, to doskonałe pytanie. Często pojawiają się pytania dotyczące dopasowywania pewnych wzorców, z wyjątkiem określonych kontekstów (na przykład w bloku kodu lub w nawiasach). Te pytania często prowadzą do dość niewygodnych rozwiązań. Twoje pytanie dotyczące wielu kontekstów jest więc szczególnym wyzwaniem.
Niespodzianka
Zaskakujące jest, że istnieje co najmniej jedno wydajne rozwiązanie, które jest ogólne, łatwe do wdrożenia i przyjemne w utrzymaniu. To działa ze wszystkimi smakami regex , które pozwalają kontrolować grupy przechwytywania w kodzie. I zdarza się, że odpowiada na wiele typowych pytań, które na pierwszy rzut oka mogą brzmieć inaczej niż twoje: „dopasuj wszystko oprócz pączków”, „zamień wszystko oprócz ...”, „dopasuj wszystkie słowa oprócz tych z czarnej listy mojej mamy”, „ignoruj tagi "," dopasuj temperaturę, chyba że są zapisane kursywą "...
Niestety, technika ta nie jest dobrze znana: szacuję, że na dwadzieścia pytań SO, które mogłyby jej użyć, tylko jedno ma odpowiedź, która o niej wspomina - co oznacza, że może jedna na pięćdziesiąt lub sześćdziesiąt odpowiedzi. Zobacz moją wymianę z Kobi w komentarzach. Technika ta jest szczegółowo opisana w tym artykule, który nazywa ją (optymistycznie) „najlepszą sztuczką wyrażeń regularnych wszechczasów”. Nie wdając się w szczegóły, spróbuję dokładnie zrozumieć, jak działa ta technika. Aby uzyskać więcej szczegółów i przykłady kodu w różnych językach, zachęcam do zapoznania się z tym źródłem.
Lepiej znana odmiana
Istnieje odmiana składni specyficzna dla Perla i PHP, która zapewnia to samo. Zobaczysz to w SO w rękach mistrzów regex, takich jak CasimiretHippolyte i HamZa . Powiem ci więcej na ten temat poniżej, ale skupiam się tutaj na ogólnym rozwiązaniu, które działa ze wszystkimi odmianami wyrażeń regularnych (o ile możesz sprawdzić grupy przechwytywania w swoim kodzie).
Dzięki za całe tło, zx81 ... Ale jaki jest przepis?
Kluczowy fakt
Metoda zwraca dopasowanie w przechwyceniu grupy 1. W ogóle nie dba o ogólny mecz.
W rzeczywistości sztuczka polega na dopasowaniu różnych kontekstów, których nie chcemy (łącząc te konteksty w łańcuch za pomocą |
OR / alternacji), aby je „zneutralizować”. Po dopasowaniu wszystkich niechcianych kontekstów ostatnia część naprzemienności dopasowuje to, czego chcemy i przechwytuje to do grupy 1.
Ogólny przepis to
Not_this_context|Not_this_either|StayAway|(WhatYouWant)
To będzie pasować Not_this_context
, ale w pewnym sensie mecz trafia do kosza, ponieważ nie będziemy patrzeć na ogólne mecze: patrzymy tylko na przechwyty w grupie 1.
W twoim przypadku, z twoimi cyframi i trzema kontekstami do zignorowania, możemy zrobić:
s1|s2|s3|(\b\d+\b)
Zauważ, że ponieważ w rzeczywistości dopasowujemy s1, s2 i s3 zamiast próbować ich uniknąć za pomocą lookarounds, poszczególne wyrażenia dla s1, s2 i s3 mogą pozostać jasne jak dzień. (Są to podwyrażenia po każdej stronie a |
)
Całe wyrażenie można zapisać w ten sposób:
(?m)^.*\.$|\([^\)]*\)|if\(.*?//endif|(\b\d+\b)
Zobacz to demo (ale skup się na grupach przechwytywania w prawym dolnym panelu).
Jeśli spróbujesz w myślach podzielić to wyrażenie regularne w każdym |
separatorze, w rzeczywistości jest to tylko seria czterech bardzo prostych wyrażeń.
W przypadku smaków obsługujących wolne odstępy jest to szczególnie dobre.
(?mx)
### s1: Match line that ends with a period ###
^.*\.$
| ### OR s2: Match anything between parentheses ###
\([^\)]*\)
| ### OR s3: Match any if(...//endif block ###
if\(.*?//endif
| ### OR capture digits to Group 1 ###
(\b\d+\b)
Jest to wyjątkowo łatwe do odczytania i utrzymania.
Rozszerzanie wyrażenia regularnego
Jeśli chcesz zignorować więcej sytuacji s4 i s5, dodaj je w większej liczbie naprzemienności po lewej stronie:
s4|s5|s1|s2|s3|(\b\d+\b)
Jak to działa?
Konteksty, których nie chcesz, są dodawane do listy wariantów po lewej stronie: będą pasować, ale te ogólne dopasowania nigdy nie są sprawdzane, więc dopasowanie ich jest sposobem na umieszczenie ich w „koszu na śmieci”.
Jednak żądana zawartość jest przechwytywana do grupy 1. Następnie musisz programowo sprawdzić, czy grupa 1 jest ustawiona, a nie pusta. Jest to trywialne zadanie programistyczne (a później porozmawiamy o tym, jak to się robi), zwłaszcza biorąc pod uwagę, że pozostawia ono proste wyrażenie regularne, które można zrozumieć na pierwszy rzut oka i zmienić lub rozszerzyć zgodnie z wymaganiami.
Nie zawsze jestem fanem wizualizacji, ale ta dobrze pokazuje, jak prosta jest metoda. Każda „linia” odpowiada potencjalnemu dopasowaniu, ale tylko dolna linia jest uwzględniana w grupie 1.
Debuggex Demo
Odmiana Perl / PCRE
W przeciwieństwie do powyższego ogólnego rozwiązania, istnieje odmiana Perla i PCRE, która jest często widoczna w SO, przynajmniej w rękach regex Gods, takich jak @CasimiretHippolyte i @HamZa. To jest:
(?:s1|s2|s3)(*SKIP)(*F)|whatYouWant
W Twoim przypadku:
(?m)(?:^.*\.$|\([^()]*\)|if\(.*?//endif)(*SKIP)(*F)|\b\d+\b
Ta odmiana jest nieco łatwiejsza w użyciu, ponieważ treść dopasowana w kontekstach s1, s2 i s3 jest po prostu pomijana, więc nie musisz sprawdzać przechwyceń grupy 1 (zwróć uwagę, że nawiasy zniknęły). Zapałki zawierają tylkowhatYouWant
Należy zauważyć, że (*F)
, (*FAIL)
i (?!)
są tym samym. Jeśli chcesz być bardziej niejasny, możesz użyć(*SKIP)(?!)
demo dla tej wersji
Aplikacje
Oto kilka typowych problemów, które ta technika często może łatwo rozwiązać. Zauważysz, że wybór słów może sprawić, że niektóre z tych problemów brzmią inaczej, podczas gdy w rzeczywistości są one praktycznie identyczne.
- Jak mogę dopasować foo z wyjątkiem dowolnego miejsca w tagu
<a stuff...>...</a>
?
- Jak mogę dopasować foo poza
<i>
tagiem lub fragmentem kodu javascript (więcej warunków)?
- Jak mogę dopasować wszystkie słowa, których nie ma na tej czarnej liście?
- Jak mogę zignorować cokolwiek wewnątrz bloku SUB ... END SUB?
- Jak mogę dopasować wszystko oprócz ... s1 s2 s3?
Jak zaprogramować przechwyty w grupie 1
Nie zrobiłeś tego, jeśli chodzi o kod, ale do ukończenia ... Kod do inspekcji grupy 1 będzie oczywiście zależał od wybranego języka. W każdym razie nie powinien dodawać więcej niż kilka wierszy do kodu, którego używałbyś do sprawdzania dopasowań.
W razie wątpliwości polecam zajrzeć do sekcji z przykładami kodu we wspomnianym wcześniej artykule, która przedstawia kod dla kilku języków.
Alternatywy
W zależności od złożoności pytania i używanego silnika wyrażeń regularnych istnieje kilka alternatyw. Oto dwa, które można zastosować w większości sytuacji, w tym w wielu warunkach. Moim zdaniem żadna z nich nie jest tak atrakcyjna jak s1|s2|s3|(whatYouWant)
przepis, choćby dlatego, że zawsze zwycięża przejrzystość.
1. Wymień, a następnie dopasuj.
Dobrym rozwiązaniem, które brzmi hakersko, ale działa dobrze w wielu środowiskach, jest dwuetapowa praca. Pierwsze wyrażenie regularne neutralizuje kontekst, który chcesz zignorować, zastępując potencjalnie sprzeczne ciągi. Jeśli chcesz tylko dopasować, możesz zastąpić pustym ciągiem, a następnie uruchomić dopasowanie w drugim kroku. Jeśli chcesz zamienić, możesz najpierw zamienić ciągi, które mają być ignorowane, na coś charakterystycznego, na przykład otoczenie cyfr łańcuchem o stałej szerokości @@@
. Po tej zamianie możesz zamienić to, czego naprawdę chciałeś, a następnie będziesz musiał przywrócić swoje charakterystyczne @@@
struny.
2. Lookarounds.
Twój oryginalny post pokazał, że wiesz, jak wykluczyć pojedynczy warunek za pomocą obejrzeń. Powiedziałeś, że C # jest do tego świetny i masz rację, ale nie jest to jedyna opcja. Formy wyrażeń regularnych .NET znalezione na przykład w C #, VB.NET i Visual C ++, a także wciąż eksperymentalny regex
moduł do zastąpienia re
w Pythonie, to jedyne dwa znane mi silniki, które obsługują lookbehind o nieskończonej szerokości. Dzięki tym narzędziom jeden warunek w jednym spojrzeniu wstecz może zająć się patrzeniem nie tylko za mecz, ale także na mecz i poza mecz, unikając potrzeby koordynacji z patrzeniem w przód. Więcej warunków? Więcej obejść.
Recykling wyrażenia regularnego, które miałeś dla s3 w C #, cały wzorzec wyglądałby tak.
(?!.*\.)(?<!\([^()]*(?=\d+[^)]*\)))(?<!if\(\D*(?=\d+.*?//endif))\b\d+\b
Ale teraz już wiesz, że tego nie polecam, prawda?
Usunięcia
@HamZa i @Jerry zasugerowali, żebym wspomniał o dodatkowej sztuczce w przypadku, gdy chcesz po prostu usunąć WhatYouWant
. Pamiętasz, że przepis na dopasowanie WhatYouWant
(przechwycenie go do grupy 1) był s1|s2|s3|(WhatYouWant)
, prawda? Aby usunąć wszystkie wystąpienia WhatYouWant
, należy zmienić wyrażenie regularne na
(s1|s2|s3)|WhatYouWant
W przypadku ciągu zastępczego używasz $1
. To, co się tutaj dzieje, to to, że dla każdego s1|s2|s3
dopasowanego wystąpienia , zamiana $1
zastępuje to wystąpienie sobą (do którego odwołuje się $1
). Z drugiej strony, gdy WhatYouWant
jest dopasowany, jest zastępowany przez pustą grupę i nic więcej - i dlatego jest usuwany. Obejrzyj to demo , dziękuję @HamZa i @Jerry za zasugerowanie tego wspaniałego dodatku.
Części zamienne
To prowadzi nas do zamienników, o których pokrótce się poruszę.
- Jeśli zastępujesz go niczym, zobacz powyższą sztuczkę „Usunięcia”.
- Podczas wymiany, jeśli używasz Perla lub PCRE, użyj
(*SKIP)(*F)
wariantu wspomnianego powyżej, aby dokładnie dopasować to, co chcesz, i wykonaj prostą wymianę.
- W innych odmianach, w ramach wywołania funkcji zamiany, sprawdź dopasowanie za pomocą wywołania zwrotnego lub lambda i zamień, jeśli ustawiono grupę 1. Jeśli potrzebujesz pomocy, artykuł już przywoływany zawiera kod w różnych językach.
Baw się dobrze!
Nie, czekaj, jest więcej!
Ach, nie, zachowam to dla moich wspomnień w dwudziestu tomach, które zostaną wydane wiosną przyszłego roku.
\K
nie ma specjalnej składni php. Proszę rozwinąć i wyjaśnić, co chcesz powiedzieć. Jeśli chcesz nam powiedzieć, że nie potrzebujesz „skomplikowanego” rozwiązania, musisz powiedzieć, co jest dla Ciebie skomplikowane i dlaczego.