Pull to refresh

Named Capturing Group и Backreferences

Reading time 3 min
Views 6.3K
Данная заметка не предназначена для начинающих изучать регулярные выражения, для начинающих я бы посоветовал книгу Ben Forta "Teach Yourself Regular Expressions in 10 Minutes" (ISBN: 0-672-32566-7).

Для тестирования и отладки регулярных выражений идеально подходит программа RegexBuddy (http://www.regexbuddy.com). Чтобы отлаживать следующие примеры нужно во вкладку Test скопировать HTML какой-либо страницы или вбить несколько тегов самим.

Задача — найти в HTML все теги IMG и вытащить из тегов значения атрибутов SRC и ALT.



Первая часть задачи найти все HTML теги решается довольно просто таким регулярным выражением:
<img .*?>

Не забудьте поставить галочки на "Dot matches newline" и "Case insensitive"


Выражение .*? не совсем то, что нам нужно. Внутри тега IMG может оказаться много атрибутов, в любой последовательности, значания атрибутов могут быть заключены в одинарные, двойные кавычки или не заключены в кавычки вообще.

Попробуем для начала отловить атрибут SRC
\s+src\s*=\s*
Выражением отлавливаем предшествующие пробельные символы, а также необязательные пробельные символы до и после знака равно.
В выражении не учтено значение атрибута, которое может быть заключено в одинарные или двойные кавычки.
И тут на помощь нам приходят Backreferences и Named Capturing Group.
\s+src\s*=\s*(?P<qt1>[\"\'])(?P<src>.*?)\k<qt1>


Итак, выражение (?P<qt1>[\"\']) создаёт именованную группу "qt1" в которую входит символ " или '.
Далее идет именованная группа src, куда лениво захватываются все символы до закрывающей кавычки.
Backreference \k<qt1> гарантирует, что закрывающая кавычка будет соответствовать той, что была использована в начале и захвачена под именем qt1.
Заметьте на рисунке как отладчик RegexBuddy выделил более темным цветом символы группы с именем src.

Аналогично можно будет построить регулярку для alt.

Объеденим атрибуты alt, src и все остальные (.*?).
Полученная регулярка выглядит чуть сложной, поэтому сначала пояснения:
выражение (?:) аналогичнa (), с тем различием, что значение внутри первых скобок (?:) не захватывается в результат.

Наша регулярка схематично выглядит так:
<img(?: (?:атрибут src)|(?:атрибут alt)|(?:остальные атрибуты) )*/?>
т.е. поле img могут встретится "атрибут src" или "атрибут alt" или остальные, причем всё это объединено в группу, которая может повторяться несколько раз.
Тег IMG заканчивается необязательным знаком / и следом за ним >
Вот что мы получим:
<img(?:(?:\s+src\s*=\s*(?P<qt1>[\"\'])(?P<src>.*?)\k<qt1>)|(?:\s+alt\s*=\s*(?P<qt2>[\"\'])(?P<alt>.*?)\k<qt2>)|(?:.*?))*/?>


Осталась самая малость. Как быть со случаем, если кавычки не указаны?
в этом случае выражение
\s+src\s*=\s*(?P<qt>[\"\'])(?P<src>.*?)\k<qt>
распадается на 2 варианта
\s+src\s*=\s*(с кавычками|без кавычек)

Итак расширенный вариант, где src значение атрибута src понимается как с кавычками, так и без:
<img(?:(?:\s+src\s*=\s*(?:(?:(?P<qt1>[\"\'])(?P<src>.*?)\k<qt1>)|(?:(?P<src>\S+))))|(?:\s+alt\s*=\s*(?P<qt2>[\"\'])(?P<alt>.*?)\k<qt2>)|(?:.*?))*/?>

Особо въедливым предоставим самим расширить регулярку, чтобы alt также понимался без кавычек. (в таком случае естественно значение не должно содержать пробелы)


UPD: Данная регулярка не претендует на универсальность. Есть вероятность ложных срабатываний внутри закомментированных блоков, кусков javascript, блогов типа PRE, где фактически изображения не выводятся и т.п.
Если парсить страницу целиком, желательно перед этим убрать со страницы скрипты и комменты, блоки PRE (отдельной регуляркой), хотя и это не решит проблем с консткукциями вида
onmouseover=«document.write('<img src=...')»


UPD2: Карма после статьи стала меньше, чем до публикации… Мотивация однако!


UPD3: Перенес в самый подходящий для темы блог, надеюсь никто не возражает :)
Tags:
Hubs:
+48
Comments 46
Comments Comments 46

Articles