RegEx dopasowuje otwarte tagi z wyjątkiem tagów niezależnych XHTML

1473

Muszę dopasować wszystkie te tagi otwierające:

<p>
<a href="foo">

Ale nie te:

<br />
<hr class="foo" />

Wymyśliłem to i chciałem się upewnić, że mam rację. Ja tylko chwytam a-z.

<([a-z]+) *[^/]*?>

Wierzę, że mówi:

  • Znajdź mniej niż wtedy
  • Znajdź (i zdobądź) az raz lub więcej razy
  • Znajdź więc zero lub więcej spacji
  • Znajdź dowolną postać zero lub więcej razy, chciwy, z wyjątkiem /wtedy
  • Znajdź coś więcej niż

Czy mam to prawo? A co ważniejsze, co myślisz?

Jeff
źródło

Odpowiedzi:

4417

Nie można parsować [X] HTML za pomocą wyrażenia regularnego. Ponieważ HTML nie może zostać przeanalizowany przez wyrażenie regularne. Regex nie jest narzędziem, którego można użyć do prawidłowego parsowania HTML. Jak już wiele razy odpowiadałem na pytania HTML i regex, użycie regex nie pozwoli ci na używanie HTML. Wyrażenia regularne to narzędzie, które nie jest wystarczająco zaawansowane, aby zrozumieć konstrukcje stosowane przez HTML. HTML nie jest językiem regularnym i dlatego nie można go analizować za pomocą wyrażeń regularnych. Kwerendy Regex nie są przystosowane do podziału HTML na znaczące części. tyle razy, ale do mnie to nie dociera. Nawet ulepszone nieregularne wyrażenia regularne używane przez Perla nie są w stanie analizować HTML. Nigdy mnie nie zmusisz. HTML jest językiem o wystarczającej złożoności, którego nie można przeanalizować za pomocą wyrażeń regularnych. Nawet Jon Skeet nie może parsować HTML za pomocą wyrażeń regularnych. Za każdym razem, gdy próbujesz parsować HTML za pomocą wyrażeń regularnych, bezbożne dziecko płacze krwią dziewic, a rosyjscy hakerzy wtłaczają twoją aplikację internetową. Analizowanie HTML za pomocą wyrażeń regularnych przywołuje skażone dusze do świata żywych. HTML i regex idą w parze jak miłość, małżeństwo i rytualne dzieciobójstwo. <center> nie może go utrzymać, jest za późno. Siła wyrażeń regularnych i HTML razem w tej samej przestrzeni koncepcyjnej zniszczy twój umysł jak bardzo wodnisty kit. Jeśli parsujesz HTML z wyrażeniami regularnymi, poddajesz się im i ich bluźnierczym sposobom, które skazują nas wszystkich na nieludzką trud dla Tego, którego imienia nie można wyrazić w Podstawowej Wielojęzycznej Planie, on przychodzi. HTML-plus-regexp zlikwiduje cnoty czujących podczas obserwowania, a twoja psychika usycha w ataku horroru.jest za późno, jest za późno, nie możemy ocalić transekcji dziecka, ponieważ regex pochłonie całą żywą tkankę (z wyjątkiem HTML, którego nie może, jak wcześniej przepowiedziano) drogi panie, pomóż nam, jak ktokolwiek może przetrwać tę plagę używając wyrażeń regularnych do analizy HTML skazał ludzkość na wieczność przerażających tortur i dziur w zabezpieczeniach, używając rege x jako narzędzia do przetwarzania HTML ustanawia przerwę między tym światem a przerażającym królestwem ͒ͪo͛ͫrrupt bytów (takich jak byty SGML, ale bardziej skorumpowane) zaledwie rzut oka na świat reg ex parserami dla HTML będzie ins tantly transportowej ap świadomość rogrammer za I nto aw orl d nieustanny krzyk, przychodziThe epidemiczny sl ithy regex zakażenie wil l pożerać swoim HT ML parsera, stosowanie i egzystencji wszystkim czasu jak Visual Basic tylko gorzej przychodzi on com es nie fi GHT h e przychodzi, hi s Unholy Radiance de stro҉ying wszystko oświecenia, HTML tagi przecieka fr̶ǫm yo ur oczy jak liq uid p Ain, piosenka regularnych exp re ssion parsowania będzie Gaśnice anguish głosy mor tal człowieka z sp tu widzę, to może zobaczyć to jest piękne t on f inal snuffing o f kłamstwo s Man wszystko stracone A ll SLOST p e pony przyjdzie s on Com es Współtworzył ME s t się Ich lub permeat es al l MÓJ Fac god E MY FACE ᵒh n O NO noo O na Θ zatrzymania t on z * ̶͑̾̾ Gl eS ͎a̧͈͖r̽̾̈́͒͑en ot rè̑ͧ̌aͨl̘̝̙̃ͤ͂̾̆ ZA̡͊͠͝LGΌ ISͮ̂҉̯͈͕̹̘̱ T O͇̹̺ͅƝ̴ȳ̳ TH̘ ͖͖͖̉ ͠P̯͍̭O̚ N̐Y̡ H̸̡̪̯ͨ͊̽̅̾̎Ȩ̬̩̾͛ͪ̈́̀́͘ ̶̧̨̱̹̭̯ͧ̾ͬC̷̙̲̝͖ͭ̏ͥͮ͟Oͮ͏̮̪̝͍M̲̖͊̒ͪͩͬ̚̚͜Ȇ̴̟̟͙̞ͩ͌͝ S̨̥̫͎̭ͯ̿̔̀ͅ


Czy zamiast tego próbowałeś użyć parsera XML?


Uwaga moderatora

Ten post jest zablokowany, aby zapobiec niewłaściwym edycjom jego treści. Post wygląda dokładnie tak, jak powinien - nie ma problemów z jego treścią. Proszę nie oznaczać tego dla naszej uwagi.

bobince
źródło
179
Kobi: Myślę, że nadszedł czas, abym zrezygnował ze stanowiska Assistant Don't Parse HTML with Regex Officer. Bez względu na to, ile razy to mówimy, nie przestaną przychodzić każdego dnia ... nawet co godzinę. To przegrana sprawa, o którą ktoś inny może trochę walczyć. Więc kontynuuj, parsuj HTML z regex, jeśli musisz. To tylko zepsuty kod, a nie życie i śmierć.
bobince
27
Czy można użyć RegEx do parsowania tej odpowiedzi?
Chris Porter,
2
Jeśli nie widzisz tego postu, oto zrzut
Andrew Keeton,
3247

Chociaż arbitralny HTML z tylko wyrażeniem regularnym jest niemożliwy, czasem należy go użyć do parsowania ograniczonego, znanego zestawu HTML.

Jeśli masz mały zestaw stron HTML, z których chcesz zeskrobać dane, a następnie wrzucić je do bazy danych, wyrażenia regularne mogą działać poprawnie. Na przykład ostatnio chciałem uzyskać nazwiska, partie i dystrykty australijskich przedstawicieli federalnych, które dostałem ze strony internetowej Parlamentu. To była ograniczona, jednorazowa praca.

Regexy działały dla mnie dobrze i były bardzo szybkie w konfiguracji.

Kaitlin Duck Sherwood
źródło
131
Ponadto, skrobanie dość regularnie sformatowanych danych z dużych dokumentów będzie ZNACZNIE szybsze dzięki rozsądnemu użyciu funkcji skanowania i wyrażenia regularnego niż jakikolwiek inny analizator składni. A jeśli nie masz nic przeciwko kodowaniu wyrażeń regularnych, kodowanie jest szybsze niż kodowanie ścieżek. I prawie na pewno mniej kruche w stosunku do zmian w tym, co skrobasz. Tak bzdurnie.
Michael Johnston,
255
@MichaelJohnston „Mniej kruchy”? Prawie na pewno nie. Regeksy dbają o szczegóły formatowania tekstu, niż parser XML może po cichu zignorować. Zmieniasz &foo;kodowanie i CDATAsekcje? Używasz minimalizatora HTML, aby usunąć wszystkie białe znaki w dokumencie, których przeglądarka nie wyświetla? Parser XML nie będzie się tym przejmował, podobnie jak dobrze napisana instrukcja XPath. Z drugiej strony „parser” oparty na wyrażeniach regularnych ...
Charles Duffy,
41
@CharlesDuffy za zadanie jeden raz jest ok, a na przestrzeni używamy \ s +
quantum
68
@xiaomao rzeczywiście, jeśli muszę znać wszystkie problemy i obejścia, aby uzyskać 80% rozwiązanie, które zawodzi przez resztę czasu „działa dla ciebie”, nie mogę cię powstrzymać. Tymczasem jestem po mojej stronie ogrodzenia, używając parserów, które działają na 100% poprawnego pod względem składniowym XML.
Charles Duffy,
374
Kiedyś musiałem pobrać trochę danych z ~ 10 000 stron, wszystkie z tym samym szablonem HTML. Były zaśmiecone błędami HTML, które powodowały dławiki parserów, a cała ich stylizacja była wbudowana lub zawierała <font>itd .: brak klas lub identyfikatorów ułatwiających poruszanie się po DOM. Po całym dniu walki z „właściwym” podejściem, w końcu przerzuciłem się na rozwiązanie regex i uruchomiłem je w ciągu godziny.
Paul A Jungwirth,
2037

Myślę, że wadą jest to, że HTML to gramatyka Chomsky'ego typu 2 (gramatyka bez kontekstu), a RegEx to gramatyka Chomsky'ego typu 3 (gramatyka zwykła) . Ponieważ gramatyka typu 2 jest zasadniczo bardziej złożona niż gramatyka typu 3 (patrz hierarchia Chomsky'ego ), matematycznie niemożliwe jest parsowanie XML za pomocą RegEx.

Ale wielu będzie próbowało, niektórzy nawet twierdzą, że odnieśli sukces - ale dopóki inni nie odkryją winy i całkowicie nie zepsują cię.

Vlad Gudim
źródło
225
OP prosi o przeanalizowanie bardzo ograniczonego podzbioru tagów XHTML: start. To, co sprawia, że ​​(X) HTML jest CFG, to jego potencjał do posiadania elementów między znacznikami początkowym i końcowym innych elementów (jak w regule gramatycznej A -> s A e). (X) HTML nie ma tej właściwości w znaczniku początkowym: znacznik początkowy nie może zawierać innych znaczników początkowych. Podzbiór, który OP próbuje analizować, nie jest CFG.
LarsH
101
W teorii CS, zwykłe języki ścisłym podzbiorem języków bezkontekstowych, ale implementacje wyrażeń regularnych w głównych językach programowania są potężniejsze. Jak opisuje noulakaz.net/weblog/2007/03/18/… , tak zwane „wyrażenia regularne” mogą sprawdzać liczby pierwsze w jednostkach, co z pewnością jest czymś, czego wyrażenie regularne z teorii CS nie może osiągnąć.
Adam Mihalcin
11
@eyelidlessness: to samo „tylko jeśli” dotyczy wszystkich CFG, prawda? To znaczy, jeśli dane wejściowe HTML (X) nie są poprawnie sformułowane, nawet pełny parser XML nie będzie działał niezawodnie. Być może jeśli podasz przykłady „błędów składni HTML (X) zaimplementowanych w rzeczywistych aplikacjach użytkownika”, o których mówisz, zrozumiem, na czym Ci zależy.
LarsH
82
@AdamMihalcin ma dokładnie rację. Większość istniejących silników wyrażeń regularnych ma większą moc niż gramatyka Chomsky'ego typu 3 (np. Niepochodne dopasowywanie, odnośniki zwrotne). Niektóre silniki wyrażeń regularnych (takie jak Perl) są w pełni Turinga. To prawda, że ​​nawet te są słabymi narzędziami do analizowania HTML, ale ten często cytowany argument nie jest powodem.
dubiousjim
26
To jest najbardziej „pełna i krótka” odpowiedź tutaj. Prowadzi ludzi do nauki podstaw gramatyki formalnej i języków oraz, mam nadzieję, matematyki, więc nie będą marnować czasu na beznadziejne rzeczy, takie jak rozwiązywanie zadań NP w czasie wielomianowym
mishmashru 19.04.13
1332

Nie słuchaj tych facetów. Jesteś całkowicie można analizować gramatyk bezkontekstowych z regex jeśli złamiesz zadanie na mniejsze kawałki. Możesz wygenerować prawidłowy wzorzec za pomocą skryptu, który wykonuje każdy z nich w kolejności:

  1. Rozwiąż problem zatrzymania.
  2. Kwadrat koła.
  3. Rozwiąż problem z podróżującym sprzedawcą w O (log n) lub mniej. Jeśli to więcej, zabraknie pamięci RAM i silnik się zawiesi.
  4. Wzorzec będzie dość duży, więc upewnij się, że masz algorytm, który bezstratnie kompresuje losowe dane.
  5. Prawie tam - po prostu podziel całość przez zero. Bułka z masłem.

Sam jeszcze nie skończyłem ostatniej części, ale wiem, że się zbliżam. Ciągle rzuca CthulhuRlyehWgahnaglFhtagnExceptions z jakiegoś powodu, więc zamierzam przenieść go na VB 6 i użyć On Error Resume Next. Zaktualizuję kod, gdy zbadam te dziwne drzwi, które właśnie otworzyły się w ścianie. Hmm

PS Pierre de Fermat również wymyślił, jak to zrobić, ale margines, w którym pisał, nie był wystarczająco duży dla kodu.

Justin Morgan
źródło
80
Dzielenie przez zero jest znacznie łatwiejszym problemem niż inne, o których wspominasz. Jeśli używasz interwałów zamiast zwykłej arytmetyki zmiennoprzecinkowej (którą wszyscy powinni być, ale nikt nie jest), możesz z przyjemnością podzielić coś przez [przedział zawierający] zero. Wynikiem jest po prostu przedział zawierający plus i minus nieskończoności.
rjmunro
147
Mały margines Fermata został rozwiązany przez miękkie marginesy w nowoczesnym oprogramowaniu do edycji tekstu.
kd4ttc
50
Mały margines Fermata został rozwiązany przez Randall Munroe, ustawiając czcionkę
heltonbiker
29
FYI: Problem Fermata jest faktycznie został rozwiązany w 1995 roku , i to tylko wzięło matematyków 358 lat, aby to zrobić.
jmiserez
10
Byłem w stanie ominąć ten lepki krok dzielenia przez zero, używając zamiast tego zapadek Browna uzyskanych z zimnej fuzji ... chociaż działa to tylko wtedy, gdy usunę stałą kosmologiczną.
Tim Lehner,
1072

Oświadczenie : użyj parsera, jeśli masz taką opcję. To mówi...

Oto wyrażenie, którego używam (!) Do dopasowania tagów HTML:

<(?:"[^"]*"['"]*|'[^']*'['"]*|[^'">])+>

To może nie być idealne, ale uruchomiłem ten kod przez wiele HTML. Pamiętaj, że wyłapuje nawet dziwne rzeczy <a name="badgenerator"">, które pojawiają się w Internecie.

Sądzę, że żeby nie pasowało do niezależnych tagów, możesz użyć negatywnego spojrzenia Kobi :

<(?:"[^"]*"['"]*|'[^']*'['"]*|[^'">])+(?<!/\s*)>

lub po prostu połącz, jeśli nie, a jeśli nie.

Do downvoters: To działa kod z rzeczywistego produktu. Wątpię, aby ktokolwiek czytający tę stronę miał wrażenie, że użycie wyrażeń regularnych w HTML jest społecznie akceptowalne.

Zastrzeżenie : Należy zauważyć, że ten regex nadal rozkłada się w obecności blokach CDATA, komentarze i elementów skryptów i stylów. Dobra wiadomość jest taka, że ​​możesz się pozbyć osób używających wyrażenia regularnego ...

itsadok
źródło
94
Poszedłbym
55
Czy ktoś używa CDATA w HTML?
Danubian Sailor
16
więc tak naprawdę nie rozwiązujesz problemu z analizą tylko wyrażeń regularnych, ale jako część parsera może to działać. PS: działający produkt nie oznacza dobrego kodu. Bez obrazy, ale tak działa programowanie przemysłowe i zarabia pieniądze
mishmashru,
32
Twoje regex nie zaczyna się na bardzo najkrótszym terminem ważności HTML: <!doctype html><title><</title>. Proste '<!doctype html><title><</title>'.match(/<(?:"[^"]*"['"]*|'[^']*'['"]*|[^'">])+>/g)zwroty ["<!doctype html>", "<title>", "<</title>"]powinny ["<title>", "</title>"].
2
jeśli tylko próbujemy dopasować i nie pasować do podanych przykładów, /<.([^r>][^>]*)?>/g działa :-) // javascript: '<p> <a href = "foo"> <br /> <Klasa h = "foo" />'.match(/<.([^r>][^>]*)?>/g)
Imma
506

Są ludzie, którzy powiedzą ci, że Ziemia jest okrągła (a może Ziemia jest spłaszczoną sferoidą, jeśli chcą użyć dziwnych słów). Oni kłamią.

Są ludzie, którzy powiedzą ci, że Wyrażenia regularne nie powinny być rekurencyjne. Ograniczają cię. Muszą cię ujarzmić i robią to, utrzymując cię w ignorancji.

Możesz żyć w ich rzeczywistości lub wziąć czerwoną pigułkę.

Podobnie jak lord marszałek (czy jest krewnym klasy marszałka .NET?), Widziałem Regex-Verse oparty na stosach odwrotnych i wróciłem z wiedzą o mocach , której nie możesz sobie wyobrazić. Tak, myślę, że chronił ich Stary lub Dwa, ale oglądali piłkę nożną w telewizji, więc nie było to trudne.

Myślę, że przypadek XML jest dość prosty. RegEx (w składni .NET), deflowany i kodowany w base64, aby ułatwić zrozumienie słabemu umysłowi, powinien wyglądać mniej więcej tak:

7L0HYBxJliUmL23Ke39K9UrX4HShCIBgEyTYkEAQ7MGIzeaS7B1pRyMpqyqBymVWZV1mFkDM7Z28
995777333nvvvfe6O51OJ/ff/z9cZmQBbPbOStrJniGAqsgfP358Hz8itn6Po9/3eIue3+Px7/3F
86enJ8+/fHn64ujx7/t7vFuUd/Dx65fHJ6dHW9/7fd/t7fy+73Ye0v+f0v+Pv//JnTvureM3b169
OP7i9Ogyr5uiWt746u+BBqc/8dXx86PP7tzU9mfQ9tWrL18d3UGnW/z7nZ9htH/y9NXrsy9fvPjq
i5/46ss3p4z+x3e8b452f9/x93a2HxIkH44PpgeFyPD6lMAEHUdbcn8ffTP9fdTrz/8rBPCe05Iv
p9WsWF788Obl9MXJl0/PXnwONLozY747+t7x9k9l2z/4vv4kqo1//993+/vf2kC5HtwNcxXH4aOf
LRw2z9/v8WEz2LTZcpaV1TL/4c3h66ex2Xv95vjF0+PnX744PbrOm59ZVhso5UHYME/dfj768H7e
Yy5uQUydDAH9+/4eR11wHbqdfPnFF6cv3ogq/V23t++4z4620A13cSzd7O1s/77rpw+ePft916c7
O/jj2bNnT7e/t/397//M9+ibA/7s6ZNnz76PP0/kT2rz/Ts/s/0NArvziYxVEZWxbm93xsrUfnlm
rASN7Hf93u/97vvf+2Lx/e89L7+/FSXiz4Bkd/hF5mVq9Yik7fcncft9350QCu+efkr/P6BfntEv
z+iX9c4eBrFz7wEwpB9P+d9n9MfuM3yzt7Nzss0/nuJfbra3e4BvZFR7z07pj3s7O7uWJM8eCkme
nuCPp88MfW6kDeH7+26PSTX8vu+ePAAiO4LVp4zIPWC1t7O/8/+pMX3rzo2KhL7+8s23T1/RhP0e
vyvm8HbsdmPXYDVhtpdnAzJ1k1jeufOtUAM8ffP06Zcnb36fl6dPXh2f/F6nRvruyHfMd9rgJp0Y
gvsRx/6/ZUzfCtX4e5hTndGzp5jQo9e/z+s3p1/czAUMlts+P3tz+uo4tISd745uJxvb3/v4ZlWs
mrjfd9SG/swGPD/6+nh+9MF4brTBRmh1Tl5+9eT52ckt5oR0xldPzp7GR8pfuXf5PWJv4nJIwvbH
W3c+GY3vPvrs9zj8Xb/147/n7/b7/+52DD2gsSH8zGDvH9+i9/fu/PftTfTXYf5hB+9H7P1BeG52
MTtu4S2cTAjDizevv3ry+vSNb8N+3+/1po2anj4/hZsGt3TY4GmjYbEKDJ62/pHB+3/LmL62wdsU
1J18+eINzTJr3dMvXr75fX7m+MXvY9XxF2e/9+nTgPu2bgwh5U0f7u/74y9Pnh6/OX4PlA2UlwTn
xenJG8L996VhbP3++PCrV68QkrjveITxr2TIt+lL+f3k22fPn/6I6f/fMqZvqXN/K4Xps6sazUGZ
GeQlar49xEvajzI35VRevDl78/sc/b7f6jkG8Va/x52N4L9lBe/kZSh1hr9fPj19+ebbR4AifyuY
12efv5CgGh9TroR6Pj2l748iYxYgN8Z7pr0HzRLg66FnRvcjUft/45i+pRP08vTV6TOe2N/9jv37
R9P0/5YxbXQDeK5E9R12XdDA/4zop+/9Ht/65PtsDVlBBUqko986WsDoWqvbPD2gH/T01DAC1NVn
3/uZ0feZ+T77fd/GVMkA4KjeMcg6RcvQLRl8HyPaWVStdv17PwHV0bOB9xUh7rfMp5Zu3icBJp25
D6f0NhayHyfI3HXHY6YYCw7Pz17fEFhQKzS6ZWChrX+kUf7fMqavHViEPPKjCf1/y5hukcyPTvjP
mHQCppRDN4nbVFPaT8+ekpV5/TP8g/79mVPo77PT1/LL7/MzL7548+XvdfritflFY00fxIsvSQPS
mvctdYZpbt7vxKRfj3018OvC/hEf/79lTBvM3debWj+b8KO0wP+3OeM2aYHumuCAGonmCrxw9cVX
X1C2d4P+uSU7eoBUMzI3/f9udjbYl/el04dI7s8fan8dWRjm6gFx+NrKeFP+WX0CxBdPT58df/X8
DaWLX53+xFdnr06f/szv++NnX7x8fnb6NAhIwsbPkPS7iSUQAFETvP2Tx8+/Og0Xt/yBvDn9vd/c
etno8S+81QKXptq/ffzKZFZ+4e/743e8zxino+8RX37/k595h5/H28+y7fPv490hQdJ349E+txB3
zPZ5J/jsR8bs/y1j2hh/2fkayOqEmYcej0cXUWMN7QrqBwjDrVZRfyQM3xjj/EgYvo4wfLTZrnVS
ebdKq0XSZJvzajKQDUv1/P3NwbEP7cN5+Odivv9/ysPfhHfkOP6b9Fl+91v7LD9aCvp/+Zi+7lLQ
j0zwNzYFP+/Y6r1NcFeDbfBIo8rug3zS3/3WPumPlN3/y8f0I2X3cz4FP+/Y6htSdr2I42fEuSPX
/ewpL4e9/n1evzn94hb+Plpw2+dnbyh79zx0CsPvbq0lb+UQ/h7xvqPq/Gc24PnR18fzVrp8I57d
mehj7ebk5VdPnp+d3GJOSP189eTsaXyk/JV7l98j4SAZgRxtf7x155PR+O6jz36Pw9/1Wz/+e/5u
v//vbsfQAxobws8M9v7xLXp/785/395ED4nO1wx5fsTeH4LnRva+eYY8rpZUBFb/j/jfm8XAvfEj
4/b/ljF1F9B/jx5PhAkp1nu/+y3n+kdZp/93jWmjJ/M11TG++VEG6puZn593PPejoOyHMQU/79jq
GwrKfpSB+tmcwZ93XPkjZffDmIKfd2z1DSm7bmCoPPmjBNT74XkrVf71I/Sf6wTU7XJA4RB+lIC6
mW1+xN5GWw1/683C5rnj/m364cmr45Pf6/SN9H4Us4LISn355vjN2ZcvtDGT6fHvapJcMISmxc0K
MAD4IyP6/5Yx/SwkP360FvD1VTH191mURr/HUY+2P3I9boPnz7Ju/pHrcWPnP3I9/r/L3sN0v52z
0fEgNrgbL8/Evfh9fw/q5Xf93u/97vvf+2Lx/e89L7+/Fe3iZ37f34P5h178kTfx/5YxfUs8vY26
7/d4/OWbb5++ogn7PX5XzOHtOP3GrsHmqobOVO/8Hh1Gk/TPl198QS6w+rLb23fcZ0fMaTfjsv29
7Zul7me2v0FgRoYVURnf9nZEkDD+H2VDf8hjeq8xff1s6GbButNLacEtefHm9VdPXp++CRTw7/v9
r6vW8b9eJ0+/PIHzs1HHdyKE/x9L4Y+s2f+PJPX/1dbsJn3wrY6wiqv85vjVm9Pnp+DgN8efM5va
j794+eb36Xz3mAf5+58+f3r68s230dRvJcxKn/l//oh3f+7H9K2O0r05PXf85s2rH83f/1vGdAvd
w+qBFqsoWvzspozD77EpXYeZ7yzdfxy0ec+l+8e/8FbR84+Wd78xbvn/qQQMz/J7L++GPB7N0MQa
2vTMBwjDrVI0PxKGb4xxfiQMX0cYPuq/Fbx2C1sU8yEF+F34iNsx1xOGa9t6l/yX70uqmxu+qBGm
AxlxWwVS11O97ULqlsFIUvUnT4/fHIuL//3f9/t9J39Y9m8W/Tuc296yUeX/b0PiHwUeP1801Y8C
j/9vz9+PAo8f+Vq35Jb/n0rAz7Kv9aPA40fC8P+RMf3sC8PP08DjR1L3DXHoj6SuIz/CCghZNZb8
fb/Hf/2+37tjvuBY9vu3jmRvxNeGgQAuaAF6Pwj8/+e66M8/7rwpRNj6uVwXZRl52k0n3FVl95Q+
+fz0KSu73/dtkGDYdvZgSP5uskadrtViRKyal2IKAiQfiW+FI+tET/9/Txj9SFf8SFf8rOuKzagx
+r/vD34mUADO1P4/AQAA//8=

Dostępne opcje to RegexOptions.ExplicitCapture . Grupa przechwytywania, której szukasz ELEMENTNAME. Jeśli grupa przechwytywania ERRORnie jest pusta, wystąpił błąd analizy i regex został zatrzymany.

Jeśli masz problemy z przekonwertowaniem go na regex czytelny dla człowieka, powinno to pomóc:

static string FromBase64(string str)
{
    byte[] byteArray = Convert.FromBase64String(str);

    using (var msIn = new MemoryStream(byteArray))
    using (var msOut = new MemoryStream()) {
        using (var ds = new DeflateStream(msIn, CompressionMode.Decompress)) {
            ds.CopyTo(msOut);
        }

        return Encoding.UTF8.GetString(msOut.ToArray());
    }
}

Jeśli nie jesteś pewien, nie, NIE żartuję (ale może kłamię). To będzie działać. Zbudowałem mnóstwo testów jednostkowych, aby to przetestować, a nawet użyłem (części) testów zgodności . Jest to tokenizer, a nie pełnoprawny parser, więc podzieli XML tylko na tokeny składowe. Nie będzie analizować / integrować DTD.

Och ... jeśli chcesz kod źródłowy wyrażenia regularnego, z kilkoma metodami pomocniczymi:

regex, aby tokenizować xml lub pełny zwykły regex

xanatos
źródło
68
Dobry Boże, jest ogromny. Moje największe pytanie brzmi: dlaczego? Zdajesz sobie sprawę, że wszystkie nowoczesne języki mają parsery XML, prawda? Możesz to wszystko zrobić w 3 liniach i mieć pewność, że zadziała. Ponadto, czy też zdać sobie sprawę, że czysta regex jest provably stanie zrobić pewne rzeczy? Chyba że masz hybrydowy parser wyrażeń regularnych / imperatywnych, ale nie wygląda to tak, jak masz. Czy potrafisz również kompresować dane losowe?
Justin Morgan
112
@ Justin Nie potrzebuję powodu. Można to zrobić (i nie było to nielegalne / niemoralne), więc to zrobiłem. Nie ma żadnych ograniczeń dla umysłu, z wyjątkiem tych, które uznajemy (Napoleon Hill) ... Nowoczesne języki potrafią analizować XML? Naprawdę? I myślałem, że TO jest nielegalne! :-)
Xanatos
76
Jestem przekonany. Zamierzam użyć tego kodu jako części jądra mojej maszyny perpetuum mobile - czy wierzysz, że ci głupcy z urzędu patentowego wciąż odrzucają moje zgłoszenie? Pokażę im. Pokażę je wszystkie!
Justin Morgan
31
@Justin Więc parser Xml jest z definicji wolny od błędów, a Regex nie? Ponieważ jeśli parser Xml z definicji nie jest wolny od błędów, może istnieć kod xml, który powoduje awarię, i wracamy do kroku 0. Powiedzmy to: zarówno parser Xml, jak i ten Regex próbują być w stanie przeanalizować wszystkie „legalne” „XML. MOGĄ parsować niektóre „nielegalne” XML. Błędy mogą spowodować awarię obu z nich. C # XmlReader jest z pewnością bardziej przetestowany niż ten Regex.
Xanatos,
31
Nie, nic nie jest wolne od błędów: 1) Wszystkie programy zawierają co najmniej jeden błąd. 2) Wszystkie programy zawierają co najmniej jeden wiersz niepotrzebnego kodu źródłowego. 3) W przypadku nr 1 i nr 2 oraz przy użyciu indukcji logicznej, prostą sprawą jest udowodnienie, że każdy program można sprowadzić do jednego wiersza kodu z błędem. (z Learning Perl)
Scott Weaver
299

W powłoce możesz analizować HTML używając sed :

  1. Turing.sed
  2. Napisz parser HTML (zadanie domowe)
  3. ???
  4. Zysk!

Powiązane (dlaczego nie powinieneś używać dopasowania wyrażenia regularnego):

kenorb
źródło
3
Obawiam się, że nie zrozumiałeś żartu, @kenorb. Przeczytaj jeszcze raz pytanie i zaakceptowaną odpowiedź. Nie chodzi tu w ogóle o narzędzia do analizowania HTML ani o narzędzia powłoki do analizowania HTML, chodzi o analizowanie HTML za pomocą wyrażeń regularnych.
Palec
1
Nie, @Abdul. Jest to całkowicie, możliwe do udowodnienia (w sensie matematycznym) niemożliwe.
Palec
3
Tak, ta odpowiedź dobrze to podsumowuje, @Abdul. Zauważ jednak, że implementacje wyrażeń regularnych nie są tak naprawdę wyrażeniami regularnymi w sensie matematycznym - mają konstrukcje, które czynią je silniejszymi, często pełnymi Turinga (odpowiednik gramatyki typu 0). Argument ten jest zerwany z tym faktem, ale nadal jest w pewnym sensie słuszny w tym sensie, że wyrażenia regularne nigdy nie były przeznaczone do wykonywania takiej pracy.
Palec
2
A tak przy okazji, żart, o którym wspomniałem, był treścią tej odpowiedzi przed (radykalnymi) edycjami kenorba, a konkretnie w wersji 4 @Abdul.
Palec
3
Zabawne jest to, że OP nigdy nie poprosił o parsowanie HTML za pomocą regex. Poprosił o dopasowanie tekstu (którym jest HTML) za pomocą wyrażenia regularnego. Co jest całkowicie rozsądne.
Paralife
274

Zgadzam się, że właściwe narzędzie do analizy XML, a zwłaszcza HTML to analizator składni, a nie silnik wyrażeń regularnych. Jednak, jak zauważyli inni, czasem użycie wyrażenia regularnego jest szybsze, łatwiejsze i wykonuje zadanie, jeśli znasz format danych.

Microsoft faktycznie ma sekcję Najlepszych praktyk dotyczących wyrażeń regularnych w .NET Framework, a konkretnie mówi o rozważaniu źródła wejściowego .

Wyrażenia regularne mają ograniczenia, ale czy bierzesz pod uwagę następujące kwestie?

.NET Framework jest wyjątkowy, jeśli chodzi o wyrażenia regularne, ponieważ obsługuje definicje grup równoważących .

Z tego powodu uważam, że MOŻESZ parsować XML przy użyciu wyrażeń regularnych. Zauważ jednak, że musi to być poprawny XML ( przeglądarki bardzo wybaczają HTML i pozwalają na złą składnię XML wewnątrz HTML ). Jest to możliwe, ponieważ „definicja grupy równoważącej” pozwoli silnikowi wyrażeń regularnych działać jako PDA.

Cytat z artykułu 1 cytowanego powyżej:

Silnik wyrażeń regularnych .NET

Jak opisano powyżej, właściwie zrównoważone konstrukty nie mogą być opisane wyrażeniem regularnym. Jednak silnik wyrażeń regularnych .NET udostępnia kilka konstrukcji, które umożliwiają rozpoznanie konstrukcji zrównoważonych.

  • (?<group>) - wypycha przechwycony wynik na stosie przechwytywania z grupą nazw.
  • (?<-group>) - wyskakuje najwyżej przechwytywanie z grupą nazw ze stosu przechwytywania.
  • (?(group)yes|no) - dopasowuje część tak, jeśli istnieje grupa z nazwą grupy, w przeciwnym razie nie pasuje do żadnej części.

Te konstrukcje pozwalają wyrażeniu regularnemu .NET emulować ograniczony PDA, zasadniczo umożliwiając proste wersje operacji na stosie: push, pop i empty. Proste operacje są w zasadzie równoważne odpowiednio inkrementacji, dekrementacji i porównanie do zera. Pozwala to silnikowi wyrażeń regularnych .NET rozpoznawać podzbiór języków bezkontekstowych, w szczególności tych, które wymagają jedynie prostego licznika. To z kolei pozwala nietradycyjnym wyrażeniom regularnym .NET rozpoznawać poszczególne odpowiednio zrównoważone konstrukcje.

Rozważ następujące wyrażenie regularne:

(?=<ul\s+id="matchMe"\s+type="square"\s*>)
(?>
   <!-- .*? -->                  |
   <[^>]*/>                      |
   (?<opentag><(?!/)[^>]*[^/]>)  |
   (?<-opentag></[^>]*[^/]>)     |
   [^<>]*
)*
(?(opentag)(?!))

Użyj flag:

  • Pojedyncza linia
  • IgnorePatternWhitespace (nie jest konieczne, jeśli zwiniesz wyrażenie regularne i usuniesz wszystkie białe znaki)
  • IgnoreCase (niepotrzebne)

Wyjaśnienie wyrażeń regularnych (wbudowane)

(?=<ul\s+id="matchMe"\s+type="square"\s*>) # match start with <ul id="matchMe"...
(?>                                        # atomic group / don't backtrack (faster)
   <!-- .*? -->                 |          # match xml / html comment
   <[^>]*/>                     |          # self closing tag
   (?<opentag><(?!/)[^>]*[^/]>) |          # push opening xml tag
   (?<-opentag></[^>]*[^/]>)    |          # pop closing xml tag
   [^<>]*                                  # something between tags
)*                                         # match as many xml tags as possible
(?(opentag)(?!))                           # ensure no 'opentag' groups are on stack

Możesz tego spróbować w A Better .NET Regular Expression Tester .

Użyłem przykładowego źródła:

<html>
<body>
<div>
   <br />
   <ul id="matchMe" type="square">
      <li>stuff...</li>
      <li>more stuff</li>
      <li>
          <div>
               <span>still more</span>
               <ul>
                    <li>Another &gt;ul&lt;, oh my!</li>
                    <li>...</li>
               </ul>
          </div>
      </li>
   </ul>
</div>
</body>
</html>

Znaleziono dopasowanie:

   <ul id="matchMe" type="square">
      <li>stuff...</li>
      <li>more stuff</li>
      <li>
          <div>
               <span>still more</span>
               <ul>
                    <li>Another &gt;ul&lt;, oh my!</li>
                    <li>...</li>
               </ul>
          </div>
      </li>
   </ul>

chociaż tak naprawdę wyszło tak:

<ul id="matchMe" type="square">           <li>stuff...</li>           <li>more stuff</li>           <li>               <div>                    <span>still more</span>                    <ul>                         <li>Another &gt;ul&lt;, oh my!</li>                         <li>...</li>                    </ul>               </div>           </li>        </ul>

Wreszcie, naprawdę podobał mi się artykuł Jeffa Atwooda: Parsing Html The Cthulhu Way . Zabawne, że przytacza odpowiedź na to pytanie, które obecnie ma ponad 4 tys. Głosów.

Sam
źródło
18
System.Textnie jest częścią C #. Jest częścią .NET.
John Saunders,
8
W pierwszym wierszu wyrażenia regularnego ( (?=<ul\s*id="matchMe"\s*type="square"\s*>) # match start with <ul id="matchMe"...), pomiędzy „<ul” i „id” powinno być \s+, nie \s*, chyba że chcesz, żeby pasowało <ulid = ...;)
C0deH4cker
@ C0deH4cker Masz rację, wyrażenie powinno mieć \s+zamiast \s*.
Sam
4
Nie, że tak naprawdę to rozumiem, ale myślę, że twój wyrażenie regularne zawiedzie<img src="images/pic.jpg" />
Scheintod
3
@Scheintod Dziękujemy za komentarz. Zaktualizowałem kod. Poprzednie wyrażenie nie powiodło się w przypadku tagów samozamykających się, które miały /gdzieś w środku błąd <img src="images/pic.jpg" />HTML.
Sam
258

Sugeruję użycie QueryPath do analizowania XML i HTML w PHP. Jest to zasadniczo taka sama składnia jak jQuery, tylko po stronie serwera.

John Fiala
źródło
8
@ Kyle - jQuery nie analizuje XML, używa wbudowanego analizatora składni klienta (jeśli taki istnieje). Dlatego nie potrzebujesz do tego jQuery, ale zaledwie dwa wiersze zwykłego starego JavaScript . Jeśli nie ma wbudowanego analizatora składni, jQuery nie pomoże.
RobG
1
@RobG W rzeczywistości jQuery używa DOM, a nie wbudowanego parsera.
Qix - MONICA MISTREATED
11
@ Qix - lepiej powiedz autorom dokumentacji: „ jQuery.parseXML używa natywnej funkcji parsowania przeglądarki… ”. Źródło: jQuery.parseXML ()
RobG
6
Po przyjeździe tutaj z memu pytanie ( meta.stackexchange.com/questions/19478/the-many-memes-of-meta/... ), uwielbiam, że jedną z odpowiedzi jest „Użyj jQuery”
Jorn
221

Chociaż odpowiedzi, których nie można parsować HTML za pomocą wyrażeń regularnych, są poprawne, nie mają tutaj zastosowania. OP chce tylko parsować jeden znacznik HTML z wyrażeniami regularnymi, i można to zrobić za pomocą wyrażenia regularnego.

Sugerowana regex jest nieprawidłowa:

<([a-z]+) *[^/]*?>

Jeśli dodać coś do regex, przez backtracking może być zmuszony dopasować głupie rzeczy, jak <a >>, [^/]jest zbyt liberalne. Zauważ też, że <space>*[^/]*jest zbędny, ponieważ[^/]* może również pasować do spacji.

Moja sugestia byłaby

<([a-z]+)[^>]*(?<!/)>

Gdzie (?<! ... ) jest (w Perl regexes) negatywne spojrzenie wstecz. Odczytuje „a <, następnie słowo, a następnie cokolwiek, co nie jest a, z których ostatnim może nie być /, a następnie>”.

Zauważ, że pozwala to na takie rzeczy <a/ >(jak oryginalne wyrażenie regularne), więc jeśli chcesz czegoś bardziej restrykcyjnego, musisz zbudować wyrażenie regularne w celu dopasowania par atrybutów oddzielonych spacjami.

Moritz
źródło
29
+1 za zauważenie, że pytanie nie dotyczy parsowania pełnego (X) HTML, chodzi o dopasowanie otwartych tagów (X) HTML.
LarsH
10
Coś jeszcze, co większość odpowiedzi wydaje się ignorować, to to, że parser HTML może bardzo dobrze używać wyrażeń regularnych w swojej implementacji dla części HTML i byłbym zaskoczony, gdyby większość parserów tego nie zrobiła.
Thayne
@Thayne Dokładnie. Podczas analizowania pojedynczych znaczników wyrażenie regularne jest właściwym narzędziem dla zadania. To niedorzeczne, że trzeba przewinąć do połowy strony, aby znaleźć rozsądną odpowiedź. Przyjęta odpowiedź jest niepoprawna, ponieważ łączy w sobie leksykację i parsowanie.
kasperd
2
Podana tutaj odpowiedź nie powiedzie się, gdy wartość atrybutu zawiera znak „>” lub „/”.
Martin L
Działa to niepoprawnie w przypadku HTML zawierających komentarze lub sekcje CData. Nie będzie również działać poprawnie, jeśli cytowany atrybut zawiera >znak. Zgadzam się, co sugeruje OP można zrobić za pomocą wyrażenia regularnego, ale ten przedstawiony tutaj jest zbyt uproszczony.
JacquesB
183

Próbować:

<([^\s]+)(\s[^>]*?)?(?<!/)>

Jest podobny do twojego, ale ostatni >nie może być po cięciu, a także akceptuje h1.

Kobi
źródło
107
<a href="foo" title="5> 3 "> Ups </a>
Gareth
21
To bardzo prawda i zastanowiłem się nad tym, ale założyłem, że >symbol właściwie uciekł do & gt ;.
Kobi
65
>jest poprawny w wartości atrybutu. Rzeczywiście, w serializacji „kanoniczny XML” nie można używać &gt;. (Co nie jest do końca istotne, z wyjątkiem podkreślenia, że >wartość atrybutu wcale nie jest niczym niezwykłym).
Bob List
5
@Kobi: co oznacza wykrzyknik (ten, który umieściłeś w kierunku końca) w wyrażeniu regularnym?
Marco Demaio
6
@ Bobince: jesteś pewien? Już nie rozumiem, więc jest to również prawidłowy kod HTML:<div title="this tag is a <div></div>">hello</div>
Marco Demaio
179

Sun Tzu, starożytny chiński strateg, generał i filozof, powiedział:

Mówi się, że jeśli znasz swoich wrogów i znasz siebie, możesz wygrać sto bitew bez żadnej straty. Jeśli znasz tylko siebie, ale nie przeciwnika, możesz wygrać lub przegrać. Jeśli nie znasz ani siebie, ani wroga, zawsze będziesz narażać siebie.

W tym przypadku twoim wrogiem jest HTML i jesteś albo sobą, albo wyrażeniem regularnym. Możesz nawet być Perlem z nieregularnym wyrażeniem regularnym. Zna HTML. Znać siebie.

Skomponowałem haiku opisujące naturę HTML.

HTML has
complexity exceeding
regular language.

Skomponowałem także haiku opisujące naturę wyrażenia regularnego w Perlu.

The regex you seek
is defined within the phrase
<([a-zA-Z]+)(?:[^>]*[^/]*)?>
cytinus
źródło
153
<?php
$selfClosing = explode(',', 'area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed');

$html = '
<p><a href="#">foo</a></p>
<hr/>
<br/>
<div>name</div>';

$dom = new DOMDocument();
$dom->loadHTML($html);
$els = $dom->getElementsByTagName('*');
foreach ( $els as $el ) {
    $nodeName = strtolower($el->nodeName);
    if ( !in_array( $nodeName, $selfClosing ) ) {
        var_dump( $nodeName );
    }
}

Wynik:

string(4) "html"
string(4) "body"
string(1) "p"
string(1) "a"
string(3) "div"

Zasadniczo wystarczy zdefiniować nazwy węzłów elementów, które są samozamykające się, załadować cały ciąg HTML do biblioteki DOM, pobrać wszystkie elementy, przejrzeć i odfiltrować te, które nie są samozamykające się i działać na nich.

Jestem pewien, że już wiesz, że nie powinieneś używać wyrażenia regularnego w tym celu.

meder
źródło
1
Jeśli masz do czynienia z prawdziwym XHTML, dodaj getElementsByTagName NSi określ przestrzeń nazw.
meder omuraliev
148

Nie wiem dokładnie, jak tego potrzebujesz, ale jeśli używasz również platformy .NET, czy nie możesz użyć pakietu HTML Agility Pack ?

Fragment:

Jest to biblioteka kodów .NET, która pozwala na analizowanie plików HTML „poza internetem”. Analizator składni jest bardzo tolerancyjny w przypadku zniekształconego HTML w „świecie rzeczywistym”.

GONeale
źródło
137

Chcesz, aby pierwsza >nie była poprzedzona znakiem /. Sprawdź tutaj, jak to zrobić. Jest to określane jako negatywne spojrzenie.

Jednak naiwna implementacja tego skończy się dopasowaniem <bar/></foo>w tym przykładowym dokumencie

<foo><bar/></foo>

Czy możesz podać trochę więcej informacji na temat problemu, który próbujesz rozwiązać? Czy programowo iterujesz po tagach?

Jherico
źródło
1
Tak, na pewno jestem. Określanie wszystkich aktualnie otwartych znaczników, a następnie porównanie ich z zamkniętymi znacznikami w osobnej tablicy. RegEx boli mój mózg.
Jeff
122

W3C wyjaśnia parsowanie w formie pseudo wyrażenia regularnego:
W3C Link

Poniższe linki VaR QName, Soraz Attributeaby uzyskać jaśniejszy obraz.
Na tej podstawie możesz utworzyć całkiem dobre wyrażenie regularne do obsługi takich rzeczy, jak usuwanie tagów.

John-David Dalton
źródło
5
To nie jest formularz wyrażenia regularnego psuedo, to formularz EBNF, jak określono tutaj: specyfikacja XML, dodatek 6
Rob G
106

Jeśli potrzebujesz tego dla PHP:

W PHP DOM funkcje nie będą działać prawidłowo, jeśli nie jest prawidłowo sformatowany XML. Bez względu na to, o ile lepsze jest ich wykorzystanie dla reszty ludzkości.

simplehtmldom jest dobry, ale uważam, że jest trochę wadliwy, i jest dość obciążony pamięcią [Zawiesza się na dużych stronach.]

Nigdy nie korzystałem z zapytania , więc nie mogę komentować jego przydatności.

Kolejnym do wypróbowania jest mój DOMParser, który jest bardzo lekki w zakresie zasobów i od dłuższego czasu korzystam z niego szczęśliwie. Prosty do nauczenia i potężny.

W przypadku Python i Java opublikowano podobne linki.

Dla downvoterów - napisałem swoją klasę tylko wtedy, gdy parsery XML okazały się niezdolne do wytrzymania rzeczywistego użycia. Religijne wycofywanie głosów po prostu uniemożliwia opublikowanie użytecznych odpowiedzi - proszę, trzymaj sprawy w perspektywie pytania.

SamGoody
źródło
95

Oto rozwiązanie:

<?php
// here's the pattern:
$pattern = '/<(\w+)(\s+(\w+)\s*\=\s*(\'|")(.*?)\\4\s*)*\s*(\/>|>)/';

// a string to parse:
$string = 'Hello, try clicking <a href="#paragraph">here</a>
    <br/>and check out.<hr />
    <h2>title</h2>
    <a name ="paragraph" rel= "I\'m an anchor"></a>
    Fine, <span title=\'highlight the "punch"\'>thanks<span>.
    <div class = "clear"></div>
    <br>';

// let's get the occurrences:
preg_match_all($pattern, $string, $matches, PREG_PATTERN_ORDER);

// print the result:
print_r($matches[0]);
?>

Aby go głęboko przetestować, wprowadziłem tagi automatycznego zamykania łańcucha, takie jak:

  1. <godz />
  2. <br/>
  3. <br>

Wprowadziłem również tagi z:

  1. jeden atrybut
  2. więcej niż jeden atrybut
  3. atrybuty, których wartość jest powiązana albo w cudzysłowie, albo w cudzysłowie
  4. atrybuty zawierające pojedyncze cudzysłowy, gdy separator jest podwójnym cudzysłowem i odwrotnie
  5. atrybuty „bezpretensjonalne” ze spacją przed symbolem „=”, po nim oraz zarówno przed, jak i po nim.

Jeśli znajdziesz coś, co nie działa w powyższym dowodzie koncepcji, jestem dostępny w analizie kodu, aby poprawić swoje umiejętności.

<EDIT> Zapomniałem, że pytaniem użytkownika było uniknięcie parsowania tagów samozamykających się. W tym przypadku wzór jest prostszy, zmieniając się w to:

$pattern = '/<(\w+)(\s+(\w+)\s*\=\s*(\'|")(.*?)\\4\s*)*\s*>/';

Użytkownik @ridgerunner zauważył, że wzorzec nie dopuszcza atrybutów bez cudzysłowu lub atrybutów bez wartości . W takim przypadku dokładne dostrojenie przynosi nam następujący wzór:

$pattern = '/<(\w+)(\s+(\w+)(\s*\=\s*(\'|"|)(.*?)\\5\s*)?)*\s*>/';

</EDIT>

Zrozumienie wzoru

Jeśli ktoś jest zainteresowany uzyskaniem dodatkowych informacji na temat tego wzoru, podaję następujące zdanie:

  1. pierwsze podwyrażenie (\ w +) pasuje do nazwy znacznika
  2. drugie podwyrażenie zawiera wzorzec atrybutu. Składa się z:
    1. co najmniej jedna biała spacja +
    2. nazwa atrybutu (\ w +)
    3. zero lub więcej białych znaków \ ​​(jest to możliwe lub nie, pozostawiając tutaj puste pola)
    4. symbol „=”
    5. ponownie zero lub więcej białych znaków
    6. separator wartości atrybutu, pojedynczy lub podwójny cudzysłów („|”). We wzorcu pojedynczy cudzysłów jest poprzedzany znakami ucieczki, ponieważ pokrywa się z ogranicznikiem łańcucha PHP. To podwyrażenie jest przechwytywane w nawiasach, dzięki czemu można się do niego odwoływać ponownie, aby przeanalizować zamknięcie atrybutu, dlatego jest to bardzo ważne.
    7. wartość atrybutu, dopasowana przez prawie wszystko: (. *?); w tej konkretnej składni, przy użyciu chciwego dopasowania (znak zapytania za gwiazdką) silnik RegExp umożliwia operatorowi wyglądający jak „wybiegający w przyszłość”, który dopasowuje wszystko oprócz tego, co następuje po tym wyrażeniu podrzędnym
    8. nadchodzi zabawa: część \ 4 jest operatorem odniesienia , który odnosi się do podwyrażenia zdefiniowanego wcześniej we wzorcu, w tym przypadku odnoszę się do czwartego podwyrażenia, które jest pierwszym znalezionym ogranicznikiem atrybutu
    9. zero lub więcej białych znaków *
    10. podwyrażenie atrybutu kończy się tutaj, ze specyfikacją zerowego lub więcej możliwych wystąpień, podanych przez gwiazdkę.
  3. Następnie, ponieważ znacznik może kończyć się białymi spacjami przed symbolem „>”, zero lub więcej białych spacji jest dopasowywanych do wzorca \ s *.
  4. Dopasowany tag może kończyć się prostym symbolem „>” lub możliwym zamknięciem XHTML, który korzysta z ukośnika przed nim: (/> |>). Ukośnik jest oczywiście unikany, ponieważ pokrywa się z separatorem wyrażeń regularnych.

Mała wskazówka: aby lepiej przeanalizować ten kod, konieczne jest sprawdzenie wygenerowanego kodu źródłowego, ponieważ nie podałem żadnych znaków specjalnych HTML.

Emanuele Del Grande
źródło
12
Nie pasuje do prawidłowych tagów mających atrybuty bez wartości, tj <option selected>. Nie pasuje również do prawidłowych tagów z niecytowanymi wartościami atrybutów, tj <p id=10>.
ridgerunner
1
@ridgerunner: Bardzo dziękuję za komentarz. W takim przypadku wzorzec musi się nieco zmienić: $ pattern = '/ <(\ w +) (\ s + (\ w +) (\ s * \ = \ s * (\' | "|) (. *?) \\ 5 \ s *)?) * \ S *> / '; Przetestowałem to i działa w przypadku niecytowanych atrybutów lub atrybutów bez wartości
Emanuele Del Grande
Co powiesz na spację przed nazwą znacznika: < a href="http://wtf.org" >Jestem prawie pewien, że jest to zgodne z prawem, ale nie pasujesz.
Floris,
7
NIE przepraszam, białe spacje przed zmienną są nielegalne. Poza tym, że jesteś „całkiem pewien”, dlaczego nie podasz niektórych dowodów swojego sprzeciwu? Oto moje, w3.org/TR/xml11/#sec-starttags odnoszące się do XML 1.1, i możesz znaleźć to samo dla HTML 4, 5 i XHTML, ponieważ walidacja W3C ostrzegłaby również, jeśli wykonasz test. Jako wielu innych bla-bla-poetów tutaj, nie otrzymałem żadnej inteligentnej argumentacji, pomijając kilkaset minusów w stosunku do moich odpowiedzi, aby wykazać, gdzie zawodzi mój kod, zgodnie z zasadami kontraktu określonymi w pytaniu. Chciałbym ich tylko powitać.
Emanuele Del Grande
@ridgerunner oczywiście twój komentarz był inteligentny i mile widziany.
Emanuele Del Grande
91

Ilekroć muszę szybko wyodrębnić coś z dokumentu HTML, używam Tidy do konwersji na XML, a następnie używam XPath lub XSLT, aby uzyskać to, czego potrzebuję. W twoim przypadku coś takiego:

//p/a[@href='foo']
Amal Murali
źródło
89

Wcześniej korzystałem z narzędzia open source o nazwie HTMLParser . Jest zaprojektowany do parsowania HTML na różne sposoby i całkiem dobrze służy temu celowi. Może parsować HTML jako różne treenode i możesz łatwo użyć jego interfejsu API, aby uzyskać atrybuty z węzła. Sprawdź to i sprawdź, czy to może ci pomóc.

wen
źródło
84

Lubię parsować HTML z wyrażeniami regularnymi. Nie próbuję parsować idiotycznego kodu HTML, który został celowo uszkodzony. Ten kod jest moim głównym parserem (edycja Perla):

$_ = join "",<STDIN>; tr/\n\r \t/ /s; s/</\n</g; s/>/>\n/g; s/\n ?\n/\n/g;
s/^ ?\n//s; s/ $//s; print

Nazywa się htmlsplit, dzieli HTML na linie, z jednym znacznikiem lub kawałkiem tekstu w każdej linii. Linie mogą być następnie przetwarzane za pomocą innych narzędzi tekstowych i skryptów, takich jak grep , sed , Perl itp. Nawet nie żartuję :) Ciesz się.

Jeśli chcesz przetwarzać ogromne strony internetowe, łatwo jest przenieść mój skrypt Perla slurp-all-first-Perl do fajnego streamingu. Ale to nie jest naprawdę konieczne.

Założę się, że zostanę za to zlekceważony.

Podział HTML


Wbrew moim oczekiwaniom zyskało to aprobatę, dlatego zaproponuję lepsze wyrażenia regularne:

/(<.*?>|[^<]+)\s*/g    # get tags and text
/(\w+)="(.*?)"/g       # get attibutes

Są dobre dla XML / XHTML.

Z niewielkimi zmianami może poradzić sobie z niechlujnym HTML ... lub najpierw przekonwertować HTML -> XHTML.


Najlepszym sposobem pisania wyrażeń regularnych jest styl Lex / Yacc , a nie jako nieprzejrzyste jednowierszowe lub komentowane wieloliniowe potworności. Jeszcze tego nie zrobiłem; ci ledwie go potrzebują.

Sam Watkins
źródło
35
„Nie próbuję analizować idiotycznego kodu HTML, który został celowo zepsuty”. Skąd twój kod zna różnicę?
Kevin Panko
Cóż, nie ma znaczenia, czy HTML jest uszkodzony, czy nie. Rzecz nadal dzieli HTML na tagi i tekst. Jedyną rzeczą, która mogłaby to zepsuć, jest to, że ludzie dołączają nieoznaczone znaki <lub> w tekście lub atrybutach. W praktyce mój mały rozdzielacz HTML działa dobrze. Nie potrzebuję ogromnego potwornego klocka pełnego heurystyki. Proste rozwiązania nie są dla wszystkich ...!
Sam Watkins,
Dodałem kilka prostszych wyrażeń regularnych do wyodrębniania tagów, tekstu i atrybutów dla XML / XHTML.
Sam Watkins
(get atrybuty błąd 1) /(\w+)="(.*?)"/zakłada podwójne cudzysłowy. Pominie wartości w pojedynczych cudzysłowach. W wersji HTML 4 i wcześniejszych dozwolona jest niecytowana wartość, jeśli jest to proste słowo.
David Andersson,
(get atrybuty błąd 2) /(\w+)="(.*?)"/może fałszywie pasować do tekstu, który wygląda jak atrybut w atrybucie, np <img title="Nope down='up' for aussies" src="..." />. Jeśli zostanie zastosowany globalnie, będzie pasować do takich rzeczy w zwykłym tekście lub w komentarzach HTML.
David Andersson,
74

Oto parser oparty na PHP, który analizuje HTML przy użyciu jakiegoś bezbożnego wyrażenia regularnego. Jako autor tego projektu mogę powiedzieć, że można analizować HTML za pomocą wyrażeń regularnych, ale nie jest to wydajne. Jeśli potrzebujesz rozwiązania po stronie serwera (tak jak ja dla mojej wtyczki WordPress typu WP-Typography ), to działa.

Kingjeffrey
źródło
1
htmlawed to kolejny projekt PHP, który analizuje HTML w celu filtrowania, konwersji itp. Ma jakiś fajny kod, jeśli potrafisz to rozgryźć !
user594694
Nie, nie możesz parsować HTML z regex. Ale w przypadku niektórych podzbiorów może to działać.
mirabilos
71

Istnieje kilka przyjemnych Wyrażenia regularne do zastąpienia HTML z BBCode tutaj . Wszyscy, którzy mówili, zauważcie, że nie próbuje on w pełni parsować HTML-a, tylko go odkażać. Prawdopodobnie może sobie pozwolić na zabicie tagów, których jego prosty „parser” nie może zrozumieć.

Na przykład:

$store =~ s/http:/http:\/\//gi;
$store =~ s/https:/https:\/\//gi;
$baseurl = $store;

if (!$query->param("ascii")) {
    $html =~ s/\s\s+/\n/gi;
    $html =~ s/<pre(.*?)>(.*?)<\/pre>/\[code]$2\[\/code]/sgmi;
}

$html =~ s/\n//gi;
$html =~ s/\r\r//gi;
$html =~ s/$baseurl//gi;
$html =~ s/<h[1-7](.*?)>(.*?)<\/h[1-7]>/\n\[b]$2\[\/b]\n/sgmi;
$html =~ s/<p>/\n\n/gi;
$html =~ s/<br(.*?)>/\n/gi;
$html =~ s/<textarea(.*?)>(.*?)<\/textarea>/\[code]$2\[\/code]/sgmi;
$html =~ s/<b>(.*?)<\/b>/\[b]$1\[\/b]/gi;
$html =~ s/<i>(.*?)<\/i>/\[i]$1\[\/i]/gi;
$html =~ s/<u>(.*?)<\/u>/\[u]$1\[\/u]/gi;
$html =~ s/<em>(.*?)<\/em>/\[i]$1\[\/i]/gi;
$html =~ s/<strong>(.*?)<\/strong>/\[b]$1\[\/b]/gi;
$html =~ s/<cite>(.*?)<\/cite>/\[i]$1\[\/i]/gi;
$html =~ s/<font color="(.*?)">(.*?)<\/font>/\[color=$1]$2\[\/color]/sgmi;
$html =~ s/<font color=(.*?)>(.*?)<\/font>/\[color=$1]$2\[\/color]/sgmi;
$html =~ s/<link(.*?)>//gi;
$html =~ s/<li(.*?)>(.*?)<\/li>/\[\*]$2/gi;
$html =~ s/<ul(.*?)>/\[list]/gi;
$html =~ s/<\/ul>/\[\/list]/gi;
$html =~ s/<div>/\n/gi;
$html =~ s/<\/div>/\n/gi;
$html =~ s/<td(.*?)>/ /gi;
$html =~ s/<tr(.*?)>/\n/gi;

$html =~ s/<img(.*?)src="(.*?)"(.*?)>/\[img]$baseurl\/$2\[\/img]/gi;
$html =~ s/<a(.*?)href="(.*?)"(.*?)>(.*?)<\/a>/\[url=$baseurl\/$2]$4\[\/url]/gi;
$html =~ s/\[url=$baseurl\/http:\/\/(.*?)](.*?)\[\/url]/\[url=http:\/\/$1]$2\[\/url]/gi;
$html =~ s/\[img]$baseurl\/http:\/\/(.*?)\[\/img]/\[img]http:\/\/$1\[\/img]/gi;

$html =~ s/<head>(.*?)<\/head>//sgmi;
$html =~ s/<object>(.*?)<\/object>//sgmi;
$html =~ s/<script(.*?)>(.*?)<\/script>//sgmi;
$html =~ s/<style(.*?)>(.*?)<\/style>//sgmi;
$html =~ s/<title>(.*?)<\/title>//sgmi;
$html =~ s/<!--(.*?)-->/\n/sgmi;

$html =~ s/\/\//\//gi;
$html =~ s/http:\//http:\/\//gi;
$html =~ s/https:\//https:\/\//gi;

$html =~ s/<(?:[^>'"]*|(['"]).*?\1)*>//gsi;
$html =~ s/\r\r//gi;
$html =~ s/\[img]\//\[img]/gi;
$html =~ s/\[url=\//\[url=/gi;
kenorb
źródło
15
Nie rób tego Proszę.
maletor
68

Jeśli chodzi o pytanie o metody RegExp do parsowania (x) HTML, odpowiedź dla wszystkich, którzy mówili o pewnych ograniczeniach, jest następująca: nie zostałeś wystarczająco wyszkolony, aby rządzić siłą tej potężnej broni, ponieważ NIKT nie mówił tutaj o rekurencji .

Pewien kolega z RegExp powiadomił mnie o tej dyskusji, która z pewnością nie jest pierwszą w Internecie na temat tego starego i gorącego tematu.

Po przeczytaniu niektórych postów, pierwszą rzeczą, jaką zrobiłem, było szukanie ciągu „? R” w tym wątku. Drugim było poszukiwanie „rekurencji”.
Nie, święta krowa, nie znaleziono dopasowania.
Ponieważ nikt nie wspomniał o głównym mechanizmie, na którym zbudowany jest parser, wkrótce zdałem sobie sprawę, że nikt nie rozumie.

Jeśli parser (x) HTML wymaga rekursji, parser RegExp bez rekurencji nie wystarczy do tego celu. To prosta konstrukcja.

Czarny RegExp sztuka jest trudna do opanowania , więc może istnieją dalsze możliwości pominęliśmy podczas próby i badania nasze osobiste rozwiązanie, aby uchwycić całą sieć w jednej ręce ... Cóż, jestem pewien o tym :)

Oto magiczny wzór:

$pattern = "/<([\w]+)([^>]*?)(([\s]*\/>)|(>((([^<]*?|<\!\-\-.*?\-\->)|(?R))*)<\/\\1[\s]*>))/s";

Po prostu spróbuj.
Jest napisany jako ciąg PHP, więc modyfikator „s” sprawia, że ​​klasy zawierają znaki nowej linii.
Oto przykładowa notatka do podręcznika PHP napisanego w styczniu: Odniesienie

(Uważaj, w tej notatce niewłaściwie użyłem modyfikatora „m”; powinien on zostać usunięty, mimo że jest odrzucony przez silnik RegExp, ponieważ nie użyto zakotwiczenia ^ ani $).

Teraz możemy mówić o granicach tej metody z bardziej świadomego punktu widzenia:

  1. zgodnie z konkretną implementacją silnika RegExp rekurencja może mieć ograniczenie liczby analizowanych wzorców zagnieżdżonych , ale zależy to od używanego języka
  2. chociaż uszkodzony (x) HTML nie powoduje poważnych błędów, nie jest odkażany .

W każdym razie jest to tylko wzorzec RegExp, ale ujawnia on możliwość opracowania wielu potężnych implementacji.
Napisałem ten wzorzec, aby zasilić parser rekurencyjnego descentowania silnika szablonu, który zbudowałem w swoim frameworku, a jego wydajność jest naprawdę świetna, zarówno w czasie wykonywania, jak i w użyciu pamięci (nie ma to nic wspólnego z innymi silnikami szablonów, które używają tej samej składni).

Emanuele Del Grande
źródło
35
Umieszczę to w koszu „Regex, który nie pozwala na wartości większe niż w atrybutach”. Sprawdź to z <wartość wejściowa = "to 5> 3?" />
Gareth,
68
Jeśli umieścisz coś takiego w kodzie produkcyjnym, opiekun prawdopodobnie zastrzeliłby cię. Jury nigdy by go nie skazało.
aehiilrs
30
Wyrażenia regularne nie mogą działać, ponieważ z definicji nie są rekurencyjne. Dodanie operatora rekurencyjnego do wyrażeń regularnych powoduje, że CFG ma tylko gorszą składnię. Dlaczego nie wykorzystać czegoś, co ma być rekurencyjne, zamiast gwałtownie wstawić rekurencję w coś, co już przepełnione jest obcymi funkcjami?
Welbog
16
Mój sprzeciw nie dotyczy funkcjonalności, lecz zainwestowania czasu. Problem z RegEx polega na tym, że zanim opublikujesz cutsey's little liner, wydaje się, że zrobiłeś coś bardziej wydajnie („Zobacz jedną linię kodu!”). I oczywiście nikt nie wspomina o pół godziny (lub 3), które spędzili z kartą i (miejmy nadzieję) testując każdą możliwą kombinację danych wejściowych. A kiedy miniesz to wszystko, gdy opiekun pójdzie wymyślić lub zweryfikować kod, nie będzie mógł po prostu na niego spojrzeć i przekonać się, że jest poprawny.
Muszą
15
... wiedzieć, że to dobrze. I stanie się to nawet z ludźmi, którzy są dobrzy w wyrażeniach regularnych. I szczerze podejrzewam, że przytłaczająca większość ludzi nie będzie o tym dobrze wiedziała. Więc bierzesz jeden z najbardziej znanych koszmarów związanych z konserwacją i łączysz go z rekurencją, która jest drugim koszmarem związanym z konserwacją, i myślę sobie, że tak naprawdę potrzebuję w moim projekcie kogoś, kto jest mniej sprytny. Celem jest napisanie kodu, który zły programiści mogą utrzymywać bez łamania podstawy kodu. Wiem, że łatwo jest kodować do najmniej powszechnego mianownika. Ale zatrudnienie doskonałego talentu jest trudne, a ty często ...
Oorang,
62

Jak wiele osób już zauważyło, HTML nie jest zwykłym językiem, co może bardzo utrudniać jego analizę. Moim rozwiązaniem jest przekształcenie go w zwykły język za pomocą uporządkowanego programu, a następnie użycie analizatora składni XML do wykorzystania wyników. Jest na to wiele dobrych opcji. Mój program jest napisany przy użyciu Java z biblioteką jtidy, aby przekształcić HTML na XML, a następnie Jaxen na xpath w wynik.

Corey Sanders
źródło
61
<\s*(\w+)[^/>]*>

Części wyjaśnione:

<: postać początkowa

\s*: może zawierać białe spacje przed nazwą znacznika (brzydkie, ale możliwe).

(\w+): tagi mogą zawierać litery i cyfry (h1). Cóż, \wpasuje również do „_”, ale chyba nie zaszkodzi. Jeśli ciekawi, użyj zamiast tego ([a-zA-Z0-9] +).

[^/>]*: cokolwiek oprócz >i /do zamknięcia>

>: zamykanie >

NIE POWIĄZANE

A dla ludzi, którzy nie doceniają wyrażeń regularnych, mówiąc, że są tak potężni jak zwykłe języki:

a n ba n ba n, który nie jest regularny i nawet pozbawiony kontekstu, można dopasować^(a+)b\1b\1$

Odwołanie zwrotne FTW !

daghan
źródło
@GlitchMr, o to mu chodziło. Współczesne wyrażenia regularne nie są technicznie regularne, ani nie ma żadnego powodu.
alanaktion
3
@alanaktion: „Nowoczesne” wyrażenia regularne (czytaj: z rozszerzeniami Perla) nie mogą się zgadzać wewnątrz O(MN)(M oznacza długość wyrażenia regularnego, N oznacza długość tekstu). Odwołania wsteczne są jedną z przyczyn tego. Implementacja w awk nie ma odnośników zwrotnych i dopasowuje wszystko w O(MN)czasie.
Konrad Borowski
56

Jeśli po prostu próbujesz znaleźć te tagi (bez ambicji parsowania), wypróbuj to wyrażenie regularne:

/<[^/]*?>/g

Napisałem to w 30 sekund i przetestowałem tutaj: http://gskinner.com/RegExr/

Pasuje do typów wspomnianych tagów, ignorując typy, które chcesz zignorować.

Lonnie Best
źródło
2
Myślę, że masz na myśli \/>zamiast \\>.
Justin Morgan,
Nie, właśnie \>to miałem na myśli; Nigdy nie zamierzałem edytować wyrażenia regularnego mojego oryginalnego postu.
Lonnie Best
2
Do twojej wiadomości, nie musisz uciekać przed nawiasami kątowymi. Oczywiście ucieczka przed nimi i tak nie szkodzi, ale spójrz na zamieszanie, którego mogłeś uniknąć. ;)
Alan Moore
Czasami niepotrzebnie uciekam, gdy nie jestem pewien, czy coś ma szczególny charakter, czy nie. Zredagowałem odpowiedź; działa tak samo, ale bardziej zwięźle.
Lonnie Best
Patrząc na to teraz, nie wiem, dlaczego myślałem, że miałeś na myśli \/, ponieważ zrobiłoby to dokładnie odwrotność wymagań. Może myślałem, że oferujesz negatywny wzorzec filtra.
Justin Morgan
54

Wydaje mi się, że próbujesz dopasować tagi bez znaku „/” na końcu. Spróbuj tego:

<([a-zA-Z][a-zA-Z0-9]*)[^>]*(?<!/)>
manixrock
źródło
8
To nie działa. Dla danych wejściowych „<xa =" <b> "/> <y>” pasujące są xiy, chociaż x jest zakończone.
ceving
51

Prawdą jest, że podczas programowania najlepiej jest używać dedykowanych analizatorów składni i interfejsów API zamiast wyrażeń regularnych podczas obsługi HTML, szczególnie jeśli dokładność jest najważniejsza (np. Jeśli przetwarzanie może mieć wpływ na bezpieczeństwo). Nie przypisuję jednak poglądu dogmatycznego, że znaczniki w stylu XML nigdy nie powinny być przetwarzane za pomocą wyrażeń regularnych. Zdarzają się przypadki, gdy wyrażenia regularne są doskonałym narzędziem do tego zadania, na przykład podczas jednorazowych edycji w edytorze tekstu, naprawiania uszkodzonych plików XML lub radzenia sobie z formatami plików, które wyglądają, ale nie są całkiem XML. Należy pamiętać o pewnych problemach, ale nie są one nie do pokonania, a nawet niekoniecznie istotne.

Zwykłe wyrażenie regularne <([^>"']|"[^"]*"|'[^']*')*>jest zwykle wystarczające, w przypadkach takich jak te, o których właśnie wspomniałem. Jest to naiwne rozwiązanie, biorąc pod uwagę wszystko, ale poprawnie dopuszcza niezakodowane >symbole w wartościach atrybutów. Jeśli szukasz np. tableTagu, możesz go dostosować jako</?table\b([^>"']|"[^"]*"|'[^']*')*> .

Aby dać wyobrażenie o tym, jak mógłby wyglądać bardziej „zaawansowany” regex HTML, poniższe czynności wykonują dość godne szacowania działania przeglądarki i algorytm analizy HTML5:

</?([A-Za-z][^\s>/]*)(?:=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)|[^>])*(?:>|$)

Poniższa definicja odpowiada dość ścisłej definicji tagów XML (chociaż nie uwzględnia pełnego zestawu znaków Unicode dozwolonych w nazwach XML):

<(?:([_:A-Z][-.:\w]*)(?:\s+[_:A-Z][-.:\w]*\s*=\s*(?:"[^"]*"|'[^']*'))*\s*/?|/([_:A-Z][-.:\w]*)\s*)>

To prawda, że ​​nie uwzględniają kontekstu otaczającego i kilku przypadków krawędzi, ale nawet z takimi rzeczami można sobie poradzić, jeśli naprawdę chcesz (np. Przeszukując dopasowania innego wyrażenia regularnego).

Na koniec użyj najbardziej odpowiedniego narzędzia do zadania, nawet w przypadkach, gdy narzędzie to jest wyrażeniem regularnym.

2 revs
źródło
49

Chociaż nie jest odpowiednie i skuteczne używanie do tego celu wyrażeń regularnych, czasami wyrażenia regularne zapewniają szybkie rozwiązania prostych problemów z dopasowaniem i moim zdaniem używanie wyrażeń regularnych w trywialnych pracach nie jest zbyt trudne.

Istnieje ostateczny post na blogu o dopasowaniu najbardziej wewnętrznych elementów HTML napisany przez Stevena Levithana.

Emre Yazici
źródło