[JS] Wyrażenie regularne wyciągające parametry html'a

0

Witam męcze sie od kilku godzin z zapytaniem wyciągającym parametry z tagów HTML'a tzn

np:
<font color="red" size="5">

Wyrażenie wygląda tak:

var reg_html_parameters = new RegExp('<[^>]*?( .*?=).*?>','gi');
w tym przypadku wyciaga mi tylko pierwszy parametr czyli color=

var reg_html_parameters = new RegExp('<[^>]*( .*?=).*?>','gi');
a w tym przypadku tylko drugi(ostatni) parametr czyli size=

Nie znam sie za bardzo na wyrazeniach regularnych, dlatego pytanie brzmi następująco jak zrobić zeby jednym wyrazeniem dostac wszystkie parametry

</span>
0

Napisz dokładnie co chcesz uzyskać, podając konkretne przykłady. Co rozumiesz przez "jednym wyrazeniem dostac wszystkie parametry"? (tak w ogóle, to masz na myśli atrybuty, a nie parametry) Czy chodzi o to, by dostając ciąg wejściowy:

<font color="red" size="5">

Dopasować następujący ciąg?

color="red" size="5"

Czy chcesz mieć jakby każdy atrybut z osobna? Czy co?</span> Czy parametry zawsze będą zamknięte w cudzysłowy, ew. będą miały proste wartości nie zawierające spacji?

0

Jeśli chodzi ci o pare atrybut="wartosc", to tak chodzilo mi o atrybuty nie parametry..

Nie wiem czy sie tak da ale chce dostać tym wyrażeniem w zmiennej $1 = "color=" a w zmiennej $2 = "size=". Czyli nazwy atrybutów. To czy będą zamkniete w cudzysłowy czy nie nie ma teraz znaczenia bo chodzi mi o pobranie atrybutów spelniających założenia:

  1. Pierwszy znak to spacja
  2. Ostatni znak to znak =

Wiem ze mógłbym rozwiązac to takim sposobem:
var reg_html_atributes = new RegExp('( .*?=)','gi');

ale potrzebuje wyciągnac tylko te dane które są pomiedzy znacznikami < i >.

0

Rozumiem, że najlepiej będzie, gdy wyciągniesz same nazwy, bez początkowej spacji i końcowego znaku równości?

0

W sumie na chwile obecną jest mi to obojętne, ale potrzebuje wyciągnąć wszystkie nazwy atrybutów razem z znakami równości

0

Da się zrobić takie dopasowanie, ale problemem będzie wyciągnięcie tych wszystkich atrybutów. Grupy przechwytujące (nawiasy) nie zadziałają tu jak należy. Wyrażenie regularne wyglądałoby jakoś tak:

/<[^>]+?(\s+([^\s=]+)=.+?)*\s*>/gi

(używam tutaj literału wyrażenia regularnego w formie /tresc_wyrazenia/flagi -- zawsze gdy możesz używaj tego zamiast new RegExp("string_z_trescia_wyrazenia", "flagi") )

Problemem jest jednak wyciągnięcie tych wszystkich atrybutów. Mimo że dopasowane są wszystkie, to w wyniku wykonania funkcji exec dostaniesz tylko ten ostatni.

Rozwiązanie jest takie jak zwykle w programowaniu: podziel trudny problem na prostsze podproblemy. W tym wypadku można wykonać dopasowanie dwuetapowo. Krok 1 to dopasowanie całej listy argumentów zawartej w znakach < i >, czyli dopasowanie np.:

color="red" size="5"

Chodzi tylko o to, by dopasować cały ten fragment ciągu -- na razie olewamy rozbijanie na atrybuty.

Do tego służy krok 2. W nim dopasowujemy w szukanym ciągu poszczególne nazwy atrybutów za pomocą osobnego (również prostego) wyrażenia regularnego z flagą g (np. /[^\s=]+=/g). W tym kroku przydatna będzie funkcja match, która zwróci tablicę wszystkich dopasowań.

Wyglądasz na ogarniętego, więc zostawię Cię na razie z taką podpowiedzią :).

0

Właśnie wcześniej mialem to zrobione na dwa kroki, ale myślalem ze da rade zrobic to w jednym wyrażeniu.. Widze ze jednak nie, a tak btw to czemu lepiej używać literału?

0

Zapis z literałem jest zwięźlejszy, ale ważniejsze jest coś innego. W składni wyrażeń regularnych ukośnik (lewego) \ ma specjalne znaczenie. Np. zapis:

\s

Oznacza klasę znaków zawierającą odstępy (spacje, tabulatory etc. -- s jak [white]space).

Tymczasem w składni stringów w JavaScripcie ukośnik ma swoje własne znaczenie. Generalnie rozpoczyna sekwencję ucieczki, czyli takie znaki jak:

\n (nowy wiersz, znak line feed - LF) 
\r (nowy wiersz, znak carriage return - CR)

No i żeby w stringu w JavaScripcie uzyskać po prostu znak , to musimy go napisać dwa razy. Tak jest w wielu językach. Czyli jeśli w stringu chcemy mieć po prostu ukośnik, to piszemy:

\\

Konstruktor wyrażeń regularnych (RegExp) przyjmuje za argumenty stringi. Więc jeśli chcemy dopasować ciągi spacji, to przy użyciu literału wygląda to normalnie:

var regex = /\s+/;

A przy użyciu konstruktora musimy zastosować podwójny ukośnik. Bo żeby w stringu uzyskać ukośnik, to trzeba go napisać dwa razy:

var regex = new RegExp("\\s+");

Robi się śmieszne, gdy chcemy w wyrażeniu regularnym użyć zwykłego ukośnika bez specjalnego znaczenia. Wtedy zgodnie ze składnią wyrażeń regularnych musimy napisać dwa ukośniki, czyli \. Tak by było w literale, ale w stringu przekazywanym jako parametr konstruktora musielibyśmy za każdym razem napisać ukośnik dwa razy, czyli w sumie \\ (4 ukośniki :) ). To już trochę śmierdzi!

Oczywiście nie zawsze możemy użyć literału. Gdy wyrażenie regularne i/lub jego flagi tworzymy dynamicznie, to musimy użyć konstruktora. W pozostałych przypadkach lepiej używać literału.

0

Dzięki za odpowiedzi :-P przynajmniej "rozwiązalem" problem, teraz wiem ze sie nie da osiągnąć tego czego chce :/

0

@JimMorrison:
Jeśli chcesz wyciągnąć te atrybuty w jednej linijce (tj. pojedynczym regexem), to chyba rzeczywiście się nie da. Ale jeśli chcesz tylko uzyskać jakiś efekt -- obojętnie jakim sposobem -- to jest spora szansa, że jednak się da. Np. za pomocą kosmicznych własności String.replace i potędze JavaScriptu można zdziałać cuda :).

0

Jeszcze mam jedno pytanie dotyczące wyrażenia

Mam takie wyrażenie:
var reg_html_values = /(["|'])[^"]*\1?/gi;

i wszystko działa, tylko ze potrzebuje mieć to ["] zrobione na takiej zasadzie [\1]. A jak tak zrobie to juz mi to nie funkcjonuje nie mam pojecia czemu:

var reg_html_values = /(["|'])[^\1]*\1?/gi;

Próbowalem też tak zrobić:
var reg_html_values = /(["|']).*?\1?/gi;
Efekt taki ze znajduje mi tylko pierwszy cudzyłów lub apostrof

Albo tak:
var reg_html_values = /(["|']).*\1?/gi;
Tutaj efekt jest taki że znajduje mi cały tekst począwszy od pierwszego wystąpienia " lub '

0

Nie możesz użyć odniesień wstecznych wewnątrz klasy znaków ([]). Ale możesz sobie poradzić inaczej:

/('|").*?\1/

Wywaliłem flagi (szczególnie i jest niepotrzebna, g sobie w razie potrzeby wstaw). Zmieniłem trochę początek. Użyłeś klasy ["|'], ale prawdopodobnie to mały bug, bo ta klasa dopasuje cudzysłów ("), znak pałki (pipe: |) lub apostrof ('). Pałka oznacza w wyrażeniach regularnych alternatywę, ale nie wewnątrz klasy znaków :-).

To by się jednak tylko przejawiło gdybyś miał ciąg w stylu color=|red|. Twój problem zaś naprawiłem poprzez zamianę .* na .*?, co umożliwiło mi uproszczenie środka.

.* normalnie zjadłoby wszystkie znaki do końca, aż do zamykającego cudzysłowu/apostrofu i dlatego drugie odniesienie \1 nigdy by nie zadziałało, ale żeby tego uniknąć włączyłem leniwość kwantyfikatora . Włącza się to stawiając znak zapytania zaraz za kwantyfikatorem. Dzięki temu .? będzie co prawda potrafiło zjeść dowolny znak, ale nie będzie się z tym spieszyło. Najpierw spróbuje zjeść zero znaków i zobaczy, czy następna część wyrażenia regularnego (zamykający cudzysłów/apostrof) zostanie dopasowana. Jeśli nie, to zje jeden znak. I znowu nastąpi próba dopasowania następnej części wyrażenia. I tak dalej, aż .*? zje wszystkie znaki z wartości atrybutu i zatrzyma się tuż przed znakiem cudzysłowu/apostrofu, który zostanie zjedzony przez \1 i całe wyrażenie zostanie dopasowane.

Usunąłem kwantyfikator ? z całego końca, bo nie wiedziałem do czego on służy. Jeśli uważasz, że ciąg wejściowy może mieć pominięty cudzysłów na samym końcu, to w końcówce można zamiast \1 wstawić (\1|$), żeby leniwy kwantyfikator *? dożarł się jednak do końca.

Jest też inna opcja zrobienia czegoś takiego. Użycie alternatywy:

/"[^"]*"|'[^']*'/gi

Proste: cudzysłów, ile się da nie-cudzysłowów, cudzysłów. Lub: apostrof, ile się da nie-apostrofów, apostrof. W tym wypadku prawdopodobnie użyłbym właśnie tego prostego wyrażenia. To moje rozwiązanie z leniwym kwantyfikatorem może być wolniejsze. Ale tym się przejmuj dopiero gdy problem wystąpi. Zwykle takie spekulowanie "X będzie wolniejsze niż Y!" i tak prowadzi do fałszywych wniosków :P.

0

Wielkie dzięki!!

Zastosowałem tą drugą opcje, dodałem tylko na końcu pytajniki (bo potrzebuje znalezc wszystkie zamkniete i otwarte cudzysłowy) i działa elegancko:

/"[^"]*"?|'[^']*'?/gi

Kurde ale nie wpadłbym na to;p

Jeszcze raz dzięki

0

Znowu mam problem....

Jak zrobić takie wyrażenie:

var reg_html_tags = /<([^>]*)([\s]*)(>?)/gi;

które szukalo by wszystko pomiedzy < a > , ale znak > nie może znajdować sie w cudzysłowach ani apostrofach?(po prostu byl by pomijany)

0

Ponoć parsowanie HTML-a za pomocą wyrażeń regularnych jest Złą Rzeczą i lepiej skorzystać z parsera, ale widzę, że jeszcze trochę i będziesz miał cały cholerny parser ;-)

To powinno dać radę:

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

Interesujący Cię ciąg siedzi sobie w grupie nr 1.

Z komentarzami i przy możliwości dowolnego wstawiania białych znaków wygląda to tak (w JavaScripcie się nie skompiluje, to tylko dla Ciebie):

/
<
(            # grupa dopasowująca cały tekst pomiędzy < i >
  (?:         # grupa dopasowująca jeden blok tekstu wewnątrz < i >
    [^"'>]     # jeden dowolny znak oprócz cudzysłowu, apostrofu i >
    |          # LUB
    "[^"]*"    # cudzysłów, zero lub więcej nie-cudzysłowów (czyli może tu być znak >), cudzysłów
    |          # LUB
    '[^']*'    # j/w, ale zamiast cudzysłowów są apostrofy
  )*          # dowolna liczba takich grup
)
>
/

Aha, normalnie grupy oznaczasz po prostu nawiasami, o tak:

(zawartość grupy)

Wtedy jeśli to np. grupa nr 3, to tablicy wynikowej np. funkcji exec dostajesz na trzecim miejscu zawartość tej grupy. Ale jeśli zrobisz tak:

(?:zawartość grupy)

To to tylko nawias pomocny przy pisaniu wyrażenia regularnego -- ja użyłem go, by móc odpowiednio doczepić kwantyfikator * -- ale jego zawartość NIE jest zapamiętywana, czy liczona jako grupa w wyniku. Zwie się to grupą nieprzechwytującą. Skoro więc nie potrzebowaliśmy tutaj grupy normalnej (przechwytującej), to zastosowaliśmy nieprzechwytującą dla minimalnego przyspieszenia regexa i oczyszczenia wyniku.

edit: Tak sobie przypomniałem przy okazji tych komentarzy w kodzie. Pisałem, by w JS używać zawsze literału wyrażenia regularnego, a nie konstruktora, gdy tylko to możliwe. Jednym z wyjątków jest sytuacja, gdy wyrażenie trzeba opisać komentarzami. Wtedy można zrobić coś takiego:

var re = new RegExp(
  'część wyrażenia' // zwykłe
+ 'lorem'           // komentarze
+ 'ipsum'           // javascriptowe
, 'i');

Podobnie, czasem warto sobie zdefiniować jakieś podwyrażenia. Np. identyfikator jako "[a-zA-Z_][a-zA-Z_0-9]*". Wtedy można sobie to zapamiętać w stringu i użyć go do konstrukcji kilku innych wyrażeń, co pomaga zachować zasadę DRY. W obu tych przypadkach nie da się użyć literału. Z drugiej strony oba mogą świadczyć o tym, że nasze wyrażenia regularne stały się zbyt skomplikowane.

0

Dzięki za kompletne objaśnienie. Oczywiście zadziałało :-)

Ale pojawił mi sie nowy problem [wstyd]

var reg_php_tags = /(<\?php)(\s+(?:[^"'\?>]*|"[^"]*"?|'[^']*'?)*)(\?>)?/gi;

Chodzi mi w sumie tylko o tą cześć ["'?>] W tym przypadku wykluczam wszystkie znaki po </sup>
ale chodzi mi zeby wykluczyc cudzysłów, apostrof i pare ?>, razem a nie ze kazdy z osobna. Po prostu jesli jest sam ? to przechodzi, jesli samo > to też, ale gdy jest para to nie..

Nie mam pojecia jak to zrobic

0

Całego Ci tym razem nie dam, bo się spieszę i nie mam czasu nawet testować. Dam Ci tylko interesujący Cię fragment, nie sprawdzając czy całość zadziała. Sam będziesz musiał pokombinować.

Do wykluczenia sekwencji ? możesz jednak wykorzystać tzw. negative lookahead (nie znam polskiej nazwy). Czyli:

// dowolny znak nie będący cudzysłowem, apostrofem, ani znakiem ? rozpoczynającym sekwencję
// (unieszkodliwienie \? wewnątrz zakresu nie jest chyba konieczne)
[^"'?]
// dokładamy: LUB znak ? (tu już unieszkodliwiamy ukośnikiem), ale tylko wtedy, gdy NIE ma za nim znaku >
[^"'?]|\?(?!>)

Bajer z lookaheadem polega na tym, że on nie dopasowuje tak naprawdę żadnego znaku. Tj. gdy napiszesz a(?!bc), to dopasuje to literę a jeśli nie ma za nią ciągu (!) liter bc. Ale dopasuje to tylko literę a. Tylko zerknie, czy z przodu nie ma bc, ale tych liter, które tam będą (np. xy) nie zeżre.

Oczywiście cały ten fragment możesz wziąć w nawias (?:fragment) bo widzę że masz tam kwantyfikator jakiś.

0

Wszystko już mi działa, można powiedzieć że troche ogarnąłem te wyrażenia. Jednak nie umiem rozwiązac takiego problemu:

var reg_php_tags = /(<\?php)(\s+(?:(?:[^"'?]|\?(?!>))*|"[^"]*"?|'[^']*'?)*)(\?>)?/gi;

Jest tu wszystko z wczesniejszych założen, tzn tag php to kazdy który:

  1. zaczyna sie <?php 2.konczy sie ?>, ale nie w cudzyslowach ani w apostrofach.

Potrzebuje jeszcze coś na podobizne tego punktu 2, tylko ze chodzi o komentarze dwóch typów:

  1. // -> nowa linia
  2. /* -> */
    Jeśli <?php albo ?> wystąpie w komentarzu to tez ma byc pomijane, ale nie wiem jak to wstawic do tego kodu..
    Znaczy chyba wiem, ale popelniam gdzies blad i mi to nie dziala

wymyślilem coś takiego:

var reg_php_tags = /(<\?php)(\s+(?:(?:[^"'?\/]|\?(?!>)|\/(?!\*))*|(?:\/\*)(?:[^/]|\/(?!\*))*(?:\*\/)?|"[^"]*"?|'[^']*'?)*)(\?>)?/gi;

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

Ale nie wiem dlaczego nigdy mi nie wyłapuje ?>

0

Dobra te wyrażenie opanowałem wszystko jest okej tylko znowu pojawił sie inny problem:

jak wyciagnac tagi html czyli wszystko co jest pomiedzy < >, ale z wyjatkiem ">" '>' i

Mam coś takiego:
var reg_html_tags = /<([^\?](?:(?:[^"'-])*|-->|"[^"]*"?|'[^']*'?)*)(>)?/gi;
ale oczywiscie nie działa jak powinno;|

Nie wiem czy sie da jaki jest znak wczesniej np. jeśli jest znak > to sprawdzic czy wczesniejszy znak to -
?
Nigdzie nie widzialem podobnego regexpa

0

@JimMorrison:
Co Ty w ogóle chcesz zrobić? Czy na pewno nie przesadzasz? Takie parsowanie HTML-a po stronie JavaScriptu naprawdę może zakrawać na przegięcie.

No bo zobacz, co jeśli miałbyś np. taki kod?

<!--
<a>To wcale nie jest tag!</a>
-->
<a rel="przeszkadzajka >">To już jest tag</a>

Wydaje mi się, że podejście do komentarzy HTML, o którym napisałeś, może nie być poprawne. Bo komentarzy wewnątrz tagów nie ma, tzn. nie ma czegoś takiego jak:

<a href="#" <!-- komentarz > dalej komentarz la la la -->>Tekst</a>

^^O ile wiem, to nie jest poprawny HTML! Zresztą czy widziałeś kiedyś taki kod? Komentarze muszą być poza znacznikami otwierającymi i zamykającymi, nie wewnątrz nich. Więc nie musisz się przejmować, że wewnątrz taga otwierającego natrafisz na komentarz. Jeśli chciałbyś to jednak ominąć, to w tych alternatywach miej nie samą końcówkę komentarza "-->", tylko początek, dowolne znaki i końcówkę, czyli "".

Powyższa technika nie ochroni Cię jednak przed jednym -- powyższe wyrażenie regularne będzie pasowało nie tylko do tagów, ale i do komentarzy!

Możesz się przed tym zabezpieczyć wymuszając, by pierwszy znak za < nie był znakiem !. Konstrukcje typu <! ... > są w HTML-u / SGML-u zarezerwowane dla różnych rzeczy, ale nie dla tagów.

Pamiętaj też, że w pewnych przypadkach łatwo jest po prostu wywalić z tekstu komentarze za pomocą osobnego wyrażenia regularnego. Czasem musisz je zostawić, jeśli zależy Ci na wiernym odwzorowaniu kodu, ale czasem możesz sobie pozwolić na ich wywalenie. Niektóre parsery XML/XHTML tak właśnie robią. Od razu ostrzegę, że niektórzy zagnieżdżają w dokumencie skrypty i umieszczają ich kod w komentarzu HTML, "żeby się zabezpieczyć przed starymi przeglądarkami". To prastara i nonsensowna już konstrukcja. Za to jakiś parser XHTML ma prawo wywalić ten ich kod w komentarzu i w ogóle go zignorować.

JimMorrison napisał(a)

Nie wiem czy sie da jaki jest znak wczesniej np. jeśli jest znak > to sprawdzic czy wczesniejszy znak to -

Da się, przy użyciu lookbehindów. (?<=a)b dopasuje "b" wtedy i tylko wtedy, gdy jest poprzedzone przez literę "a". Z kolei (?<!c)d to negatywny lookbehind -- dopasuje d wtedy i tylko wtedy, gdy tuż przed nią NIE ma litery c. Problem w tym, że w JavaScripcie lookbehindów nie ma, są tylko lookaheady.

1 użytkowników online, w tym zalogowanych: 0, gości: 1