Pull to refresh

280 кроказябл или взрывная мощь регулярных выражений

Reading time 4 min
Views 18K
В общем, наверное, как и другой любой начинающий JavaScript прогрммист (2 года назад), мне хотелось все реализовать своими руками. Так возникло ужасающее очень быстрое регулярное выражение из 280 символов.

Немного истории


Приблизительно полтора года назад, я узнал о библиотеке yass, которая была самым быстрым инструментом для поиска DOM элементов в JavaScript по CSS селекторам (ссылка на тесты).
И тут у меня возник ужасный интерес. Я захотел придумать способ, который будет еще быстрее. В то время я как раз читал книгу «Регулярные выражения Библиотека программиста» второе издание от Дж. Фридла. И вот… Это было лето, я еще был студентом и у меня была масса времени. Работа закипела…

Чего шумим?


Статью я решил написать именно из-за следующего выражения, которое умеет почти полностью проанализировать CSS селектор-запрос (даже немного продвинутый, выходящий за рамки стандарта CSS3):
/(?:(?:\s*[+>~,]\s*|\s+)|[^:+>~,\s\\[\]]+(?:\\.[^:+>~,\s\\[\]]*)*)|\[(?:[^\\[\]]*(?:\\.[^\\[\]]*)*|[^=]+=~?\s*(?:"[^\\"]*(?:\\.[^"\\]*)*"|'[^\\']*(?:\\.[^'\\]*)*'))\]|:[^\\:([]+(?:\\.[^\\:([]*)*(?:\((?:[^\\()]*(?:\\.[^\\()]*)*|"[^\\"]*(?:\\.[^"\\]*)*"|'[^\\']*(?:\\.[^'\\]*)*')\))?/g


Давайте дружить


Скажу сразу, что нормальный человек, в таком виде, не поймет в строках выше ничего! Я, относящийся к числу ненормальных, чтобы это написать сделал анализатор регулярных выражений на JavaScript. По сути получилась простая форма: в одно поле — регулярное выражение, в другое — строку для поиска и третье — результат, несколько checkbox-ов.
Напишем это выражение в читабельном виде, используя модификатор «х» (я реализовал бажную его эмуляцию для JavaScript).
(?:
    (?:\s*[+>~,]\s*|\s+)
    |
    [^:+>~,\s\\[\]]+(?:\\.[^:+>~,\s\\[\]]*)*
)
|
\[(?:
    [^\\[\]]*(?:\\.[^\\[\]]*)*
    |
    [^=]+=~?\s*
    (?:
        "[^\\"]*(?:\\.[^"\\]*)*"
        |
        '[^\\']*(?:\\.[^'\\]*)*'
    )
)\]
|
:[^\\:([]+(?:\\.[^\\:([]*)*
(?:
    \((?:
        [^\\()]*(?:\\.[^\\()]*)*
        |
        "[^\\"]*(?:\\.[^"\\]*)*"
        |
        '[^\\']*(?:\\.[^'\\]*)*'
    )\)
)?



Немного теории


Сразу чтобы было понятно и я не писал для себя или гуру регулярных выражений скажу, что в этом выражении очень часть повторяется конструкция вида «начало (нормальные символы)*(спец символы (нормальные символы)*)* конец». Это почти универсальная конструкция нахождения чего-либо между какими-то символами, например, поиск текста между кавычками и допускаются вложенные кавычки с учетом экранирования. Более детальную информацию можно найти в выше упомянутой книге, в разделе «Построение эффективных регулярных выражений».
В нашем случае это касается поиска текста между кавычками (" и '), круглыми и квадратными скобками, а также символами "+", ">", "~", ",", " ", ":".

Анализируем


Основой построения этого выражения является возможность разбить CSS селектор на части. Я разбил его так:
  • где будем искать (среди детей 1 поколения, соседей или же во всем дереве, то есть + > ~, _пробел_)
  • псевдо селектор, вида ":some_function(some_arguments)"
  • селектор по атрибутам, вида "[someAttr(some expression like =)someValue]"

Сопоставим теперь все это с выражением.
Первая часть ищет или "+", ">", ",", "~", "\s+", если не находит, тогда ищем все что между ними.
Вторая — обрабатывает квадратные скобки. Шаблон "[^=]+=~?\s*" был построен для того, чтобы можно было делать поиск по атрибут селекторам, с помощью сколь угодно сложных регулярных выражений.
Третья — находит совпадения для псевдо селектора, при чем круглые скобки ставить не обязательно.
Все символы можно экранировать с помощью обратного слэша ("\") или же взять выражение в одинарные или же двойные кавычки, тогда они не будут восприняты как управляющие.

Заключение


Думаю дальше уже понятно насколько просто написать парсер CSS3 селекторов. Кому интересно поэкспериментировать — идите сюда. Буду очень благодарен, если кто-то выскажется на счет улучшения скорости роботы или строгости регулярного выражения.
Ну и конечно же, большое спасибо Дж. Фридлу, автору серии бесценных книг по регулярным выражениям

P.S.: извиняюсь за бажность анализатора регулярных выражений. Он был создан как промежуточный этап (работает в Chrome и FF точно). Если что-то не работает, там стоит callback на событие change, нажимайте на checkbox-и или просто вставте пробел в поле с регулярным выражением, должно запустится.
Tags:
Hubs:
+96
Comments 68
Comments Comments 68

Articles