final cleanup regexps
|
@ -0,0 +1,104 @@
|
|||
# Паттерны и флаги
|
||||
|
||||
Регулярные выражения –- мощное средство поиска и замены в строке.
|
||||
|
||||
В JavaScript регулярные выражения реализованы отдельным объектом `RegExp` и интегрированы в методы строк.
|
||||
[cut]
|
||||
|
||||
## Регэкспы
|
||||
|
||||
Регулярное выражение (оно же "регэксп", "регулярка" или просто "рег"), состоит из *паттерна* (он же "шаблон") и необязательных *флагов*.
|
||||
|
||||
Синтаксис создания регулярного выражения:
|
||||
|
||||
```js
|
||||
var regexp = new RegExp("шаблон", "флаги");
|
||||
```
|
||||
|
||||
Как правило, используют более короткую запись: шаблон внутри слешей `"/"`:
|
||||
|
||||
```js
|
||||
var regexp = /шаблон/; // без флагов
|
||||
var regexp = /шаблон/gmi; // с флагами gmi (изучим их дальше)
|
||||
```
|
||||
|
||||
Слэши `"/"` говорят JavaScript о том, что это регулярное выражение. Они играют здесь ту же роль, что и кавычки для обозначения строк.
|
||||
|
||||
## Использование
|
||||
|
||||
Основа регулярного выражения -- паттерн. Это строка, которую можно расширить специальными символами, которые делают поиск намного мощнее.
|
||||
|
||||
В простейшем случае, если флагов и специальных символов нет, поиск по паттерну -- то же самое, что и обычный поиск подстроки:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = "Я люблю JavaScript!"; // будем искать в этой строке
|
||||
|
||||
var regexp = /лю/;
|
||||
alert( str.search(regexp) ); // 2
|
||||
```
|
||||
|
||||
Сравните с обычным поиском:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = "Я люблю JavaScript!";
|
||||
|
||||
var substr = "лю";
|
||||
alert( str.indexOf(substr) ); // 2
|
||||
```
|
||||
|
||||
Как видим, то же самое, разве что для регэкспа использован метод [search](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/search) -- он как раз работает с регулярными выражениями, а для подстроки -- [indexOf](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf).
|
||||
|
||||
Но это соответствие лишь кажущееся. Очень скоро мы усложним регулярные выражения, и тогда увидим, что они гораздо мощнее.
|
||||
|
||||
[smart header="Цветовые обозначения"]
|
||||
Здесь и далее в тексте используется следующая цветовая схема:
|
||||
<ul>
|
||||
<li>регэксп (регулярное выражение) - <code class="pattern">красный</code></li>
|
||||
<li>строка - <code class="subject">синий</code></li>
|
||||
<li>результат - <code class="match">зеленый</code></li>
|
||||
</ul>
|
||||
[/smart]
|
||||
|
||||
## Флаги
|
||||
|
||||
Регулярные выражения могут иметь флаги, которые влияют на поиск.
|
||||
|
||||
В JavaScript их всего три:
|
||||
|
||||
<dl>
|
||||
<dt>`i`</dt>
|
||||
<dd>Если этот флаг есть, то регэксп ищет независимо от регистра, то есть не различает между `А` и `а`.</dd>
|
||||
<dt>`g`</dt>
|
||||
<dd>Если этот флаг есть, то регэксп ищет все совпадения, иначе -- только первое.</dd>
|
||||
<dt>`m`</dt>
|
||||
<dd>Многострочный режим.</dd>
|
||||
</dl>
|
||||
|
||||
Самый простой для понимания из этих флагов -- безусловно, `i`.
|
||||
|
||||
Пример его использования:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = "Я люблю JavaScript!"; // будем искать в этой строке
|
||||
|
||||
alert( str.search( *!*/ЛЮ/*/!* ) ); // -1
|
||||
alert( str.search( *!*/ЛЮ/i*/!* ) ); // 2
|
||||
```
|
||||
|
||||
<ol>
|
||||
<li>С регом <code class="pattern">/ЛЮ/</code> вызов вернул `-1`, что означает "не найдено" (как и в `indexOf`),</li>
|
||||
<li>С регом <code class="pattern">/ЛЮ/i</code> вызов нашёл совпадение на позиции 2, так как стоит флаг `i`, а значит "лю" тоже подходит.</li>
|
||||
</ol>
|
||||
|
||||
Другие флаги мы рассмотрим в последующих главах.
|
||||
|
||||
## Итого
|
||||
|
||||
<ul>
|
||||
<li>Регулярное выражение состоит из шаблона и необязательных флагов `g`, `i` и `m`.</li>
|
||||
<li>Поиск по регулярному выражению без флагов и спец. символов, которые мы изучим далее -- это то же самое, что и обычный поиск подстроки в строке. Но флаги и спец. символы, как мы увидим далее, могут сделать его гораздо мощнее.</li>
|
||||
<li>Метод строки `str.search(regexp)` возвращает индекс, на котором найдено совпадение.</li>
|
||||
</ul>
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
Нам нужна строка, которая начинается -- и тут же кончается. То есть, пустая.
|
||||
|
||||
Или, если быть ближе к механике регэкспов, то движок сначала будет искать в тексте начальную позицию ``pattern`^`, а как только найдёт её -- будет ожидать конечной ``pattern`$`.
|
||||
|
||||
Заметим, что и ``pattern`^` и ``pattern`$` не требуют наличия символов. Это -- проверки. В пустой строке движок сначала проверит первую, а потом -- вторую -- и зафиксирует совпадение.
|
|
@ -0,0 +1,4 @@
|
|||
# Регэксп ^$
|
||||
|
||||
Предложите строку, которая подойдёт под регулярное выражение ``pattern`^$`.
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
Двузначное шестнадцатиричное число -- это ``pattern`[0-9a-f]{2}` (с учётом флага ``pattern`/i`).
|
||||
|
||||
Нам нужно одно такое число, и за ним ещё 5 с двоеточиями перед ними: ``pattern`[0-9a-f]{2}(:[0-9a-f]{2}){5}`
|
||||
|
||||
И, наконец, совпадение должно начинаться в начале строки и заканчиваться -- в конце. То есть, строка целиком должна подходить под шаблон. Для этого обернём шаблон в ``pattern`^...$`.
|
||||
|
||||
Итого, в действии:
|
||||
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var re = /^[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}$/i;
|
||||
|
||||
alert( re.test('01:32:54:67:89:AB') ); // true
|
||||
|
||||
alert( re.test('0132546789AB') ); // false (нет двоеточий)
|
||||
|
||||
alert( re.test('01:32:54:67:89') ); // false (5 чисел, а не 6)
|
||||
|
||||
alert( re.test('01:32:54:67:89:ZZ') ) // false (ZZ в конце)
|
||||
```
|
|
@ -0,0 +1,20 @@
|
|||
# Проверьте MAC-адрес
|
||||
|
||||
MAC-адрес сетевого интерфейса состоит из шести двузначиных шестандцатиричных чисел, разделённых двоеточием.
|
||||
|
||||
Например: ``subject`'01:32:54:67:89:AB'`.
|
||||
|
||||
Напишите регулярное выражение, которое по строке проверяет, является ли она корректным MAC-адресом.
|
||||
|
||||
Использование:
|
||||
```js
|
||||
var re = ваш регэксп
|
||||
|
||||
alert( re.test('01:32:54:67:89:AB') ); // true
|
||||
|
||||
alert( re.test('0132546789AB') ); // false (нет двоеточий)
|
||||
|
||||
alert( re.test('01:32:54:67:89') ); // false (5 чисел, а не 6)
|
||||
|
||||
alert( re.test('01:32:54:67:89:ZZ') ) // false (ZZ в конце)
|
||||
```
|
|
@ -0,0 +1,68 @@
|
|||
# Начало строки ^ и конец $
|
||||
|
||||
Знак каретки <code class="pattern">'^'</code> и доллара <code class="pattern">'$'</code> имеют в регулярном выражении особый смысл. Их называют "якорями" (anchor - англ.).
|
||||
[cut]
|
||||
|
||||
Каретка <code class="pattern">^</code> совпадает в начале текста, а доллар <code class="pattern">$</code> -- в конце.
|
||||
|
||||
**Якоря являются не символами, а проверками.**
|
||||
|
||||
До этого мы говорили о регулярных выражениях, которые ищут один или несколько символов. Если совпадение есть -- эти символы включаются в результат.
|
||||
|
||||
А якоря -- не такие. Когда поиск ходит до якоря -- он проверяет, есть ли соответствие, если есть -- продолжает идти по шаблону, не прибавляя ничего к результату.
|
||||
|
||||
Каретку <code class="pattern">^</code> обычно используют, чтобы указать, что регулярное выражение необходимо проверить именно с начала текста.
|
||||
|
||||
Например, без каретки найдёт все числа:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = '100500 попугаев съели 500100 бананов!';
|
||||
alert( str.match(/\d+/ig) ); // 100500, 500100 (нашло все числа)
|
||||
```
|
||||
|
||||
А с кареткой -- только первое:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = '100500 попугаев съели 500100 бананов!';
|
||||
alert( str.match(/^\d+/ig) ); // 100500 (только в начале строки)*!*
|
||||
```
|
||||
|
||||
Знак доллара <code class="pattern">$</code> используют, чтобы указать, что паттерн должен заканчиваться в конце текста.
|
||||
|
||||
Аналогичный пример с долларом для поиска числа в конце:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = '100500 попугаев съели 500100';
|
||||
alert( str.match(/\d+$/ig) ); // 500100
|
||||
```
|
||||
|
||||
Оба якоря используют одновременно, если требуется, чтобы шаблон охватывал текст с начала и до конца. Обычно это требуется при валидации.
|
||||
|
||||
Например, мы хотим проверить, что в переменной `num` хранится именно десятичная дробь.
|
||||
|
||||
Ей соответствует регэксп <code class="pattern">\d+\.\d+</code>. Но простой поиск найдёт дробь в любом тексте:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var num = "ля-ля 12.34";
|
||||
alert( num.match(/\d+\.\d+/ig) ); // 12.34
|
||||
```
|
||||
|
||||
Наша же задача -- проверить, что `num` *целиком* соответствует паттерну <code class="pattern">\d+\.\d+</code>.
|
||||
|
||||
Для этого обернём шаблон в якоря <code class="pattern">^...$</code>:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var num = "ля-ля 12.34";
|
||||
alert( num.match(/^\d+\.\d+$/ig) ); // null, не дробь
|
||||
|
||||
var num = "12.34";
|
||||
alert( num.match(/^\d+\.\d+$/ig) ); // 12.34, дробь!
|
||||
```
|
||||
|
||||
Теперь поиск ищет начало текста, за которым идёт число, затем точка, ещё число и конец текста. Это как раз то, что нужно.
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
# Многострочный режим, флаг "m"
|
||||
|
||||
Многострочный режим включается, если у регэкспа есть флаг <code class="pattern">/m</code>.
|
||||
[cut]
|
||||
|
||||
В этом случае изменяется поведение <code class="pattern">^</code> и <code class="pattern">$</code>.
|
||||
|
||||
В многострочном режиме якоря означают не только начало/конец текста, но и начало/конец строки.
|
||||
|
||||
## Начало строки ^
|
||||
|
||||
В примере ниже текст состоит из нескольких строк. Паттерн <code class="pattern">/^\d+/gm</code> берёт число с начала каждой строки:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = '1е место: Винни\n' +
|
||||
'2е место: Пятачок\n' +
|
||||
'33е место: Слонопотам';
|
||||
|
||||
*!*
|
||||
alert( str.match(/^\d+/gm) ); // 1, 2, 33
|
||||
*/!*
|
||||
```
|
||||
|
||||
Обратим внимание -- без флага <code class="pattern">/m</code> было бы найдено только первое число:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = '1е место: Винни\n' +
|
||||
'2е место: Пятачок\n' +
|
||||
'33е место: Слонопотам';
|
||||
|
||||
alert( str.match(/^\d+/g) ); // 1
|
||||
```
|
||||
|
||||
Это потому что в обычном режиме каретка <code class="pattern">^</code> -- это только начало текста, а в многострочном -- начало любой строки.
|
||||
|
||||
Движок регулярных выражений двигается по тексту, и как только видит начало строки, начинает искать там <code class="pattern">\d+</code>.
|
||||
|
||||
## Конец строки $
|
||||
|
||||
Символ доллара <code class="pattern">$</code> ведёт себя аналогично.
|
||||
|
||||
Регулярное выражение <code class="pattern">[а-я]+$</code> в следующем примере находит последнее слово в каждой строке:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = '1е место: Винни\n' +
|
||||
'2е место: Пятачок\n' +
|
||||
'33е место: Слонопотам';
|
||||
|
||||
alert( str.match(/[а-я]+$/gim) ); // Винни,Пятачок,Слонопотам
|
||||
```
|
||||
|
||||
Без флага <code class="pattern">m</code> якорь <code class="pattern">$</code> обозначал бы конец всего текста, и было бы найдено только последнее слово.
|
||||
|
||||
[smart header="Якорь `$` против `\n`"]
|
||||
Для того, чтобы найти конец строки, можно использовать не только `$`, но и символ `\n`.
|
||||
|
||||
Но, в отличие от `$`, символ `\n` во-первых берёт символ в результат, а во-вторых -- не совпадает в конце текста (если, конечно, последний символ -- не конец строки).
|
||||
|
||||
Посмотрим, что будет с примером выше, если вместо <code class="pattern">[а-я]+$</code> использовать <code class="pattern">[а-я]+\n</code>:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = '1е место: Винни\n' +
|
||||
'2е место: Пятачок\n' +
|
||||
'33е место: Слонопотам';
|
||||
|
||||
alert( str.match(/[а-я]+\n/gim) );
|
||||
/*
|
||||
Винни
|
||||
,Пятачок
|
||||
*/
|
||||
```
|
||||
|
||||
Всего два результата: <code class="match">Винни\n</code> (с символом перевода строки) и <code class="match">Пятачок\n</code>. Последнее слово "Слонопотам" здесь не даёт совпадения, так как после него нет перевода строки.
|
||||
[/smart]
|
||||
|
||||
## Итого
|
||||
|
||||
В мультистрочном режиме:
|
||||
<ul>
|
||||
<li>Символ `^` означает начало строки.</li>
|
||||
<li>Символ `$` означает конец строки.</li>
|
||||
</ul>
|
||||
|
||||
Оба символа являются проверками, они не добавляют ничего к результату. Про них также говорят, что "они имеют нулевую длину".
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
# Предпросмотр (неготово)
|
||||
|
||||
Требуется добавить главу про предпросмотр lookahead.
|
||||
|
|
@ -0,0 +1,303 @@
|
|||
# Чёрная дыра бэктрекинга
|
||||
|
||||
Некоторые регулярные выражения, с виду являясь простыми, могут выполняться оооочень долго, и даже "подвешивать" интерпретатор JavaScript.
|
||||
|
||||
Рано или поздно, с этим сталкивается любой разработчик, потому что нечаянно создать такое регулярное выражение -- легче лёгкого.
|
||||
|
||||
Типична ситуация, когда регулярное выражение до поры до времени работает нормально, и вдруг на каком-то тексте как начнёт "подвешивать" интерпретатор и есть 100% процессора.
|
||||
|
||||
Это может стать уязвимостью. Например, если JavaScript выполняется на сервере, то при разборе данных, присланных посетителем, он может зависнуть, если использует подобный регэксп. На клиенте тоже возможно подобное, при использовании регэкспа для подсветки синтаксиса.
|
||||
|
||||
Такие уязвимости "убивали" почтовые сервера и системы обмена сообщениями и до появления JavaScript, и наверно будут "убивать" и после его исчезновения. Так что мы просто обязаны с ними разобраться.
|
||||
|
||||
[cut]
|
||||
|
||||
## Пример
|
||||
|
||||
План изложения у нас будет таким:
|
||||
|
||||
<ol>
|
||||
<li>Сначала посмотрим на проблему в реальной ситуации.</li>
|
||||
<li>Потом упростим реальную ситуацию до "корней" и увидим, откуда она берётся.</li>
|
||||
</ol>
|
||||
|
||||
Рассмотрим, например, поиск по HTML.
|
||||
|
||||
Мы хотим найти теги с атрибутами, то есть совпадения вида <code class="subject"><a href="..." class=doc ...></code>.
|
||||
|
||||
Самый простой способ это сделать -- <code class="pattern"><[^>]*></code>. Но он же и не совсем корректный, так как тег может выглядеть так: <code class="subject"><a test="<>" href="#"></code>. То есть, внутри "закавыченного" атрибута может быть символ `>`. Простейший регэксп на нём остановится и найдёт <code class="match"><a test="<></code>.
|
||||
|
||||
Соответствие:
|
||||
```
|
||||
<[^>]*....>
|
||||
<a test="<>" href="#">
|
||||
```
|
||||
|
||||
А нам нужен весь тег.
|
||||
|
||||
Для того, чтобы правильно обрабатывать такие ситуации, нужно учесть их в регулярном выражении. Оно будет иметь вид <code class="pattern"><тег (ключ=значение)*></code>.
|
||||
|
||||
Если перевести на язык регэкспов, то: <code class="pattern"><\w+(\s*\w+=(\w+|"[^"]*")\s*)*></code>:
|
||||
<ol>
|
||||
<li><code class="pattern"><\w+</code> -- начало тега</li>
|
||||
<li><code class="pattern">(\s*\w+=(\w+|"[^"]*")\s*)*</code> -- произвольное количество пар вида `слово=значение`, где "значение" может быть также словом <code class="pattern">\w+</code>, либо строкой в кавычках <code class="pattern">"[^"]*"</code>.</li>
|
||||
</ol>
|
||||
|
||||
|
||||
Мы пока не учитываем все детали грамматики HTML, ведь строки возможны и в 'одинарных' кавычках, но на данный момент этого достаточно. Главное, что регулярное выражение получилось в меру простым и понятным.
|
||||
|
||||
|
||||
Испытаем полученный регэксп в действии:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var reg = /<\w+(\s*\w+=(\w+|"[^"]*")\s*)*>/g;
|
||||
|
||||
var str='...<a test="<>" href="#">... <b>...';
|
||||
|
||||
alert( str.match(reg) ); // <a test="<>" href="#">, <b>
|
||||
```
|
||||
|
||||
Отлично, всё работает! Нашло как длинный тег <code class="match"><a test="<>" href="#"></code>, так и одинокий <code class="match"><b></code>.
|
||||
|
||||
А теперь -- демонстрация проблемы.
|
||||
|
||||
Если запустить пример ниже, то он может подвесить браузер:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var reg = /<\w+(\s*\w+=(\w+|"[^"]*")\s*)*>/g;
|
||||
|
||||
var str = "<tag a=b a=b a=b a=b a=b a=b a=b a=b \
|
||||
a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b";
|
||||
|
||||
*!*
|
||||
// Этот поиск будет выполняться очень, очень долго
|
||||
alert( str.match(reg) );
|
||||
*/!*
|
||||
```
|
||||
|
||||
Некоторые движки регулярных выражений могут в разумное время разобраться с таким поиском, но большинство -- нет.
|
||||
|
||||
В чём дело? Почему несложное регулярное выражение на такой небольшой строке "виснет" наглухо?
|
||||
|
||||
Упростим ситуацию, удалив тег и возможность указывать строки в кавычках:
|
||||
|
||||
|
||||
```js
|
||||
//+ run
|
||||
// только атрибуты, разделённые пробелами
|
||||
var reg = /<(\s*\w+=\w+\s*)*>/g;
|
||||
|
||||
var str = "<a=b a=b a=b a=b a=b a=b a=b a=b \
|
||||
a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b";
|
||||
|
||||
*!*
|
||||
// Этот поиск будет выполняться очень, очень долго
|
||||
alert( str.match(reg) );
|
||||
*/!*
|
||||
```
|
||||
|
||||
То же самое.
|
||||
|
||||
На этом мы закончим с демонстрацией "практического примера" и перейдём к разбору происходящего.
|
||||
|
||||
## Бектрекинг
|
||||
|
||||
В качестве ещё более простого регулярного выражения, рассмотрим <code class="pattern">(\d+)*$</code>.
|
||||
|
||||
В большинстве движков регэкспов, например в Chrome или IE, этот поиск выполняется очень долго (осторожно, может "подвесить" браузер):
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( '12345678901234567890123456789123456789z'.match(/(\d+)*$/) );
|
||||
```
|
||||
|
||||
В чём же дело, что не так с регэкспом?
|
||||
|
||||
Внимательный читатель, посмотрев на него, наверняка удивится, ведь он "какой-то странный". Квантификатор <code class="pattern">*</code> здесь выглядит лишним.
|
||||
|
||||
Если хочется найти число, то с тем же успехом можно искать <code class="pattern">\d+$</code>.
|
||||
|
||||
Да, этот регэксп носит искусственный характер, но, разобравшись с ним, мы поймём и практический пример, данный выше. Причина их медленной работы одинакова.
|
||||
|
||||
В целом, с регэкспом "всё так", синтаксис вполне допустимый. Проблема в том, как выполняется поиск по нему.
|
||||
|
||||
Посмотрим, что происходит при поиске в строке <code class="subject">123456789z</code>:
|
||||
|
||||
<ol>
|
||||
<li>Первым делом, движок регэкспов пытается найти <code class="pattern">\d+</code>. Плюс <code class="pattern">+</code> является жадным по умолчанию, так что он хватает все цифры, какие может:
|
||||
|
||||
```
|
||||
\d+.......
|
||||
(123456789)z
|
||||
```
|
||||
</li>
|
||||
<li>Затем движок пытается применить звёздочку вокруг скобок <code class="pattern">(\d+)*</code>, но больше цифр нет, так что звёздочка не даёт повторений.
|
||||
|
||||
Затем в шаблоне идёт символ конца строки <code class="pattern">$</code>, а в тексте -- символ <code class="subject">z</code>.
|
||||
|
||||
```
|
||||
X
|
||||
\d+........$
|
||||
(123456789)z
|
||||
```
|
||||
Соответствия нет.
|
||||
</li>
|
||||
<li>Так как соответствие не найдено, то "жадный" плюс <code class="pattern">+</code> отступает на один символ (бэктрекинг).
|
||||
|
||||
Теперь `\d+` -- это все цифры, за исключением последней:
|
||||
```
|
||||
\d+.......
|
||||
(12345678)9z
|
||||
```
|
||||
</li>
|
||||
<li>После бэктрекинга, <code class="pattern">\d+</code> содержит всё число, кроме последней цифры. Движок снова пытается найти совпадение, уже с новой позиции (`9`).
|
||||
|
||||
Звёздочка <code class="pattern">(\d+)*</code> теперь может быть применена -- она даёт число <code class="match">9</code>:
|
||||
|
||||
```
|
||||
|
||||
\d+.......\d+
|
||||
(12345678)(9)z
|
||||
```
|
||||
Движок пытается найти `$`, но это ему не удаётся -- на его пути опять `z`:
|
||||
|
||||
```
|
||||
X
|
||||
\d+.......\d+
|
||||
(12345678)(9)z
|
||||
```
|
||||
|
||||
Так как совпадения нет, то поисковой движок отступает назад ещё раз.
|
||||
</li>
|
||||
<li>Теперь первое число <code class="pattern">\d+</code> будет содержать 7 цифр, а остаток строки <code class="subject">89</code> становится вторым <code class="pattern">\d+</code>:
|
||||
|
||||
|
||||
```
|
||||
X
|
||||
\d+......\d+
|
||||
(1234567)(89)z
|
||||
```
|
||||
|
||||
Увы, всё ещё нет соответствия для <code class="pattern">$</code>.
|
||||
|
||||
Поисковой движок снова должен отступить назад. При этом последний жадный квантификатор отпускает символ. В данном случае это означает, что укорачивается второй <code class="pattern">\d+</code>, до одного символа <code class="subject">8</code>, и звёздочка забирает следующий <code class="subject">9</code>.
|
||||
|
||||
|
||||
```
|
||||
X
|
||||
\d+......\d+\d+
|
||||
(1234567)(8)(9)z
|
||||
```
|
||||
</li>
|
||||
<li>...И снова неудача. Второе и третье <code class="pattern">\d+</code> отступили по-максимуму, так что сокращается снова первое число, до <code class="subject">123456</code>, а звёздочка берёт оставшееся:
|
||||
|
||||
```
|
||||
X
|
||||
\d+.......\d+
|
||||
(123456)(789)z
|
||||
```
|
||||
|
||||
Снова нет совпадения. Процесс повторяется, последний жадный квантификатор <code class="pattern">+</code> отпускает один символ (`9`):
|
||||
|
||||
```
|
||||
X
|
||||
\d+.....\d+ \d+
|
||||
(123456)(78)(9)z
|
||||
```
|
||||
</li>
|
||||
|
||||
...И так далее.
|
||||
|
||||
Получается, что движок регулярных выражений перебирает все комбинации из `123456789` и их подпоследовательности. А таких комбинаций очень много.
|
||||
|
||||
На этом месте умный читатель может воскликнуть: "Во всём виноват бэктрекинг? Давайте включим ленивый режим -- и не будет никакого бэктрекинга!"
|
||||
|
||||
Что ж, заменим <code class="pattern">\d+</code> на <code class="pattern">\d+?</code> и посмотрим (аккуратно, может подвесить браузер):
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( '12345678901234567890123456789123456789z'.match(/(\d+?)*$/) );
|
||||
```
|
||||
|
||||
Не помогло!
|
||||
|
||||
**Ленивые регулярные выражения делают то же самое, но в обратном порядке.**
|
||||
|
||||
Просто подумайте о том, как будет в этом случае работать поисковой движок.
|
||||
|
||||
Некоторые движки регулярных выражений содержат хитрые проверки и конечные автоматы, которые позволяют избежать бесконечного перебора или кардинально ускорить его, но все движки и не всегда.
|
||||
|
||||
Возвращаясь к примеру выше -- при поиске <code class="pattern"><(\s*\w+=\w+\s*)*></code> в строке <code class="subject"><a=b a=b a=b a=b</code> происходит то же самое.
|
||||
|
||||
Поиск успешно начинается, выбирается некая комбинация из <code class="pattern">\s*\w+=\w+\s*</code>, которая, так как в конце нет `>`, оказывается не подходящей. Движок честно отступает, пробует другую комбинацию -- и так далее.
|
||||
|
||||
## Что делать?
|
||||
|
||||
Проблема -- в сверхмноговариантном переборе.
|
||||
|
||||
Движок регулярных выражений перебирает кучу возможных вариантов скобок там, где это не нужно.
|
||||
|
||||
Например, в регэкспе <code class="pattern">(\d+)*$</code> нам (людям) очевидно, что в <code class="pattern">(\d+)</code> откатываться не нужно. От того, что вместо одного <code class="pattern">\d+</code> у нас два независимых <code class="pattern">\d+\d+</code>, ничего не изменится.
|
||||
|
||||
Без разницы:
|
||||
|
||||
```
|
||||
\d+........
|
||||
(123456789)z
|
||||
|
||||
\d+...\d+....
|
||||
(1234)(56789)z
|
||||
```
|
||||
|
||||
Если вернуться к более реальному примеру <code class="pattern"><(\s*\w+=\w+\s*)*></code> то
|
||||
cам алгоритм поиска, который у нас в голове, предусматривает, что мы "просто" ищем тег, а потом пары `атрибут=значение` (сколько получится).
|
||||
|
||||
Никакого "отката" здесь не нужно.
|
||||
|
||||
В современных регулярных выражениях для решения этой проблемы придумали "possessive" (сверхжадные? неоткатные? точный перевод пока не устоялся) квантификаторы, которые вообще не используют бэктрегинг.
|
||||
|
||||
То есть, они даже проще, чем "жадные" -- берут максимальное количество символов и всё. Поиск продолжается дальше. При несовпадении никакого возврата не происходит.
|
||||
|
||||
Это, c стороны уменьшает количество возможных результатов, но с другой стороны -- в ряде случаев очевидно, что возврат (уменьшение количество повторений квантификатора) результата не даст. А только потратит время, что как раз и доставляет проблемы. Как раз такие ситуации и описаны выше.
|
||||
|
||||
Есть и другое средство -- "атомарные скобочные группы", которые запрещают перебор внутри скобок, по сути позволяя добиваться того же, что и сверхжадные квантификаторы,
|
||||
|
||||
К сожалению, в JavaScript они не поддерживаются.
|
||||
|
||||
Однако, можно получить подобный эффект при помощи предпросмотра. Подробное описание соответствия с учётом синтаксиса сверхжадных квантификаторов и атомарных групп есть в статьях [Regex: Emulate Atomic Grouping (and Possessive Quantifiers) with LookAhead](http://instanceof.me/post/52245507631/regex-emulate-atomic-grouping-with-lookahead) и [Mimicking Atomic Groups](http://blog.stevenlevithan.com/archives/mimic-atomic-groups), здесь же мы останемся в рамках синтаксиса JavaScript.
|
||||
|
||||
Взятие максимального количества повторений `a+` без отката выглядит так: <code class="pattern">(?=(a+))\1</code>.
|
||||
|
||||
То есть, иными словами, предпросмотр <code class="pattern">?=</code> ищет максимальное количество повторений <code class="pattern">a+</code>, доступных с текущей позиции. А затем они "берутся в результат" обратной ссылкой <code class="pattern">\1</code>. Дальнейший поиск -- после найденных повторений.
|
||||
|
||||
Откат в этой логике принципе не предусмотрен, поскольку предпросмотр "откатываться" не умеет. То есть, если предпросмотр нашёл 5 штук <code class="pattern">a+</code>, и в результате поиск не удался, то он не будет откатываться на 4 повторения. Эта возможность в предпросмотре отсутствует, а в данном случае она как раз и не нужна.
|
||||
|
||||
Исправим регэксп для поиска тега с атрибутами <code class="pattern"><\w+(\s*\w+=(\w+|"[^"]*")\s*)*></code>, описанный в начале главы. Используем предпросмотр, чтобы запретить откат на меньшее количество пар `атрибут=значение`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
// регэксп для пары атрибут=значение
|
||||
var attr = /(\s*\w+=(\w+|"[^"]*")\s*)/
|
||||
|
||||
// используем его внутри регэкспа для тега
|
||||
var reg = new RegExp('<\\w+(?=(' + attr.source + '*))\\1>', 'g');
|
||||
|
||||
var good = '...<a test="<>" href="#">... <b>...';
|
||||
|
||||
var bad = "<tag a=b a=b a=b a=b a=b a=b a=b a=b\
|
||||
a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b a=b";
|
||||
|
||||
alert( good.match(reg) ); // <a test="<>" href="#">, <b>
|
||||
alert( bad.match(reg) ); // null (нет результатов, быстро)
|
||||
```
|
||||
|
||||
Отлично, всё работает! Нашло как длинный тег <code class="match"><a test="<>" href="#"></code>, так и одинокий <code class="match"><b></code>.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 2.4 KiB |
392
10-regular-expressions-javascript/2-regexp-methods/article.md
Normal file
|
@ -0,0 +1,392 @@
|
|||
# Методы RegExp и String
|
||||
|
||||
Регулярные выражения в JavaScript являются объектами класса [RegExp](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/RegExp).
|
||||
|
||||
Кроме того, методы для поиска по регулярным выражениям встроены прямо в обычные строки `String`.
|
||||
|
||||
К сожалению, общая структура встроенных методов слегка запутана, поэтому мы сначала рассмотрим их по отдельности, а затем -- рецепты по решению стандартных задач с ними.
|
||||
|
||||
[cut]
|
||||
|
||||
## str.search(reg)
|
||||
|
||||
Этот метод мы уже видели.
|
||||
|
||||
Он возвращает позицию первого совпадения или `-1`, если ничего не найдено.
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = "Люблю регэкспы я, но странною любовью";
|
||||
|
||||
alert( str.search( *!*/лю/i*/!* ) ); // 0
|
||||
```
|
||||
|
||||
**Ограничение метода `search` -- он всегда ищет только первое совпадение.**
|
||||
|
||||
Нельзя заставить `search` искать дальше первого совпадения, такой синтаксис попросту не предусмотрен. Но есть другие методы, которые это умеют.
|
||||
|
||||
## str.match(reg) без флага g
|
||||
|
||||
Метод `str.match` работает по-разному, в зависимости от наличия или отсутствия флага `g`, поэтому сначала мы разберём вариант, когда его нет.
|
||||
|
||||
В этом случае `str.match(reg)` находит только одно, первое совпадение.
|
||||
|
||||
Результат вызова -- это массив, состоящий из этого совпадения, с дополнительными свойствами `index` -- позиция, на которой оно обнаружено и `input` -- строка, в которой был поиск.
|
||||
|
||||
Например:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = "ОЙ-Ой-ой";
|
||||
|
||||
var result = str.match( *!*/ой/i*/!* );
|
||||
|
||||
alert( result[0] ); // ОЙ (совпадение)
|
||||
alert( result.index ); // 0 (позиция)
|
||||
alert( result.input ); // ОЙ-Ой-ой (вся поисковая строка)
|
||||
```
|
||||
|
||||
У этого массива не всегда только один элемент.
|
||||
|
||||
**Если часть шаблона обозначена скобками, то она станет отдельным элементом массива.**
|
||||
|
||||
Например:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = "javascript - это такой язык";
|
||||
|
||||
var result = str.match( *!*/JAVA(SCRIPT)/i*/!* );
|
||||
|
||||
alert( result[0] ); // javascript (всё совпадение полностью)
|
||||
alert( result[1] ); // script (часть совпадения, соответствующая скобкам)
|
||||
alert( result.index ); // 0
|
||||
alert( result.input ); // javascript - это такой язык
|
||||
```
|
||||
|
||||
Благодаря флагу `i` поиск не обращает внимание на регистр буквы, поэтому находит <code class="match">javascript</code>. При этом часть строки, соответствующая <code class="pattern">SCRIPT</code>, выделена в отдельный элемент массива.
|
||||
|
||||
Позже мы ещё вернёмся к скобочным выражениям, они особенно удобны для поиска с заменой.
|
||||
|
||||
## str.match(reg) с флагом g
|
||||
|
||||
При наличии флага `g`, вызов `match` возвращает обычный массив из всех совпадений.
|
||||
|
||||
Никаких дополнительных свойств у массива в этом случае нет, скобки дополнительных элементов не порождают.
|
||||
|
||||
Например:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = "ОЙ-Ой-ой";
|
||||
|
||||
var result = str.match( *!*/ой/ig*/!* );
|
||||
|
||||
alert( result ); // ОЙ, Ой, ой
|
||||
```
|
||||
|
||||
Пример со скобками:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = "javascript - это такой язык";
|
||||
|
||||
var result = str.match( *!*/JAVA(SCRIPT)/gi*/!* );
|
||||
|
||||
alert( result[0] ); // javascript
|
||||
alert( result.length ); // 1
|
||||
alert( result.index ); // undefined
|
||||
```
|
||||
|
||||
Из последнего примера видно, что элемент в массиве ровно один, и свойства `index` также нет. Такова особенность глобального поиска при помощи `match` -- он просто возвращает все совпадения.
|
||||
|
||||
Для расширенного глобального поиска, который позволит получить все позиции и, при желании, скобки, нужно использовать метод [RegExp#exec](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec), которые будет рассмотрен далее.
|
||||
|
||||
[warn header="В случае, если совпадений не было, `match` возвращает `null`"]
|
||||
Обратите внимание, это важно -- если `match` не нашёл совпадений, он возвращает не пустой массив, а именно `null`.
|
||||
|
||||
Это важно иметь в виду, чтобы не попасть в такую ловушку:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = "Ой-йой-йой";
|
||||
|
||||
// результат match не всегда массив!
|
||||
alert(str.match(/лю/gi).length) // ошибка! нет свойства length у null
|
||||
```
|
||||
[/warn]
|
||||
|
||||
## str.split(reg|substr, limit)
|
||||
|
||||
Разбивает строку в массив по разделителю -- регулярному выражению `regexp` или подстроке `substr`.
|
||||
|
||||
Обычно мы используем метод `split` со строками, вот так:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert('12-34-56'.split('-')) // [12, 34, 56]
|
||||
```
|
||||
|
||||
Можно передать в него и регулярное выражение, тогда он разобьёт строку по всем совпадениям.
|
||||
|
||||
Тот же пример с регэкспом:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert('12-34-56'.split(/-/)) // [12, 34, 56]
|
||||
```
|
||||
|
||||
## str.replace(reg, str|func)
|
||||
|
||||
Швейцарский нож для работы со строками, поиска и замены любого уровня сложности.
|
||||
|
||||
Его простейшее применение -- поиск и замена подстроки в строке, вот так:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
// заменить дефис на двоеточие
|
||||
alert('12-34-56'.replace("-", ":")) // 12:34-56
|
||||
```
|
||||
|
||||
**При вызове со строкой замены `replace` всегда заменяет только первое совпадение.**
|
||||
|
||||
Чтобы заменить *все* совпадения, нужно использовать для поиска не строку `"-"`, а регулярное выражение <code class="pattern">/-/g</code>, причём обязательно с флагом `g`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
// заменить дефис на двоеточие
|
||||
alert( '12-34-56'.replace( *!*/-/g*/!*, ":" ) ) // 12:34:56
|
||||
```
|
||||
|
||||
|
||||
В строке для замены можно использовать специальные символы:
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Спецсимволы</th>
|
||||
<th>Действие в строке замены</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>`$$`</td>
|
||||
<td>Вставляет `"$"`.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`$&`</td>
|
||||
<td>Вставляет всё найденное совпадение.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>$`</code></td>
|
||||
<td>Вставляет часть строки до совпадения.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>$'</code>
|
||||
</td>
|
||||
<td>Вставляет часть строки после совпадения.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>$*n*</code>
|
||||
</td>
|
||||
<td>где `n` -- цифра или двузначное число, обозначает `n-ю` по счёту скобку, если считать слева-направо.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
Пример использования скобок и `$1`, `$2`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = "Василий Пупкин";
|
||||
|
||||
alert(str.replace(/(Василий) (Пупкин)/, '$2, $1')) // Пупкин, Василий
|
||||
```
|
||||
|
||||
Ещё пример, с использованием `$&`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = "Василий Пупкин";
|
||||
|
||||
alert(str.replace(/Василий Пупкин/, 'Великий $&!')) // Великий Василий Пупкин!
|
||||
```
|
||||
|
||||
**Для ситуаций, который требуют максимально "умной" замены, в качестве второго аргумента предусмотрена функция.**
|
||||
|
||||
Она будет вызвана для каждого совпадения, и её результат будет вставлен как замена.
|
||||
|
||||
Например:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var i = 0;
|
||||
|
||||
// заменить каждое вхождение "ой" на результат вызова функции
|
||||
alert("ОЙ-Ой-ой".replace(/ой/gi, function() {
|
||||
return ++i;
|
||||
})); // 1-2-3
|
||||
```
|
||||
|
||||
В примере выше функция просто возвращала числа по очереди, но обычно она основывается на поисковых данных.
|
||||
|
||||
Эта функция получает следующие аргументы:
|
||||
|
||||
<ol>
|
||||
<li>`str` -- найденное совпадение,</li>
|
||||
<li>`p1, p2, ..., pn` -- содержимое скобок (если есть),</li>
|
||||
<li>`offset` -- позиция, на которой найдено совпадение,</li>
|
||||
<li>`s` -- исходная строка.</li>
|
||||
</ol>
|
||||
|
||||
Если скобок в регулярном выражении нет, то у функции всегда будет ровно 3 аргумента: `replacer(str, offset, s)`.
|
||||
|
||||
Используем это, чтобы вывести полную информацию о совпадениях:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
// вывести и заменить все совпадения
|
||||
function replacer(str, offset, s) {
|
||||
alert( "Найдено: " + str + " на позиции: " + offset + " в строке: " + s );
|
||||
return str.toLowerCase();
|
||||
}
|
||||
|
||||
var result = "ОЙ-Ой-ой".replace(/ой/gi, replacer);
|
||||
alert( 'Результат: ' + result ); // Результат: ой-ой-ой
|
||||
```
|
||||
|
||||
С двумя скобочными выражениями -- аргументов уже 5:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
function replacer(str, name, surname, offset, s) {
|
||||
return surname + ", " + name;
|
||||
}
|
||||
|
||||
alert(str.replace(/(Василий) (Пупкин)/, replacer)) // Пупкин, Василий
|
||||
```
|
||||
|
||||
Функция -- это самый мощный инструмент для замены, какой только может быть. Она владеет всей информацией о совпадении и имеет доступ к замыканию, поэтому может всё.
|
||||
|
||||
## regexp.test(str)
|
||||
|
||||
Теперь переходим к методам класса `RegExp`.
|
||||
|
||||
Метод `test` проверяет, есть ли хоть одно совпадение в строке `str`. Возвращает `true/false`.
|
||||
|
||||
Работает, по сути, так же, как и проверка `str.search(reg) != -1`, например:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = "Люблю регэкспы я, но странною любовью";
|
||||
|
||||
// эти две проверки идентичны
|
||||
alert( *!*/лю/i*/!*.test(str) ) // true
|
||||
alert( str.search(*!*/лю/i*/!*) != -1 ) // true
|
||||
```
|
||||
|
||||
Пример с отрицательным результатом:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = "Ой, цветёт калина...";
|
||||
|
||||
alert( *!*/javascript/i*/!*.test(str) ) // false
|
||||
alert( str.search(*!*/javascript/i*/!*) != -1 ) // false
|
||||
```
|
||||
|
||||
## regexp.exec(str)
|
||||
|
||||
Для поиска мы уже видели методы:
|
||||
<ul>
|
||||
<li>`search` -- ищет индекс</li>
|
||||
<li>`match` -- если регэксп без флага `g` -- ищет совпадение с подрезультатами в скобках</li>
|
||||
<li>`match` -- если регэксп с флагом `g` -- ищет все совпадения, но без скобочных групп.</li>
|
||||
</ul>
|
||||
|
||||
Метод `regexp.exec` дополняет их. Он позволяет искать и все совпадения и скобочные группы в них.
|
||||
|
||||
Он ведёт себя по-разному, в зависимости от того, есть ли у регэкспа флаг `g`.
|
||||
|
||||
<ul>
|
||||
<li>Если флага `g` нет, то `regexp.exec(str)` ищет и возвращает первое совпадение, является полным аналогом вызова `str.match(reg)`.</li>
|
||||
<li>Если флаг `g` есть, то вызов `regexp.exec` возвращает первое совпадение и *запоминает* его позицию в свойстве `regexp.lastIndex`. Последующий поиск он начнёт уже с этой позиции. Если совпадений не найдено, то сбрасывает `regexp.lastIndex` в ноль.</li>
|
||||
</ul>
|
||||
|
||||
Это используют для поиска всех совпадений в цикле:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = 'Многое по JavaScript можно найти на сайте http://javascript.ru';
|
||||
|
||||
var regexp = /javascript/ig;
|
||||
|
||||
alert( "Начальное значение lastIndex: " + regexp.lastIndex );
|
||||
|
||||
while (result = regexp.exec(str)) {
|
||||
alert( 'Найдено: ' + result[0] + ' на позиции:' + result.index );
|
||||
alert( 'Свойство lastIndex: ' + regexp.lastIndex );
|
||||
}
|
||||
|
||||
alert( 'Конечное значение lastIndex: ' + regexp.lastIndex );
|
||||
```
|
||||
|
||||
Здесь цикл продолжается до тех пор, пока `regexp.exec` не вернёт `null`, что означает "совпадений больше нет".
|
||||
|
||||
Найденные результаты последовательно помещаются в `result`, причём находятся там в том же формате, что и `match` -- с учётом скобок, со свойствами `result.index` и `result.input`.
|
||||
|
||||
[smart header="Поиск с нужной позиции"]
|
||||
Можно заставить `regexp.exec` искать сразу с нужной позиции, если поставить `lastIndex` вручную:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = 'Многое по JavaScript можно найти на сайте http://javascript.ru';
|
||||
|
||||
var regexp = /javascript/ig;
|
||||
regexp.lastIndex = 40;
|
||||
|
||||
alert( regexp.exec(str).index ); // 49, поиск начат с 40й позиции
|
||||
```
|
||||
[/smart]
|
||||
|
||||
## Итого, рецепты
|
||||
|
||||
Методы становятся гораздо понятнее, если разбить их использование по задачам, которые нужны в реальной жизни.
|
||||
|
||||
<dl>
|
||||
<dt>Для поиска только одного совпадения:</dt>
|
||||
<dd>
|
||||
<ul>
|
||||
<li>Найти позицию первого совпадения -- `str.search(reg)`.</li>
|
||||
<li>Найти само совпадение -- `str.match(reg)`.</li>
|
||||
<li>Проверить, есть ли хоть одно совпадение -- `regexp.test(str)` или `str.search(reg) != -1`.</li>
|
||||
<li>Найти совпадение с нужной позиции -- `regexp.exec(str)`, начальную позицию поиска задать в `regexp.lastIndex`.</li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt>Для поиска всех совпадений:</dt>
|
||||
<dd>
|
||||
<ul>
|
||||
<li>Найти массив совпадений -- `str.match(reg)`, с флагом `g`.</li>
|
||||
<li>Получить все совпадения, с подробной информацией о каждом -- `regexp.exec(str)` с флагом `g`, в цикле.</li>
|
||||
</ul>
|
||||
</dd>
|
||||
</dt>
|
||||
<dt>Для поиска-и-замены:</dt>
|
||||
<dd>
|
||||
<ul>
|
||||
<li>Замена на другую строку или функцией -- `str.replace(reg, str|func)`</li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt>Для разбивки строки на части:</dt>
|
||||
<dd>
|
||||
<ul>
|
||||
<li>`str.split(str|reg)`</li>
|
||||
</ul>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
Зная эти методы, мы уже можем использовать регулярные выражения.
|
||||
|
||||
Конечно, для этого желательно хорошо понимать их синтаксис и возможности, так что переходим к ним дальше.
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
Ответ: <code class="pattern">\d\d:\d\d</code>.
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( "Завтрак в 09:00.".match( /\d\d:\d\d/ ) ); // 09:00
|
||||
```
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
# Найдите время
|
||||
|
||||
Время имеет формат `часы:минуты`. И часы и минуты состоят из двух цифр, например: `09:00`.
|
||||
|
||||
Напишите регулярное выражение для поиска времени в строке: <code class="subject">Завтрак в 09:00.</code>
|
||||
|
||||
P.S. В этой задаче выражению позволительно найти и некорректное время, например `25:99`.
|
||||
|
|
@ -0,0 +1,271 @@
|
|||
# Классы и спецсимволы
|
||||
|
||||
Рассмотрим практическую задачу -- есть телефонный номер `"+7(903)-123-45-67"`, и нам нужно найти в этой строке цифры. А остальные символы нас не интересуют.
|
||||
|
||||
Для поиска символов определённого вида в регулярных выражениях предусмотрены "классы символов".
|
||||
|
||||
[cut]
|
||||
|
||||
Класс символов -- это специальное обозначение, под которое подходит любой символ из определённого набора.
|
||||
|
||||
Например, есть класс "любая цифра". Он обозначается `\d`. Это обозначение вставляется в шаблон, и при поиске под него подходит любая цифра.
|
||||
|
||||
То есть, регулярное выражение <code class="pattern">/\d/</code> ищет ровно одну цифру:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = "+7(903)-123-45-67";
|
||||
|
||||
var reg = /\d/;
|
||||
|
||||
// не глобальный регэксп, поэтому ищет только первую цифру
|
||||
alert( str.match(reg) ); // 7
|
||||
```
|
||||
|
||||
...Ну а для поиска всех цифр достаточно добавить к регэкспу флаг `g`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = "+7(903)-123-45-67";
|
||||
|
||||
var reg = /\d/g;
|
||||
|
||||
alert( str.match(reg) ); // массив всех совпадений: 7,9,0,3,1,2,3,4,5,6,7
|
||||
```
|
||||
|
||||
## Важнейшие классы: \d \s \w
|
||||
|
||||
Это был класс для цифр.
|
||||
|
||||
Конечно же, есть и другие.
|
||||
|
||||
Наиболее часто используются:
|
||||
<dl>
|
||||
<dt>`\d` (от английского "digit" -- "цифра")</dt>
|
||||
<dd>Цифра, символ от `0` до `9`.</dd>
|
||||
<dt>`\s` (от английского "space" -- "пробел")</dt>
|
||||
<dd>Пробельный символ, включая табы, переводы строки и т.п.</dd>
|
||||
<dt>`\w` (от английского "word" -- "слово") </dt>
|
||||
<dd>Символ "слова", а точнее -- буква латинского алфавита или цифра или подчёркивание `'_'`. Не-английские буквы не являются `\w`, то есть русская буква не подходит.</dd>
|
||||
</dl>
|
||||
|
||||
Например, <code class="pattern">\d\s\w</code> обозначает цифру, за которой идёт пробельный символ, а затем символ слова.
|
||||
|
||||
Регулярное выражение может содержать одновременно и обычные символы и классы.
|
||||
|
||||
Например, <code class="pattern">CSS\d</code> найдёт строку <code class="match">CSS</code>, с любой цифрой после неё:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = "Стандарт CSS4 - это здорово";
|
||||
var reg = /CSS\d/
|
||||
|
||||
alert( str.match(reg) ); // CSS4
|
||||
```
|
||||
|
||||
И много классов подряд:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( "Я люблю HTML5!".match(/\s\w\w\w\w\d/) ); // 'HTML5'
|
||||
```
|
||||
|
||||
Совпадение (каждому классу в регэкспе соответствует один символ результата):
|
||||
|
||||
<img src="love-html5-classes.svg">
|
||||
|
||||
## Граница слова \b
|
||||
|
||||
Граница слова <code class="pattern">\b</code> -- это особый класс.
|
||||
|
||||
Он интересен тем, что обозначает не символ, а границу между символами.
|
||||
|
||||
Например, <code class="pattern">\bJava\b</code> найдёт слово <code class="match">Java</code> в строке <code class="subject">Hello, Java!</code>, но не в строке <code class="subject">Hello, Javascript!</code>.
|
||||
|
||||
|
||||
```js
|
||||
//+ run
|
||||
|
||||
alert( "Hello, Java!".match(/\bJava\b/) ); // Java
|
||||
alert( "Hello, Javascript!".match(/\bJava\b/) ); // null
|
||||
```
|
||||
|
||||
Граница имеет "нулевую ширину" в том смысле, что обычно символам регулярного выражения соответствуют символы строки, но не в этом случае.
|
||||
|
||||
Граница -- это проверка.
|
||||
|
||||
При поиске движок регулярных выражений идёт по шаблону и одновременно по строке, пытаясь построить соответствие. Когда он видит <code class="pattern">\b</code>, то проверяет, что текущая позиция в строке подходит под одно из условий:
|
||||
<ul>
|
||||
<li>Начало текста, если первый символ `\w`.</li>
|
||||
<li>Конец текста, если последний символ `\w`.</li>
|
||||
<li>Внутри текста, если с одной стороны `\w`, а с другой -- не `\w`.</li>
|
||||
</ul>
|
||||
|
||||
Например, в строке <code class="subject">Hello, Java!</code> под `\b` подходят следующие позиции:
|
||||
|
||||
<img src="hello-java-boundaries.svg">
|
||||
|
||||
Как правило, `\b` используется, чтобы искать отдельно стоящее слово. Не на русском конечно, хотя подобную проверку, как мы увидим далее, можно легко сделать для любого языка. А вот на английском, как в примере выше или для чисел, которые являются частным случаем `\w` -- легко.
|
||||
|
||||
Например, регэксп <code class="pattern">\b\d\d\b</code> ищет отдельно двузначные числа. Иными словами, он требует, чтобы до и после <code class="pattern">\d\d</code> был символ, отличный от `\w` (или начало/конец текста).
|
||||
|
||||
|
||||
## Обратные классы
|
||||
|
||||
Для каждого класса существует "обратный ему", представленный такой же, но заглавной буквой.
|
||||
|
||||
"Обратный" -- означает, что ему соответствуют все остальные символы, например:
|
||||
|
||||
<dl>
|
||||
<dt>`\D`</dt>
|
||||
<dd>Не-цифра, то есть любой символ кроме `\d`, например буква.</dd>
|
||||
<dt>`\S`</dt>
|
||||
<dd>Не-пробел, то есть любой символ кроме `\s`, например буква.</dd>
|
||||
<dt>`\W`</dt>
|
||||
<dd>Любой символ, кроме `\w`, то есть не латинница, не подчёркивание, не цифра. В частности, русские буквы принадлежат этому классу.</dd>
|
||||
<dt>`\B`</dt>
|
||||
<dd>Проверка, обратная `\b`.</dd>
|
||||
</dl>
|
||||
|
||||
В начале этой главы мы видели, как получить из телефона <code class="subject">+7(903)-123-45-67</code> все цифры.
|
||||
|
||||
Первый способ -- найти все цифры через `match(/\d/g)`.
|
||||
|
||||
Обратные классы помогут реализовать альтернативный -- найти все НЕцифры и удалить их из строки:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = "+7(903)-123-45-67";
|
||||
|
||||
alert( str.replace(/\D/g, "") ); // 79031234567
|
||||
```
|
||||
|
||||
## Пробелы -- обычные символы
|
||||
|
||||
Заметим, что в регулярных выражениях пробел -- такой же символ, как и другие.
|
||||
|
||||
Обычно мы не обращаем внимание на пробелы. Для нашего взгляда строки <code class="subject">1-5</code> и <code class="subject">1 - 5</code> почти идентичны.
|
||||
|
||||
Однако, если регэксп не учитывает пробелов, то он не сработает.
|
||||
|
||||
Попытаемся найти цифры, разделённые дефисом:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( "1 - 5".match(/\d-\d/) ); // null, нет совпадений!
|
||||
```
|
||||
|
||||
Поправим это, добавив в регэксп пробелы:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( "1 - 5".match(/\d - \d/) ); // работает, пробелы вокруг дефиса
|
||||
```
|
||||
|
||||
Конечно же, пробелы в регэкспе нужны лишь тогда, когда мы их ищем. Лишние пробелы (как и любые лишние символы) могут навредить:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( "1-5".match(/\d - \d/) ); // null, так как в строке 1-5 нет пробелов
|
||||
```
|
||||
|
||||
Короче говоря, в регулярном выражении все символы имеют значение. Даже (и тем более) -- пробелы.
|
||||
|
||||
## Точка -- любой символ
|
||||
|
||||
Особым классом символов является точка `"."`.
|
||||
|
||||
В регулярном выражении, точка <code class="pattern">"."</code> обозначает *любой символ*, кроме перевода строки:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( "Z".match(/./) ); // найдено Z
|
||||
```
|
||||
|
||||
Посередине регулярного выражения:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var re = /CS.4/;
|
||||
|
||||
alert( "CSS4".match(re) ); // найдено "CSS4"
|
||||
alert( "CS-4".match(re) ); // найдено "CS-4"
|
||||
alert( "CS 4".match(re) ); // найдено "CS 4" (пробел тоже символ)
|
||||
```
|
||||
|
||||
Обратим внимание -- точка означает именно "произвольный символ".
|
||||
|
||||
То есть какой-то символ на этом месте в строке должен быть:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( "CS4".match(/CS.4/) ); // нет совпадений, так как для точки нет символа
|
||||
```
|
||||
|
||||
## Экранирование специальных символов
|
||||
|
||||
В регулярных выражениях есть и другие символы, имеющие особый смысл.
|
||||
|
||||
Они используются, чтобы расширить возможности поиска.
|
||||
|
||||
Вот их полный список: <code class="pattern">[ \ ^ $ . | ? * + ( )</code>.
|
||||
|
||||
Не пытайтесь запомнить его -- когда мы разберёмся с каждым из них по отдельности, он запомнится сам собой.
|
||||
|
||||
**Чтобы использовать специальный символ в качестве обычного, он должен быть *экранирован*.**
|
||||
|
||||
Или, другими словами, перед символом должен быть обратный слэш `'\'`.
|
||||
|
||||
Например, нам нужно найти точку <code class="pattern">'.'</code>. В регулярном выражении она означает "любой символ, кроме новой строки", поэтому чтобы найти именно сам символ "точка" -- её нужно экранировать: <code class="pattern">\.</code>.
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( "Глава 5.1".match(/\d\.\d/) ); // 5.1
|
||||
```
|
||||
|
||||
Круглые скобки также являются специальными символами, так что для поиска именно скобки нужно использовать `\(`. Пример ниже ищет строку `"g()"`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( "function g()".match(/g\(\)/) ); // "g()"
|
||||
```
|
||||
|
||||
Сам символ слэш `'/'`, хотя и не является специальными символом в регулярных выражениях, но открывает-закрывает регэксп в синтаксисе <code class="pattern">/...pattern.../</code>, поэтому его тоже нужно экранировать.
|
||||
|
||||
Так выглядит поиск слэша `'/'`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( "/".match(/\//) ); // '/'
|
||||
```
|
||||
|
||||
Ну и, наконец, если нам нужно найти сам обратный слэш `\`, то его нужно просто задублировать.
|
||||
|
||||
Так выглядит поиск обратного слэша `"\"`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( "1\2".match(/\\/) ); // '\'
|
||||
```
|
||||
|
||||
|
||||
## Итого
|
||||
|
||||
Мы рассмотрели классы для поиска типов символов:
|
||||
|
||||
<ul>
|
||||
<li>`\d` -- цифры.</li>
|
||||
<li>`\D` -- не-цифры.</li>
|
||||
<li>`\s` -- пробельные символы, переводы строки.</li>
|
||||
<li>`\S` -- всё, кроме `\s`.</li>
|
||||
<li>`\w` -- латинница, цифры, подчёркивание `'_'`.</li>
|
||||
<li>`'.'` -- точка обозначает любой символ, кроме перевода строки.</li>
|
||||
</ul>
|
||||
|
||||
Если хочется поискать именно сочетание `"\d"` или символ "точка", то его экранируют обратным слэшем, вот так: <code class="pattern">\.</code>
|
||||
|
||||
Заметим, что регулярное выражение может также содержать перевод строки `\n`, табуляцию `\t` и прочие спецсимволы для строк. Конфликта с классами не происходит, так как для них зарезервированы другие буквы.
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="201px" height="57px" viewBox="0 0 201 57" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||
<!-- Generator: bin/sketchtool 1.3 (252) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>hello-java-boundaries.svg</title>
|
||||
<desc>Created with bin/sketchtool.</desc>
|
||||
<defs></defs>
|
||||
<g id="combined" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||
<g id="hello-java-boundaries.svg" sketch:type="MSArtboardGroup">
|
||||
<text id="Hello,-Java!" sketch:type="MSTextLayer" font-family="Consolas" font-size="24" font-weight="normal" letter-spacing="2.29999995" fill="#8A704D">
|
||||
<tspan x="20" y="39">Hello, Java</tspan>
|
||||
<tspan x="190.448437" y="39">!</tspan>
|
||||
</text>
|
||||
<path d="M14.5,15.5 L14.5,45.5" id="Line" stroke="#EE6B47" stroke-width="3" stroke-linecap="square" fill="#EE6B47" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M96.5,15.5 L96.5,45.5" id="Line-2" stroke="#EE6B47" stroke-width="3" stroke-linecap="square" fill="#EE6B47" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M126.5,15.5 L126.5,45.5" id="Line-3" stroke="#EE6B47" stroke-width="3" stroke-linecap="square" fill="#EE6B47" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M190.5,15.5 L190.5,45.5" id="Line-4" stroke="#EE6B47" stroke-width="3" stroke-linecap="square" fill="#EE6B47" sketch:type="MSShapeGroup"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="187px" height="52px" viewBox="0 0 187 52" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||
<!-- Generator: bin/sketchtool 1.3 (252) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>love-html5-classes.svg</title>
|
||||
<desc>Created with bin/sketchtool.</desc>
|
||||
<defs></defs>
|
||||
<g id="combined" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||
<g id="love-html5-classes.svg" sketch:type="MSArtboardGroup" font-weight="normal" font-family="Consolas">
|
||||
<text id="люблю-HTML5" sketch:type="MSTextLayer" font-size="24" letter-spacing="2.29999995" fill="#8A704D">
|
||||
<tspan x="9" y="39">люблю HTML</tspan>
|
||||
<tspan x="163.953125" y="39">5</tspan>
|
||||
</text>
|
||||
<text id="\s\w\w\w\w\d" sketch:type="MSTextLayer" font-size="14" letter-spacing="1.49011614e-09" fill="#F48158">
|
||||
<tspan x="83" y="18">\s\w\w\w\w\</tspan>
|
||||
<tspan x="167.669922" y="18">d</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1,18 @@
|
|||
Ответы: **нет, да**.
|
||||
|
||||
<ul>
|
||||
<li>В строке <code class="subject">Java</code> он ничего не найдёт, так как исключающие квадратные скобки в `Java[^...]` означают "один символ, кроме указанных". А после <code>"Java"</code> -- конец строки, символов больше нет.
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( "Java".match(/Java[^script]/) ); // нет совпадений
|
||||
```
|
||||
</li>
|
||||
<li>Да, найдёт. Поскольку регэксп регистрозависим, то под `[^script]` вполне подходит символ `"S"`.
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( "JavaScript".match(/Java[^script]/) ); // "JavaS"
|
||||
```
|
||||
</li>
|
||||
</ul>
|
|
@ -0,0 +1,5 @@
|
|||
# Java[^script]
|
||||
|
||||
Найдет ли регэксп <code class="pattern">/Java[^script]/</code> что-нибудь в строке <code class="subject">Java</code>?
|
||||
|
||||
А в строке <code class="subject">JavaScript</code>?
|
|
@ -0,0 +1,9 @@
|
|||
Ответ: <code class="pattern">\d\d[-:]\d\d</code>.
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var re = /\d\d[-:]\d\d/g;
|
||||
alert( "Завтрак в 09:00. Обед - в 21-30".match(re) );
|
||||
```
|
||||
|
||||
Обратим внимание, что дефис <code class="pattern">'-'</code> не экранирован, поскольку в начале скобок он не может иметь специального смысла.
|
|
@ -0,0 +1,11 @@
|
|||
# Найдите время в одном из форматов
|
||||
|
||||
Время может быть в формате `часы:минуты` или `часы-минуты`. И часы и минуты состоят из двух цифр, например `09:00`, `21-30`.
|
||||
|
||||
Напишите регулярное выражение для поиска времени:
|
||||
|
||||
```js
|
||||
var re = /ваше выражение/;
|
||||
alert( "Завтрак в 09:00. Обед - в 21-30".match(re) ); // 09:00, 21-30
|
||||
```
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
# Наборы и диапазоны [...]
|
||||
|
||||
Если в регулярном выражении несколько символов или символьных классов заключены в квадратные скобки `[…]`, то это означает "искать любой символ из указанных в `[…]`".
|
||||
|
||||
[cut]
|
||||
|
||||
## Набор
|
||||
|
||||
Например, <code class="pattern">[еао]</code> означает любой символ из этих трёх: `'а'`, `'е'`, или `'о'`.
|
||||
|
||||
Такое обозначение называют *набором*. Наборы используются в регулярном выражении наравне с обычными символами:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
// найти [г или т], а затем "оп"
|
||||
alert( "Гоп-стоп".match(/[гт]оп/gi) ); // "Гоп", "топ"
|
||||
```
|
||||
|
||||
Обратим внимание: несмотря на то, что в наборе указано несколько символов, в совпадении должен присутствовать *ровно один* из них.
|
||||
|
||||
Поэтому в примере ниже нет результатов:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
// найти "В", затем [у или а], затем "ля"
|
||||
alert( "Вуаля".match(/В[уа]ля/) ); // совпадений нет
|
||||
```
|
||||
|
||||
Поиск подразумевает:
|
||||
<ul>
|
||||
<li><code class="pattern">В</code>,</li>
|
||||
<li>затем *одну* из букв набора <code class="pattern">[уа]</code>,</li>
|
||||
<li>а затем <code class="pattern">ля</code></li>
|
||||
</ul>
|
||||
|
||||
Таким образом, совпадение было бы для строки <code class="match">Вуля</code> или <code class="match">Валя</code>.
|
||||
|
||||
## Диапазоны
|
||||
|
||||
Квадратные скобки могут также содержать *диапазоны символов*.
|
||||
|
||||
Например, <code class="pattern">[a-z]</code> -- произвольный символ от `a` до `z`, <code class="pattern">[0-5]</code> -- цифра от `0` до `5`.
|
||||
|
||||
В примере ниже мы будем искать `"x"`, после которого идёт два раза любая цифра или буква от A до F:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
// найдёт "xAF"
|
||||
alert( "Exception 0xAF".match(/x[0-9A-F][0-9A-F]/g) );
|
||||
```
|
||||
|
||||
Обратим внимание, в слове <code class="subject">Exception</code> есть сочетание <code class="subject">xce</code>, но оно не подошло, потому что буквы в нём маленькие, а в диапазоне <code class="pattern">[0-9A-F]</code> -- большие.
|
||||
|
||||
Если хочется искать и его тоже, можно добавить в скобки диапазон `a-f`: <code class="pattern">[0-9A-Fa-f]</code>. Или же просто указать у всего регулярного выражения флаг `i`.
|
||||
|
||||
**Символьные классы -- всего лишь более короткие записи для диапазонов, в частности:**
|
||||
|
||||
<ul>
|
||||
<li>**\d** -- то же самое, что <code class="pattern">[0-9]</code>,</li>
|
||||
<li>**\w** -- то же самое, что <code class="pattern">[a-zA-Z0-9_]</code>,</li>
|
||||
<li>**\s** -- то же самое, что <code class="pattern">[\t\n\v\f\r ]</code> плюс несколько юникодных пробельных символов.</li>
|
||||
</ul>
|
||||
|
||||
В квадратных скобках можно использовать и диапазоны и символьные классы -- вместе.
|
||||
|
||||
Например, нам нужно найти все слова в тексте. Если они на английском -- это достаточно просто:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = "The sun is rising!";
|
||||
|
||||
alert( str.match(/\w+/g) ); // The, sun, is, rising*!*
|
||||
```
|
||||
|
||||
А если есть слова и на русском?
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = "Солнце встаёт!";
|
||||
|
||||
alert( str.match(/\w+/g) ); // null*!*
|
||||
```
|
||||
|
||||
Ничего не найдено! Это можно понять, ведь <code class="pattern">\w</code> -- это именно английская букво-цифра, как можно видеть из аналога <code class="pattern">[a-zA-Z0-9_]</code>.
|
||||
|
||||
Чтобы находило слово на русском -- нужно использовать диапазон, например <code class="pattern">/[а-я]/</code>.
|
||||
|
||||
А чтобы на обоих языках -- и то и другое вместе:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = "Солнце (the sun) встаёт!";
|
||||
|
||||
alert( str.match(/[\wа-я]+/gi) ); // Солнце, the, sun, вста, т*!*
|
||||
```
|
||||
|
||||
...Присмотритесь внимательно к предыдущему примеру! Вы видите странность? Оно не находит букву <code class="match">ё</code>, более того -- считает её разрывом в слове. Причина -- в кодировке юникод, она подробно раскрыта в главе [](/string).
|
||||
|
||||
Буква `ё` лежит в стороне от основной кириллицы и её следует добавить в диапазон дополнительно, вот так:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = "Солнце (the sun) встаёт!";
|
||||
|
||||
alert( str.match(/[\wа-яё]+/gi) ); // Солнце, the, sun, встаёт*!*
|
||||
```
|
||||
|
||||
Теперь всё в порядке.
|
||||
|
||||
## Диапазоны "кроме"
|
||||
|
||||
**Кроме обычных, существуют также *исключающие* диапазоны: <code class="pattern">[^…]</code>.**
|
||||
|
||||
Квадратные скобки, начинающиеся со знака каретки: <code class="pattern">[^…]</code> находят любой символ, *кроме указанных*.
|
||||
|
||||
Например:
|
||||
|
||||
<ul>
|
||||
<li><code class="pattern">[^аеуо]</code> -- любой символ, кроме `'a'`, `'e'`, `'y'`, `'o'`.</li>
|
||||
<li><code class="pattern">[^0-9]</code> -- любой символ, кроме цифры, то же что `\D`.</li>
|
||||
<li><code class="pattern">[^\s]</code> -- любой не-пробельный символ, то же что `\S`.</li>
|
||||
</ul>
|
||||
|
||||
Пример ниже ищет любые символы, кроме букв, цифр и пробелов:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( "alice15@gmail.com".match(/[^\d\sA-Z]/gi) ); // "@", "."
|
||||
```
|
||||
|
||||
## Не нужно экранирование
|
||||
|
||||
Обычно, если мы хотим искать именно точку, а не любой символ, или именно символ `\`, то мы используем экранирование: указываем `\.` или `\\`.
|
||||
|
||||
В квадратных скобках большинство специальных символов можно использовать без экранирования, если конечно ни не имеют какой-то особый смысл именно внутри квадратных скобок.
|
||||
|
||||
То есть, "как есть", без экранирования можно использовать символы:
|
||||
<ul>
|
||||
<li>Точка <code class="pattern">'.'</code>.</li>
|
||||
<li>Плюс <code class="pattern">'+'</code>.</li>
|
||||
<li>Круглые скобки <code class="pattern">'( )'</code>.</li>
|
||||
<li>Дефис <code class="pattern">'-'</code>, если он находится в начале или конце квадратных скобок, то есть не выделяет диапазон.</li>
|
||||
<li>Символ каретки <code class="pattern">'^'</code>, если не находится в начале квадратных скобок.</li>
|
||||
<li>А также открывающая квадратная скобка <code class="pattern">'['</code>.</li>
|
||||
</ul>
|
||||
|
||||
То есть, точка `"."` в квадратных скобках означает не "любой символ", а обычную точку.
|
||||
|
||||
Регэксп <code class="pattern">[.,]</code> ищет один из символов "точка" или "запятая".
|
||||
|
||||
В примере ниже регэксп <code class="pattern">[-().^+]</code> ищет один из символов `-().^`. Они не экранированы:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
// Без экранирования
|
||||
var re = /[-().^+]/g;
|
||||
|
||||
alert( "1 + 2 - 3".match(re) ); // найдёт +, -
|
||||
```
|
||||
|
||||
...Впрочем, даже если вы решите "на всякий случай" заэкранировать эти символы, поставив перед ними обратный слэш `\` -- вреда не будет:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
// Всё заэкранировали
|
||||
var re = /[\-\(\)\.\^\+]/g;
|
||||
|
||||
alert( "1 + 2 - 3".match(re) ); // тоже работает: +, -
|
||||
```
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
Решение:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var reg = /\.{3,}/g;
|
||||
alert( "Привет!... Как дела?.....".match(reg) ); // ..., .....
|
||||
```
|
||||
|
||||
Заметим, что символ `.` является специальным, значит его надо экранировать, то есть вставлять как `\.`.
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# Как найти многоточие... ?
|
||||
|
||||
[importance 5]
|
||||
|
||||
Напишите регулярное выражения для поиска многоточий: трёх или более точек подряд.
|
||||
|
||||
Проверьте его:
|
||||
|
||||
```js
|
||||
var reg = /ваше выражение/g;
|
||||
alert( "Привет!... Как дела?.....".match(reg) ); // ..., .....
|
||||
```
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
Итак, нужно написать выражение для описания цвета, который начинается с "#", за которым следуют 6 шестнадцатеричных символов.
|
||||
|
||||
Шестнадцатеричный символ можно описать с помощью <code class="pattern">[0-9a-fA-F]</code>. Мы можем сократить выражение, используя не чувствительный к регистру шаблон <code class="pattern">[0-9a-f]</code>.
|
||||
|
||||
Для его шестикратного повторения мы будем использовать квантификатор <code class="pattern">{6}</code>.
|
||||
|
||||
В итоге, получаем выражение вида <code class="pattern">/#[a-f0-9]{6}/gi</code>.
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var re = /#[a-f0-9]{6}/gi;
|
||||
|
||||
var str = "color:#121212; background-color:#AA00ef bad-colors:f#fddee #fd2";
|
||||
|
||||
alert( str.match(re) ); // #121212,#AA00ef
|
||||
```
|
||||
|
||||
Проблема этого выражения в том, что оно находит цвет и в более длинных последовательностях:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( "#12345678".match( /#[a-f0-9]{6}/gi ) ) // #12345678
|
||||
```
|
||||
|
||||
Чтобы такого не было, можно добавить в конец `\b`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
// цвет
|
||||
alert( "#123456".match( /#[a-f0-9]{6}\b/gi ) ); // #123456
|
||||
|
||||
// не цвет
|
||||
alert( "#12345678".match( /#[a-f0-9]{6}\b/gi ) ); // null
|
||||
```
|
|
@ -0,0 +1,14 @@
|
|||
# Регулярное выражение для цвета
|
||||
|
||||
Напишите регулярное выражение для поиска HTML-цвета, заданного как `#ABCDEF`, то есть `#` и содержит затем 6 шестнадцатеричных символов.
|
||||
|
||||
Пример использования:
|
||||
|
||||
```
|
||||
var re = /*...ваше регулярное выражение...*/
|
||||
|
||||
var str = "color:#121212; background-color:#AA00ef bad-colors:f#fddee #fd2"
|
||||
|
||||
alert( str.match(re) ) // #121212,#AA00ef
|
||||
```
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
|
||||
|
||||
Целое число -- это <code class="pattern">\d+</code>.
|
||||
|
||||
Десятичная точка с дробной частью -- <code class="pattern">\.\d+</code>.
|
||||
|
||||
Она не обязательна, так что обернём её в скобки с квантификатором <code class="pattern">'?'</code>.
|
||||
|
||||
Итого, получилось регулярное выражение <code class="pattern">\d+(\.\d+)?</code>:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var re = /\d+(\.\d+)?/g
|
||||
|
||||
var str = "1.5 0 12. 123.4.";
|
||||
|
||||
alert( str.match(re) ); // 1.5, 0, 12, 123.4
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
# Найдите положительные числа
|
||||
|
||||
Создайте регэксп, который ищет все положительные числа, в том числе и с десятичной точкой.
|
||||
|
||||
Пример использования:
|
||||
|
||||
```js
|
||||
var re = /* ваш регэксп */
|
||||
|
||||
var str = "1.5 0 12. 123.4.";
|
||||
|
||||
alert( str.match(re) ); // 1.5, 0, 12, 123.4
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
Целое число с необязательной дробной частью -- это <code class="pattern">\d+(\.\d+)?</code>.
|
||||
|
||||
К этому нужно добавить необязательный `-` в начале:
|
||||
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var re = /-?\d+(\.\d+)?/g
|
||||
|
||||
var str = "-1.5 0 2 -123.4.";
|
||||
|
||||
alert( str.match(re) ); // -1.5, 0, 2, -123.4
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
# Найдите десятичные числа
|
||||
|
||||
Создайте регэксп, который ищет все числа, в том числе и с десятичной точкой, в том числе и отрицательные.
|
||||
|
||||
Пример использования:
|
||||
|
||||
```js
|
||||
var re = /* ваш регэксп */
|
||||
|
||||
var str = "-1.5 0 2 -123.4.";
|
||||
|
||||
alert( str.match(re) ); // -1.5, 0, 2, -123.4
|
||||
```
|
|
@ -0,0 +1,168 @@
|
|||
# Квантификаторы +, *, ? и {n}
|
||||
|
||||
Рассмотрим ту же задачу, что и ранее -- взять телефон вида `+7(903)-123-45-67` и найти все числа в нём. Но теперь нас интересуют не цифры по отдельности, а именно числа, то есть результат вида `7, 903, 123, 45, 67`.
|
||||
|
||||
Для поиска цифр по отдельности нам было достаточно класса `\d`. Но здесь нужно искать *числа* -- последовательности из 1 или более цифр.
|
||||
|
||||
## Количество {n}
|
||||
|
||||
Количество повторений символа можно указать с помощью числа в фигурных скобках: `{n}`.
|
||||
|
||||
Такое указание называют *квантификатором* (от англ. quantifier).
|
||||
|
||||
У него есть несколько подформ записи:
|
||||
|
||||
<dl>
|
||||
<dt>Точное количество: `{5}`</dt>
|
||||
<dd>Регэксп <code class="pattern">\d{5}</code> обозначает ровно 5 цифр, в точности как <code class="pattern">\d\d\d\d\d</code>.
|
||||
|
||||
Следующий пример находит пятизначное число.
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( "Мне 12345 лет".match(/\d{5}/) ); // "12345"
|
||||
```
|
||||
|
||||
</dd>
|
||||
<dt>Количество от-до: `{3,5}`</dt>
|
||||
<dd>Для того, чтобы найти, например, числа размером от трёх до пяти знаков, нужно указать границы в фигурных скобках: <code class="pattern">\d{3,5}</code>
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( "Мне не 12, а 1234 года".match(/\d{3,5}/) ); // "1234"
|
||||
```
|
||||
|
||||
Последнее значение можно и не указывать. Тогда выражение <code class="pattern">\d{3,}</code> найдет числа, длиной от трех цифр:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( "Мне не 12, а 345678 лет".match(/\d{3,5}/) ); // "345678"
|
||||
```
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
В случае с телефоном нам нужны числа -- одна или более цифр подряд. Этой задаче соответствует регулярное выражение <code class="pattern">\d{1,}</code>:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = "+7(903)-123-45-67";
|
||||
|
||||
alert( str.match(/\d{1,}/g) ); // 7,903,123,45,67
|
||||
```
|
||||
|
||||
|
||||
## Короткие обозначения
|
||||
|
||||
Для самые часто востребованных квантификаторов есть специальные короткие обозначения.
|
||||
|
||||
<dl>
|
||||
<dt>`+`</dt>
|
||||
<dd>Означает "один или более", то же что `{1,}`.
|
||||
|
||||
Например, <code class="pattern">\d+</code> находит числа -- последовательности из 1 или более цифр:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = "+7(903)-123-45-67";
|
||||
|
||||
alert( str.match(/\d+/g) ); // 7,903,123,45,67
|
||||
```
|
||||
|
||||
</dd>
|
||||
<dt>`?`</dt>
|
||||
<dd>Означает "ноль или один", то же что и `{0,1}`. По сути, делает символ необязательным.
|
||||
|
||||
Например, регэксп <code class="pattern">ou?r</code> найдёт <code class="match">o</code>, после которого, возможно, следует <code class="match">u</code>, а затем <code class="match">r</code>.
|
||||
|
||||
Этот регэксп найдёт <code class="match">or</code> в слове <code class="subject">color</code> и <code class="match">our</code> в <code class="subject">colour</code>:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = "Можно писать color или colour (британский вариант)";
|
||||
|
||||
alert( str.match(/colou?r/g) ); // color, colour
|
||||
```
|
||||
|
||||
</dd>
|
||||
<dt>`*`</dt>
|
||||
<dd>Означает "ноль или более", то же что `{0,}`. То есть, символ может повторяться много раз или вообще отсутствовать.
|
||||
|
||||
Пример ниже находит цифру, после которой идёт один или более нулей:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( "100 10 1".match(/\d0*/g) ); // 100, 10, 1
|
||||
```
|
||||
|
||||
Сравните это с `'+'` (один или более):
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( "100 10 1".match(/\d0+/g) ); // 100, 10
|
||||
```
|
||||
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
## Ещё примеры
|
||||
|
||||
Эти квантификаторы принадлежат к числу самых важных "строительных блоков" для сложных регулярных выражений, поэтому мы рассмотрим ещё примеры.
|
||||
|
||||
<dl>
|
||||
<dt>Регэксп "десятичная дробь" (число с точкой внутри): <code class="pattern">\d+\.\d+</code></dt>
|
||||
<dd>
|
||||
|
||||
В действии:
|
||||
```js
|
||||
//+ run
|
||||
alert( "0 1 12.345 7890".match(/\d+\.\d+/g) ); // 123.45
|
||||
```
|
||||
|
||||
</dd>
|
||||
<dt>Регэксп "открывающий HTML-тег без атрибутов", такой как `<span>` или `<p>`: <code class="pattern">/<[a-z]+>/i</code></dt>
|
||||
<dd>Пример:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( "<BODY> ... </BODY>".match(/<[a-z]+>/gi) ); // <BODY>
|
||||
```
|
||||
|
||||
Это регулярное выражение ищет символ <code class="pattern">'<'</code>, за которым идут одна или более букв английского алфавита, и затем <code class="pattern">'>'</code>.
|
||||
</dd>
|
||||
<dt>Регэксп "открывающий HTML-тег без атрибутов" (лучше): <code class="pattern">/<[a-z][a-z0-9]*>/i</code></dt>
|
||||
<dd>
|
||||
Здесь регулярное выражение расширено: в соответствие со стандартом, HTML-тег может иметь символ на любой позиции, кроме первой, например `<h1>`.
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( "<h1>Привет!</h1>".match(/<[a-z][a-z0-9]*>/gi) ); // <h1>
|
||||
```
|
||||
|
||||
</dd>
|
||||
<dt>Регэксп "открывающий или закрывающий HTML-тег без атрибутов": <code class="pattern">/<\/?[a-z][a-z0-9]*>/i</code></dt>
|
||||
<dd>В предыдущий паттерн добавили необязательный слэш <code class="pattern">/?</code> перед тегом. Его понадобилось заэкранировать, чтобы JavaScript не принял его за конец шаблона.
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( "<h1>Привет!</h1>".match(/<\/?[a-z][a-z0-9]*>/gi) ); // <h1>, </h1>
|
||||
```
|
||||
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
|
||||
[smart header="Точнее -- значит сложнее"]
|
||||
В этих примерах мы видим общее правило, которое повторяется из раза в раз: чем точнее регулярное выражение, тем оно длиннее и сложнее.
|
||||
|
||||
Например, для HTML-тегов, скорее всего, подошло бы и более короткое регулярное выражение <code class="pattern"><\w+></code>.
|
||||
|
||||
Так как класс `\w` означает "любая цифра или английская буква или `'_'`, то под такой регэксп подойдут и не теги, например <code class="match"><_></code>. Однако он гораздо проще, чем более точный регэксп <code class="pattern"><[a-z][a-z0-9]*></code>.
|
||||
|
||||
Подойдёт ли нам <code class="pattern"><\w+></code> или нужно использовать именно <code class="pattern"><[a-z][a-z0-9]*></code>?
|
||||
|
||||
В реальной жизни допустимы оба варианта. Ответ на подобные вопросы зависит от того, насколько реально важна точность и насколько сложно потом будет отфильтровать лишние совпадения (если появятся).
|
||||
[/smart]
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
Результат: `123 456`.
|
||||
|
||||
Ленивый `\d+?` будет брать цифры до пробела, то есть `123`. После каждой цифры он будет останавливаться, проверять -- не пробел ли дальше? Если нет -- брать ещё цифру, в итоге возьмёт `123`.
|
||||
|
||||
З в дело вступит `\d+`, который по-максимуму возьмёт дальнейшие цифры, то есть `456`.
|
|
@ -0,0 +1,8 @@
|
|||
# Совпадение для /d+? d+/
|
||||
|
||||
Что будет при таком поиске, когда сначало стоит ленивый, а потом жадный квантификаторы?
|
||||
|
||||
```js
|
||||
"123 456".match(/\d+? \d+/g) ); // какой результат?
|
||||
```
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
Они очень похожи и, да, *почти* одинаковы. Оба ищут от одной кавычки до другой.
|
||||
|
||||
Различие здесь в символе точка <code class="pattern">'.'</code>. Как мы помним, точка <code class="pattern">'.'</code> обозначает *любой символ, кроме перевода строки*.
|
||||
|
||||
А <code class="pattern">[^"]</code> -- это *любой символ, кроме кавычки <code class="pattern">'"'</code>.
|
||||
|
||||
Получатся, что первый регэксп <code class="pattern">"[^"]*"</code> найдёт закавыченные строки с `\n` внутри, а второй регэксп <code class="pattern">".*?"</code> -- нет.
|
||||
|
||||
Вот пример:
|
||||
```js
|
||||
//+ run
|
||||
alert( '"многострочный \n текст"'.match(/"[^"]*"/) ); // найдёт
|
||||
|
||||
alert( '"многострочный \n текст"'.match(/".*?"/) ); // null (нет совпадений)
|
||||
```
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# Различие между "[^"]*" и ".*?"
|
||||
|
||||
Регулярные выражения <code class="pattern">"[^"]*"</code> и <code class="pattern">".*?"</code> -- при выполнении одинаковы?
|
||||
|
||||
Иначе говоря, существует ли такая строка, на которой они дадут разные результаты? Если да -- дайте такую строку.
|
|
@ -0,0 +1,18 @@
|
|||
Нужно найти начало комментария <code class="match"><!--</code>, затем всё до конца <code class="match">--></code>.
|
||||
|
||||
С первого взгляда кажется, что это сделает регулярное выражение <code class="pattern"><!--.*?--></code> -- квантификатор сделан ленивым, чтобы остановился, достигнув <code class="match">--></code>.
|
||||
|
||||
Однако, точка в JavaScript -- любой символ, *кроме* конца строки. Поэтому такой регэксп не найдёт многострочный комментарий.
|
||||
|
||||
Всё получится, если вместо точки использовать полностю "всеядный" <code class="pattern">[\s\S]</code>.
|
||||
|
||||
Итого:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var re = /<!--[\s\S]*?-->/g;
|
||||
|
||||
var str = '.. <!-- Мой -- комментарий \n тест --> .. <!----> .. ';
|
||||
|
||||
alert( str.match(re) ); // '<!-- Мой -- комментарий \n тест -->', '<!---->'
|
||||
```
|
|
@ -0,0 +1,11 @@
|
|||
# Найти HTML-комментарии
|
||||
|
||||
Найдите все HTML-комментарии в тексте:
|
||||
|
||||
```js
|
||||
var re = ..ваш регэксп..
|
||||
|
||||
var str = '.. <!-- Мой -- комментарий \n тест --> .. <!----> .. ';
|
||||
|
||||
alert( str.match(re) ); // '<!-- Мой -- комментарий \n тест -->', '<!---->'
|
||||
```
|
|
@ -0,0 +1,38 @@
|
|||
Начнём поиск с <code class="pattern"><</code>, затем один или более произвольный символ, но до закрывающего "уголка": <code class="pattern">.+?></code>.
|
||||
|
||||
Проверим, как работает этот регэксп:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var re = /<.+?>/g;
|
||||
|
||||
var str = '<> <a href="/"> <input type="radio" checked> <b>';
|
||||
|
||||
alert( str.match(re) ); // <> <a href="/">, <input type="radio" checked>, <b>
|
||||
```
|
||||
|
||||
Результат неверен! В качестве первого тега регэксп нашёл подстроку <code class="match"><> <a href="/"></code>, но это явно не тег.
|
||||
|
||||
Всё потому, что <code class="pattern">.+?</code> -- это "любой символ (кроме `\n`), повторяющийся один и более раз до того, как оставшаяся часть шаблона совпадёт (ленивость)".
|
||||
|
||||
Поэтому он находит первый `<`, затем есть "всё подряд" до следующего `>`.
|
||||
|
||||
Первое совпадение получается как раз таким:
|
||||
|
||||
```
|
||||
<.............>
|
||||
<> <a href="/"> <input type="radio" checked> <b>
|
||||
```
|
||||
|
||||
Правильным решением будет использовать <code class="pattern"><[^>]+></code>:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var re = /<[^>]+>/g
|
||||
|
||||
var str = '<> <a href="/"> <input type="radio" checked> <b>';
|
||||
|
||||
alert( str.match(re) ); // <a href="/">, <input type="radio" checked>, <b>
|
||||
```
|
||||
|
||||
Это же решение автоматически позволяет находится внутри тегу символу `\n`, который в класс точка `.` не входит.
|
|
@ -0,0 +1,18 @@
|
|||
# Найти HTML-теги
|
||||
|
||||
Создайте регулярное выражение для поиска всех (открывающихся и закрывающихся) HTML-тегов вместе с атрибутами.
|
||||
|
||||
Пример использования:
|
||||
```js
|
||||
//+ run
|
||||
var re = /* ваш регэксп */
|
||||
|
||||
var str = '<> <a href="/"> <input type="radio" checked> <b>';
|
||||
|
||||
alert( str.match(re) ); // '<a href="/">', '<input type="radio" checked>', '<b>'
|
||||
```
|
||||
|
||||
В этой задаче можно считать, что тег начинается с <code><</code>, заканчивается <code>></code> и может содержать внутри любые символы, кроме <code><</code> и <code>></code>.
|
||||
|
||||
Но хотя бы один символ внутри тега должен быть: <code><></code> -- не тег.
|
||||
|
|
@ -0,0 +1,335 @@
|
|||
# Жадные и ленивые квантификаторы
|
||||
|
||||
Квантификаторы -- с виду очень простая, но на самом деле очень хитрая штука.
|
||||
|
||||
Необходимо очень хорошо понимать, как именно происходит поиск, если конечно мы хотим искать что-либо сложнее чем <code class="pattern">/\d+/</code>.
|
||||
|
||||
[cut]
|
||||
|
||||
Для примера рассмотрим задачу, которая часто возникает в типографике -- заменить в тексте кавычки вида `"..."` (их называют "английские кавычки") на "кавычки-ёлочки": `«...»`.
|
||||
|
||||
Для этого нужно сначала найти все слова в таких кавычках.
|
||||
|
||||
Соотверствующее регулярное выражение может выглядеть так: <code class="pattern">/".+"/g</code>, то есть мы ищем кавычку, после которой один или более произвольный символ, и в конце опять кавычка.
|
||||
|
||||
Однако, если попробовать применить его на практике, даже на таком простом случае...
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var reg = /".+"/g;
|
||||
|
||||
var str = 'a "witch" and her "broom" is one';
|
||||
|
||||
alert( str.match(reg) ); // "witch" and her "broom"
|
||||
```
|
||||
|
||||
...Мы увидим, что оно работает совсем не так, как задумано!
|
||||
|
||||
Вместо того, чтобы найти два совпадения <code class="match">"witch"</code> и <code class="match">"broom"</code>, оно находит одно: <code class="match">"witch" and her "broom"</code>.
|
||||
|
||||
Это как раз тот случай, когда *жадность* -- причина всех зол.
|
||||
|
||||
## Жадный поиск
|
||||
|
||||
Чтобы найти совпадение, движок регулярных выражений обычно использует следующий алгоритм:
|
||||
|
||||
<ul>
|
||||
<li>Для каждой позиции в поисковой строке
|
||||
<ul>
|
||||
<li>Проверить совпадение на данной позиции
|
||||
<ul><li>Посимвольно, с учётом классов и квантификаторов сопоставив с ней регулярное выражение.</li></ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
Это общие слова, гораздо понятнее будет, если мы проследим, что именно он делает для регэкспа <code class="pattern">".+"</code>.
|
||||
|
||||
<ol>
|
||||
<li>Первый символ шаблона -- это кавычка <code class="pattern">"</code>.
|
||||
|
||||
Движок регулярных выражений пытается сопоставить её на 0й позиции в строке, но символ `a`, поэтому на 0й позиции соответствия явно нет.
|
||||
|
||||
Далее он переходит 1ю, 2ю позицию в исходной строке и, наконец, обнаруживает кавычку на 3й позиции:
|
||||
<img src="witch_greedy1.svg">
|
||||
</li>
|
||||
<li>Кавычка найдена, далее движок проверяет, есть ли соответствие для остальной части паттерна.
|
||||
|
||||
В данном случае следующий символ шаблона: <code class="pattern">.</code> (точка). Она обозначает "любой символ", так что следующая буква строки <code class="match">'w'</code> вполне подходит:
|
||||
<img src="witch_greedy2.svg">
|
||||
</li>
|
||||
<li>Далее "любой символ" повторяется, так как стоит квантификатор <code class="pattern">.+</code>. Движок регулярных выражений берёт один символ за другим, до тех пор, пока у него это получается.
|
||||
|
||||
В данном случае это означает "до конца строки":
|
||||
<img src="witch_greedy3.svg">
|
||||
</li>
|
||||
<li>Итак, текст закончился, движок регулярных выражений больше не может найти "любой символ", он закончил повторения для <code class="pattern">.+</code> и переходит к следующему символу шаблона.
|
||||
|
||||
Следующий символ шаблона -- это кавычка. Её тоже необходимо найти, чтобы соответствие было полным. А тут -- беда, ведь поисковый текст завершился!
|
||||
|
||||
Движок регулярных выражений понимает, что, наверное, взял многовато <code class="pattern">.+</code> и начинает отступать обратно.
|
||||
|
||||
Иными словами, он сокращает текущее совпадение на один символ:
|
||||
|
||||
<img src="witch_greedy4.svg">
|
||||
|
||||
Это называется "фаза возврата" или "фаза бэктрекинга" (backtracking -- англ.).
|
||||
|
||||
Теперь <code class="pattern">.+</code> соответствует почти вся оставшаяся строка, за исключением одного символа, и движок регулярных выражений ещё раз пытается подобрать соответствие для остатка шаблона, начиная с оставшейся части строки.
|
||||
|
||||
Если бы последним символом строки была кавычка <code class="pattern">'"'</code>, то на этом бы всё и закончилось. Но последний символ <code class="subject">'e'</code>, так что совпадения нет.</li>
|
||||
<li>...Поэтому движок уменьшает число повторений <code class="pattern">.+</code> ещё на один символ:
|
||||
|
||||
<img src="witch_greedy5.svg">
|
||||
|
||||
Кавычка <code class="pattern">'"'</code> не совпадает с <code class="subject">'n'</code>. Опять неудача.</li>
|
||||
<li>Движок продолжает отступать, он уменьшает количество повторений точки <code class="pattern">'.'</code> до тех пор, пока остаток паттерна, то есть в данном случае кавычка <code class="pattern">'"'</code>, не совпадёт:
|
||||
|
||||
<img src="witch_greedy6.svg">
|
||||
</li>
|
||||
<li>Совпадение получено. Дальнейший поиск по оставшейся части строки <code class="subject">is one</code> новых совпадений не даст.</li>
|
||||
</ol>
|
||||
|
||||
Возможно, это не совсем то, что мы ожидали.
|
||||
|
||||
**В жадном режиме (по умолчанию) регэксп повторяет квантификатор настолько много раз, насколько это возможно, чтобы найти соответствие.**
|
||||
|
||||
То есть, любой символ <code class="pattern">.+</code> повторился максимальное количество раз, что и привело к такой длинной строке.
|
||||
|
||||
А мы, наверное, хотели, чтобы каждая строка в кавычках была независимым совпадением? Для этого можно переключить квантификатор `+` в "ленивый" режим, о котором будет речь далее.
|
||||
|
||||
## Ленивый режим
|
||||
|
||||
Ленивый режим работы квантификаторов -- противоположность жадному, он означает "повторять минимальное количество раз".
|
||||
|
||||
Его можно включить, если поставить знак вопроса <code class="pattern">'?'</code> после квантификатора, так что он станет таким: <code class="pattern">*?</code> или <code class="pattern">+?</code> или даже <code class="pattern">??</code> для <code class="pattern">'?'</code>.
|
||||
|
||||
Чтобы не возникло путаницы -- важно понимать: обычно `?` сам является квантификатором (ноль или один). Но если он стоит *после другого квантификатора (или даже после себя)*, то обретает другой смысл -- в этом случае он меняет режим его работы на ленивый.
|
||||
|
||||
Регэксп <code class="pattern">/".+?"/g</code> работает, как задумано -- находит отдельно <code class="match">witch</code> и <code class="match">broom</code>:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var reg = /".+?"/g;
|
||||
|
||||
var str = 'a "witch" and her "broom" is one';
|
||||
|
||||
alert( str.match(reg) ); // witch, broom
|
||||
```
|
||||
|
||||
Чтобы в точности понять, как поменялась работа квантификатора, разберём поиск по шагам.
|
||||
|
||||
<ol>
|
||||
<li>Первый шаг -- тот же, кавычка <code class="pattern">'"'</code> найдена на 3й позиции:
|
||||
<img src="witch_greedy1.svg">
|
||||
</li>
|
||||
|
||||
<li>Второй шаг -- тот же, находим произвольный символ <code class="pattern">'.'</code>:
|
||||
<img src="witch_greedy2.svg">
|
||||
</li>
|
||||
|
||||
<li>А вот дальше -- так как стоит ленивый режим работы `+`, то движок не повторет точку (произвольный символ) ещё раз, а останавливается на достигнутом и пытается проверить, есть ли соответствие остальной части шаблона, то есть <code class="pattern">'"'</code>:
|
||||
<img src="witch_lazy3.svg">
|
||||
|
||||
Если бы остальная часть шаблона на данной позиции совпала, то совпадение было бы найдено. Но в данном случе -- нет, символ `'i'` не равен '"'.
|
||||
</li>
|
||||
<li>Движок регулярных выражений увиличивает количество повторений точки на одно и пытается найти соответствие остатку шаблона ещё раз:
|
||||
|
||||
<img src="witch_lazy4.svg">
|
||||
Опять неудача. Тогда поисковой движок увеличивает количество повторений ещё и ещё...
|
||||
</li>
|
||||
<li>Только на 5м шаге поисковой движок наконец находит соответствие для остатка паттерна:
|
||||
|
||||
<img src="witch_lazy5.svg">
|
||||
</li>
|
||||
<li>Так как поиск происходит с флагом `g`, то он продолжается с конца текущего совпадения, давая ещё один результат:
|
||||
|
||||
<img src="witch_lazy6.svg">
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
В примере выше продемонстрирована работа ленивого режима для <code class="pattern">+?</code>. Квантификаторы <code class="pattern">+?</code> и <code class="pattern">??</code> ведут себя аналогично -- "ленивый" движок увеличивает количество повторений только в том случае, если для остальной части шаблона на данной позиции нет соответствия.
|
||||
|
||||
**Ленивость распространяется только на тот квантификатор, после которого стоит `?`.**
|
||||
|
||||
Прочие квантификаторы остаются жадными.
|
||||
|
||||
Например:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( "123 456".match(/\d+ \d+?/g) ); // 123 4
|
||||
```
|
||||
|
||||
<ol>
|
||||
<li>Подшаблон <code class="pattern">\d+</code> пытается найти столько цифр, сколько возможно (работает жадно), так что он находит <code class="match">123</code> и останавливается, поскольку символ пробела <code class="pattern">' '</code> не подходит под <code class="pattern">\d</code>.</li>
|
||||
<li>Далее в шаблоне пробел, он совпадает.</li>
|
||||
<li>Далее в шаблоне идёт <code class="pattern">\d+?</code>.
|
||||
|
||||
Квантификатор указан в ленивом режиме, поэтому он находит одну цифру <code class="match">4</code> и пытается проверить, есть ли совпадение с остатком шаблона.
|
||||
|
||||
Но после <code class="pattern">\d+?</code> в шаблоне ничего нет.
|
||||
|
||||
**Ленивый режим без необходимости лишний раз квантификатор не повторит.**
|
||||
|
||||
Так как шаблон завершился, то искать дальше, в общем-то нечего. Получено совпадение <code class="match">123 4</code>.</li>
|
||||
<li>Следующий поиск продолжится с `5`, но ничего не найдёт.</li>
|
||||
</ol>
|
||||
|
||||
[smart header="Конечные автоматы и не только"]
|
||||
Современные движки регулярных выражений могут иметь более хитрую реализацию внутренних алгоритмов, чтобы искать быстрее.
|
||||
|
||||
Однако, чтобы понять, как работает регулярное выражение, и строить регулярные выражения самому, знание этих хитрых алгоритмов ни к чему. Они служат лишь внутренней оптимизации способа поиска, описанного выше.
|
||||
|
||||
Кроме того, сложные регулярные выражения плохо поддаются всяким оптимизациям, так что поиск вполне может работать и в точности как здесь описано.
|
||||
[/smart]
|
||||
|
||||
## Альтернативный подход
|
||||
|
||||
В данном конкретном случае, возможно искать строки в кавычках, оставаясь в жадном режиме, с использованием регулярного выражения <code class="pattern">"[^"]+"</code>:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var reg = /"[^"]+"/g;
|
||||
|
||||
var str = 'a "witch" and her "broom" is one';
|
||||
|
||||
alert( str.match(reg) ); // witch, broom
|
||||
```
|
||||
|
||||
Регэксп <code class="pattern">"[^"]+"</code> даст правильные результаты, поскольку ищет кавычку <code class="pattern">'"'</code>, за которой идут столько не-кавычек (исключающие квадратные скобки), сколько возможно.
|
||||
|
||||
Так что вторая кавычка автоматически прекращает повторения <code class="pattern">[^"]+</code> и позволяет найти остаток шаблона <code class="pattern">"</code>.
|
||||
|
||||
**Эта логика ни в коей мере не заменяет ленивые квантификаторы!**
|
||||
|
||||
|
||||
Она просто другая. И то и другое бывает полезно.
|
||||
|
||||
Давайте посмотрим пример, когда нужен именно такой вариант, а ленивые квантификаторы не подойдут.
|
||||
|
||||
Например, мы хотим найти в тексте ссылки вида `<a href="..." class="doc">`, с любым содержанием `href`.
|
||||
|
||||
Какое регулярное выражение для этого подойдёт?
|
||||
|
||||
Первый вариант может выглядеть так: <code class="pattern">/<a href=".*" class="doc">/g</code>.
|
||||
|
||||
Проверим его:
|
||||
```js
|
||||
//+ run
|
||||
var str = '...<a href="link" class="doc">...';
|
||||
var reg = /<a href=".*" class="doc">/g;
|
||||
|
||||
// Сработало!
|
||||
alert( str.match(reg) ); // <a href="link" class="doc">
|
||||
```
|
||||
|
||||
А если в тексте несколько ссылок?
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = '...<a href="link1" class="doc">... <a href="link2" class="doc">...';
|
||||
var reg = /<a href=".*" class="doc">/g;
|
||||
|
||||
// Упс! Сразу две ссылки!
|
||||
alert( str.match(reg) ); // <a href="link1" class="doc">... <a href="link2" class="doc">
|
||||
```
|
||||
|
||||
На этот раз результат неверен.
|
||||
|
||||
Жадный <code class="pattern">.*</code> взял слишком много символов.
|
||||
|
||||
Соответствие получилось таким:
|
||||
```
|
||||
<a href="....................................." class="doc">
|
||||
<a href="link1" class="doc">... <a href="link2" class="doc">
|
||||
```
|
||||
|
||||
Модифицируем шаблон -- добавим ленивость квантификатору <code class="pattern">.*?</code>:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = '...<a href="link1" class="doc">... <a href="link2" class="doc">...';
|
||||
var reg = /<a href=".*?" class="doc">/g;
|
||||
|
||||
// Сработало!
|
||||
alert( str.match(reg) ); // <a href="link1" class="doc">, <a href="link2" class="doc">
|
||||
```
|
||||
|
||||
Теперь всё верно, два результата:
|
||||
|
||||
```
|
||||
<a href="....." class="doc"> <a href="....." class="doc">
|
||||
<a href="link1" class="doc">... <a href="link2" class="doc">
|
||||
```
|
||||
|
||||
Почему теперь всё в порядке -- для внимательного читателя, после объяснений, данных выше в этой главе, должно быть полностью очевидно.
|
||||
|
||||
Поэтому не будем останавливаться здесь на деталях, а попробуем ещё пример:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = '...<a href="link1" class="wrong">... <p style="" class="doc">...';
|
||||
var reg = /<a href=".*?" class="doc">/g;
|
||||
|
||||
// Неправильное совпадение!
|
||||
alert( str.match(reg) ); // <a href="link1" class="wrong">... <p style="" class="doc">
|
||||
```
|
||||
|
||||
Совпадение -- не ссылка, а более длинный текст.
|
||||
|
||||
Получилось следующее:
|
||||
<ol>
|
||||
<li>Найдено совпадение <code class="match"><a href="</code>.</li>
|
||||
<li>Лениво ищем <code class="pattern">.*?</code>, после каждого символа проверяя, есть ли совпадение остальной части шаблона.
|
||||
|
||||
Подшаблон <code class="pattern">.*?</code> будет брать символы до тех пор, пока не найдёт <code class="match">class="doc"></code>.
|
||||
|
||||
В данном случае этот поиск закончится уже за пределами ссылки, в теге `<p>`, вообще не имеющем отношения к `<a>`.
|
||||
</li>
|
||||
<li>Получившееся совпадение:
|
||||
|
||||
```
|
||||
<a href="..................................." class="doc">
|
||||
<a href="link1" class="wrong">... <p style="" class="doc">
|
||||
```
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
Итак, ленивость нам не помогла.
|
||||
|
||||
Необходимо как-то прекратить поиск <code class="pattern">.*</code>, чтобы он не вышел за пределы кавычек.
|
||||
|
||||
Для этого мы используем более точное указание, какие символы нам подходят, а какие нет.
|
||||
|
||||
Правильный вариант: <code class="pattern">[^"]*</code>. Этот шаблон будет брать все символы до ближайшей кавычки, как раз то, что требуется.
|
||||
|
||||
Рабочий пример:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str1 = '...<a href="link1" class="wrong">... <p style="" class="doc">...';
|
||||
var str2 = '...<a href="link1" class="doc">... <a href="link2" class="doc">...';
|
||||
var reg = /<a href="[^"]*" class="doc">/g;
|
||||
|
||||
// Работает!
|
||||
alert( str1.match(reg) ); // null, совпадений нет, и это верно
|
||||
alert( str2.match(reg) ); // <a href="link1" class="doc">, <a href="link2" class="doc">
|
||||
```
|
||||
|
||||
## Итого
|
||||
|
||||
Квантификаторы имеют два режима работы:
|
||||
<dl>
|
||||
<dt>Жадный</dt>
|
||||
<dd>Режим по умолчанию -- движок регулярных выражений повторяет его по-максимуму. Когда повторять уже нельзя, например нет больше цифр для `\d+`, он продолжает поиск с оставшейся части текста. Если совпадение найти не удалось -- отступает обратно, уменьшая количество повторений.</dd>
|
||||
<dt>Ленивый</dt>
|
||||
<dd>При указании после квантификатора символа `?` он работает в ленивом режиме. То есть, он перед каждым повторением проверяет совпадение оставшейся части шаблона на текущей позиции.</dd>
|
||||
</dl>
|
||||
|
||||
Как мы видели в примере выше, ленивый режим -- не панацея от "слишком жадного" забора символов. Альтернатива -- более аккуратно настроенный "жадный", с исключением символов. Как мы увидим далее, можно исключать не только символы, но и целые подшаблоны.
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="463px" height="130px" viewBox="0 0 463 130" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||
<!-- Generator: bin/sketchtool 1.3 (252) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>witch_greedy1.svg</title>
|
||||
<desc>Created with bin/sketchtool.</desc>
|
||||
<defs></defs>
|
||||
<g id="combined" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||
<g id="witch_greedy1.svg" sketch:type="MSArtboardGroup">
|
||||
<text id="a-"witch"-and-her-"b" sketch:type="MSTextLayer" font-family="Consolas" font-size="24" font-weight="normal" fill="#8A704D">
|
||||
<tspan x="20" y="112">a "witch" and her "broom" is one</tspan>
|
||||
</text>
|
||||
<rect id="Rectangle-1" stroke="#E8C48E" sketch:type="MSShapeGroup" x="45" y="50" width="15" height="77"></rect>
|
||||
<path d="M52.0039062,60.4414062 L51.6757812,65.9375 L49.7539062,65.9375 L49.4140625,60.4414062 L52.0039062,60.4414062 Z M56.5625,60.4414062 L56.234375,65.9375 L54.3125,65.9375 L53.9726562,60.4414062 L56.5625,60.4414062 Z" id="--"-----------------" fill="#CB1E31" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M52.5,9.5 L52.5,44.5" id="Line" stroke="#EE6B47" stroke-width="3" stroke-linecap="square" fill="#EE6B47" sketch:type="MSShapeGroup"></path>
|
||||
<path id="Line-decoration-1" d="M52.5,44.5 C53.55,40.72 54.45,37.48 55.5,33.7 C53.4,33.7 51.6,33.7 49.5,33.7 C50.55,37.48 51.45,40.72 52.5,44.5 C52.5,44.5 52.5,44.5 52.5,44.5 Z" stroke="#EE6B47" stroke-width="3" stroke-linecap="square" fill="#EE6B47"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="463px" height="130px" viewBox="0 0 463 130" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||
<!-- Generator: bin/sketchtool 1.3 (252) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>witch_greedy2.svg</title>
|
||||
<desc>Created with bin/sketchtool.</desc>
|
||||
<defs></defs>
|
||||
<g id="combined" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||
<g id="witch_greedy2.svg" sketch:type="MSArtboardGroup">
|
||||
<text id="a-"witch"-and-her-"b" sketch:type="MSTextLayer" font-family="Consolas" font-size="24" font-weight="normal" fill="#8A704D">
|
||||
<tspan x="20" y="107">a "witch" and her "broom" is one</tspan>
|
||||
</text>
|
||||
<rect id="Rectangle-1" stroke="#E8C48E" sketch:type="MSShapeGroup" x="45" y="45" width="29" height="77"></rect>
|
||||
<path d="M52.0039062,55.4414062 L51.6757812,60.9375 L49.7539062,60.9375 L49.4140625,55.4414062 L52.0039062,55.4414062 Z M56.5625,55.4414062 L56.234375,60.9375 L54.3125,60.9375 L53.9726562,55.4414062 L56.5625,55.4414062 Z M66.0898438,68.3085938 C66.3554701,68.3085938 66.6074207,68.3593745 66.8457031,68.4609375 C67.0839856,68.5625005 67.2910147,68.7031241 67.4667969,68.8828125 C67.642579,69.0625009 67.7812495,69.2714832 67.8828125,69.5097656 C67.9843755,69.7480481 68.0351562,70.0039049 68.0351562,70.2773438 C68.0351562,70.5429701 67.9843755,70.7929676 67.8828125,71.0273438 C67.7812495,71.2617199 67.642579,71.466796 67.4667969,71.6425781 C67.2910147,71.8183603 67.0839856,71.9570307 66.8457031,72.0585938 C66.6074207,72.1601568 66.3554701,72.2109375 66.0898438,72.2109375 C65.8164049,72.2109375 65.5625012,72.1601568 65.328125,72.0585938 C65.0937488,71.9570307 64.8886728,71.8183603 64.7128906,71.6425781 C64.5371085,71.466796 64.398438,71.2617199 64.296875,71.0273438 C64.195312,70.7929676 64.1445312,70.5429701 64.1445312,70.2773438 C64.1445312,70.0039049 64.195312,69.7480481 64.296875,69.5097656 C64.398438,69.2714832 64.5371085,69.0625009 64.7128906,68.8828125 C64.8886728,68.7031241 65.0937488,68.5625005 65.328125,68.4609375 C65.5625012,68.3593745 65.8164049,68.3085938 66.0898438,68.3085938 L66.0898438,68.3085938 Z" id="--".----------------" fill="#CB1E31" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M48.5,32.5 L69.5,32.5" id="Line" stroke="#EE6B47" stroke-width="3" stroke-linecap="square" fill="#EE6B47" sketch:type="MSShapeGroup"></path>
|
||||
<path id="Line-decoration-1" d="M69.5,32.5 C65.72,31.45 62.48,30.55 58.7,29.5 C58.7,31.6 58.7,33.4 58.7,35.5 C62.48,34.45 65.72,33.55 69.5,32.5 C69.5,32.5 69.5,32.5 69.5,32.5 Z" stroke="#EE6B47" stroke-width="3" stroke-linecap="square" fill="#EE6B47"></path>
|
||||
</g>
|
||||
<g id="witch_greedy3.svg" sketch:type="MSArtboardGroup" transform="translate(455.000000, 0.000000)"></g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 65 KiB |
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 25 KiB |
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="463px" height="130px" viewBox="0 0 463 130" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||
<!-- Generator: bin/sketchtool 1.3 (252) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>witch_lazy3.svg</title>
|
||||
<desc>Created with bin/sketchtool.</desc>
|
||||
<defs></defs>
|
||||
<g id="combined" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||
<g id="witch_lazy3.svg" sketch:type="MSArtboardGroup">
|
||||
<text id="a-"witch"-and-her-"b" sketch:type="MSTextLayer" font-family="Consolas" font-size="24" font-weight="normal" fill="#8A704D">
|
||||
<tspan x="20" y="105">a "witch" and her "broom" is one</tspan>
|
||||
</text>
|
||||
<rect id="Rectangle-1" stroke="#E8C48E" sketch:type="MSShapeGroup" x="45" y="43" width="29" height="77"></rect>
|
||||
<path d="M52.0039062,53.4414062 L51.6757812,58.9375 L49.7539062,58.9375 L49.4140625,53.4414062 L52.0039062,53.4414062 Z M56.5625,53.4414062 L56.234375,58.9375 L54.3125,58.9375 L53.9726562,53.4414062 L56.5625,53.4414062 Z M66.0898438,66.3085938 C66.3554701,66.3085938 66.6074207,66.3593745 66.8457031,66.4609375 C67.0839856,66.5625005 67.2910147,66.7031241 67.4667969,66.8828125 C67.642579,67.0625009 67.7812495,67.2714832 67.8828125,67.5097656 C67.9843755,67.7480481 68.0351562,68.0039049 68.0351562,68.2773438 C68.0351562,68.5429701 67.9843755,68.7929676 67.8828125,69.0273438 C67.7812495,69.2617199 67.642579,69.466796 67.4667969,69.6425781 C67.2910147,69.8183603 67.0839856,69.9570307 66.8457031,70.0585938 C66.6074207,70.1601568 66.3554701,70.2109375 66.0898438,70.2109375 C65.8164049,70.2109375 65.5625012,70.1601568 65.328125,70.0585938 C65.0937488,69.9570307 64.8886728,69.8183603 64.7128906,69.6425781 C64.5371085,69.466796 64.398438,69.2617199 64.296875,69.0273438 C64.195312,68.7929676 64.1445312,68.5429701 64.1445312,68.2773438 C64.1445312,68.0039049 64.195312,67.7480481 64.296875,67.5097656 C64.398438,67.2714832 64.5371085,67.0625009 64.7128906,66.8828125 C64.8886728,66.7031241 65.0937488,66.5625005 65.328125,66.4609375 C65.5625012,66.3593745 65.8164049,66.3085938 66.0898438,66.3085938 L66.0898438,66.3085938 Z" id="--"."---------------" fill="#CB1E31" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M78.3945312,53.4414062 L78.0664062,58.9375 L76.1445312,58.9375 L75.8046875,53.4414062 L78.3945312,53.4414062 Z" id="Path" fill="#5C8058" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M82.953125,53.4414062 L82.625,58.9375 L80.703125,58.9375 L80.3632812,53.4414062 L82.953125,53.4414062 Z" id="Path" fill="#5C8058" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M50.5,33.5 L71.5,33.5" id="Line" stroke="#EE6B47" stroke-width="3" stroke-linecap="square" fill="#EE6B47" sketch:type="MSShapeGroup"></path>
|
||||
<path id="Line-decoration-1" d="M71.5,33.5 C67.72,32.45 64.48,31.55 60.7,30.5 C60.7,32.6 60.7,34.4 60.7,36.5 C64.48,35.45 67.72,34.55 71.5,33.5 C71.5,33.5 71.5,33.5 71.5,33.5 Z" stroke="#EE6B47" stroke-width="3" stroke-linecap="square" fill="#EE6B47"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.2 KiB |
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="463px" height="130px" viewBox="0 0 463 130" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||
<!-- Generator: bin/sketchtool 1.3 (252) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>witch_lazy4.svg</title>
|
||||
<desc>Created with bin/sketchtool.</desc>
|
||||
<defs></defs>
|
||||
<g id="combined" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||
<g id="witch_lazy4.svg" sketch:type="MSArtboardGroup">
|
||||
<text id="a-"witch"-and-her-"b" sketch:type="MSTextLayer" font-family="Consolas" font-size="24" font-weight="normal" fill="#8A704D">
|
||||
<tspan x="20" y="105">a "witch" and her "broom" is one</tspan>
|
||||
</text>
|
||||
<rect id="Rectangle-1" stroke="#E8C48E" sketch:type="MSShapeGroup" x="45" y="43" width="41" height="77"></rect>
|
||||
<path d="M52.0039062,53.4414062 L51.6757812,58.9375 L49.7539062,58.9375 L49.4140625,53.4414062 L52.0039062,53.4414062 Z M56.5625,53.4414062 L56.234375,58.9375 L54.3125,58.9375 L53.9726562,53.4414062 L56.5625,53.4414062 Z M66.0898438,66.3085938 C66.3554701,66.3085938 66.6074207,66.3593745 66.8457031,66.4609375 C67.0839856,66.5625005 67.2910147,66.7031241 67.4667969,66.8828125 C67.642579,67.0625009 67.7812495,67.2714832 67.8828125,67.5097656 C67.9843755,67.7480481 68.0351562,68.0039049 68.0351562,68.2773438 C68.0351562,68.5429701 67.9843755,68.7929676 67.8828125,69.0273438 C67.7812495,69.2617199 67.642579,69.466796 67.4667969,69.6425781 C67.2910147,69.8183603 67.0839856,69.9570307 66.8457031,70.0585938 C66.6074207,70.1601568 66.3554701,70.2109375 66.0898438,70.2109375 C65.8164049,70.2109375 65.5625012,70.1601568 65.328125,70.0585938 C65.0937488,69.9570307 64.8886728,69.8183603 64.7128906,69.6425781 C64.5371085,69.466796 64.398438,69.2617199 64.296875,69.0273438 C64.195312,68.7929676 64.1445312,68.5429701 64.1445312,68.2773438 C64.1445312,68.0039049 64.195312,67.7480481 64.296875,67.5097656 C64.398438,67.2714832 64.5371085,67.0625009 64.7128906,66.8828125 C64.8886728,66.7031241 65.0937488,66.5625005 65.328125,66.4609375 C65.5625012,66.3593745 65.8164049,66.3085938 66.0898438,66.3085938 L66.0898438,66.3085938 Z M79.2851562,66.3085938 C79.5507826,66.3085938 79.8027332,66.3593745 80.0410156,66.4609375 C80.2792981,66.5625005 80.4863272,66.7031241 80.6621094,66.8828125 C80.8378915,67.0625009 80.976562,67.2714832 81.078125,67.5097656 C81.179688,67.7480481 81.2304688,68.0039049 81.2304688,68.2773438 C81.2304688,68.5429701 81.179688,68.7929676 81.078125,69.0273438 C80.976562,69.2617199 80.8378915,69.466796 80.6621094,69.6425781 C80.4863272,69.8183603 80.2792981,69.9570307 80.0410156,70.0585938 C79.8027332,70.1601568 79.5507826,70.2109375 79.2851562,70.2109375 C79.0117174,70.2109375 78.7578137,70.1601568 78.5234375,70.0585938 C78.2890613,69.9570307 78.0839853,69.8183603 77.9082031,69.6425781 C77.732421,69.466796 77.5937505,69.2617199 77.4921875,69.0273438 C77.3906245,68.7929676 77.3398438,68.5429701 77.3398438,68.2773438 C77.3398438,68.0039049 77.3906245,67.7480481 77.4921875,67.5097656 C77.5937505,67.2714832 77.732421,67.0625009 77.9082031,66.8828125 C78.0839853,66.7031241 78.2890613,66.5625005 78.5234375,66.4609375 C78.7578137,66.3593745 79.0117174,66.3085938 79.2851562,66.3085938 L79.2851562,66.3085938 Z" id="--".."--------------" fill="#CB1E31" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M91.5898438,53.4414062 L91.2617188,58.9375 L89.3398438,58.9375 L89,53.4414062 L91.5898438,53.4414062 Z" id="Path" fill="#5C8058" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M96.1484375,53.4414062 L95.8203125,58.9375 L93.8984375,58.9375 L93.5585938,53.4414062 L96.1484375,53.4414062 Z" id="Path" fill="#5C8058" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M50.5,29.5 L81.5,29.5" id="Line" stroke="#EE6B47" stroke-width="3" stroke-linecap="square" fill="#EE6B47" sketch:type="MSShapeGroup"></path>
|
||||
<path id="Line-decoration-1" d="M81.5,29.5 C77.72,28.45 74.48,27.55 70.7,26.5 C70.7,28.6 70.7,30.4 70.7,32.5 C74.48,31.45 77.72,30.55 81.5,29.5 C81.5,29.5 81.5,29.5 81.5,29.5 Z" stroke="#EE6B47" stroke-width="3" stroke-linecap="square" fill="#EE6B47"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 7.4 KiB |
After Width: | Height: | Size: 14 KiB |
|
@ -0,0 +1,30 @@
|
|||
Регулярное выражение для поиска 3-значного цвета вида `#abc`: <code class="pattern">/#[a-f0-9]{3}/i</code>.
|
||||
|
||||
Нужно добавить ещё три символа, причём нужны именно три, четыре или семь символов не нужны. Эти три символа либо есть, либо нет.
|
||||
|
||||
Самый простой способ добавить -- просто дописать в конец регэкспа: <code class="pattern">/#[a-f0-9]{3}([a-f0-9]{3})?/i</code>
|
||||
|
||||
Можно поступить и хитрее: <code class="pattern">/#([a-f0-9]{3}){1,2}/i</code>.
|
||||
|
||||
Здесь регэксп <code class="pattern">[a-f0-9]{3}</code> заключён в скобки, чтобы квантификатор <code class="pattern">{1,2}</code> применялся целиком ко всей этой структуре.
|
||||
|
||||
В действии:
|
||||
```js
|
||||
//+ run
|
||||
var re = /#([a-f0-9]{3}){1,2}/gi;
|
||||
|
||||
var str = "color: #3f3; background-color: #AA00ef; and: #abcd";
|
||||
|
||||
alert( str.match(re) ); // #3f3 #AA0ef #abc
|
||||
```
|
||||
|
||||
В последнем выражении <code class="subject">#abcd</code> было найдено совпадение <code class="match">#abc</code>. Чтобы этого не происходило, добавим в конец <code class="pattern">\b</code>:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var re = /#([a-f0-9]{3}){1,2}\b/gi;
|
||||
|
||||
var str = "color: #3f3; background-color: #AA00ef; and: #abcd";
|
||||
|
||||
alert( str.match(re) ); // #3f3 #AA0ef
|
||||
```
|
|
@ -0,0 +1,14 @@
|
|||
# Найдите цвет в формате #abc или #abcdef
|
||||
|
||||
Напишите регулярное выражение, которое находит цвет в формате `#abc` или `#abcdef`. То есть, символ `#`, после которого идут 3 или 6 шестнадцатиричных символа.
|
||||
|
||||
Пример использования:
|
||||
```js
|
||||
var re = /* ваш регэксп */
|
||||
|
||||
var str = "color: #3f3; background-color: #AA00ef; and: #abcd";
|
||||
|
||||
alert( str.match(re) ); // #3f3 #AA0ef
|
||||
```
|
||||
|
||||
P.S. Значения из любого другого количества букв, кроме 3 и 6, такие как `#abcd`, не должны подходить под регэксп.
|
|
@ -0,0 +1,53 @@
|
|||
Регулярное выражение для числа, возможно, дробного и отрицательного: <code class="pattern">-?\d+(\.\d+)?</code>. Мы уже разбирали его в предыдущих задачах.
|
||||
|
||||
Оператор -- это <code class="pattern">[-+*/]</code>. Заметим, что дефис <code class="pattern">-</code> идёт в списке первым, так как на любой позиции, кроме первой и последней, он имеет специальный смысл внутри <code class="pattern">[...]</code>, и его понадобилось бы экранировать.
|
||||
|
||||
Кроме того, когда мы оформим это в JavaScript-синтаксис <code class="pattern">/.../</code> -- понадобится заэкранировать слэш <code class="pattern">/</code>.
|
||||
|
||||
Нам нужно число, затем оператор, затем число, и необязательные пробелы между ними.
|
||||
|
||||
Полное регулярное выражение будет таким: <code class="pattern">-?\d+(\.\d+)?\s*[-+*/]\s*-?\d+(\.\d+)?</code>.
|
||||
|
||||
Чтобы получить результат в виде массива, добавим скобки вокруг тех данных, которые нам интересны, то есть -- вокруг чисел и оператора: <code class="pattern">(-?\d+(\.\d+)?)\s*([-+*/])\s*(-?\d+(\.\d+)?)</code>.
|
||||
|
||||
Посмотрим в действии:
|
||||
```js
|
||||
//+ run
|
||||
var re = /(-?\d+(\.\d+)?)\s*([-+*\/])\s*(-?\d+(\.\d+)?)/;
|
||||
|
||||
alert( "1.2 + 12".match(re) );
|
||||
```
|
||||
|
||||
Итоговый массив будет включать в себя компоненты:
|
||||
|
||||
<ul>
|
||||
<li>`result[0] == "1.2 + 12"` (вначале всегда полное совпадение)</li>
|
||||
<li>`result[1] == "1"` (первая скобка)</li>
|
||||
<li>`result[2] == "2"` (вторая скобка -- дробная часть `(\.\d+)?`)</li>
|
||||
<li>`result[3] == "+"` (...)</li>
|
||||
<li>`result[4] == "12"` (...)</li>
|
||||
<li>`result[5] == undefined` (последняя скобка, но у второго числа дробная часть отсутствует)</li>
|
||||
</ul>
|
||||
|
||||
Нам из этого массива нужны только числа и оператор. А, скажем, дробная часть сама по себе -- не нужна.
|
||||
|
||||
Уберём её из запоминания, добавив в начало скобки <code class="pattern">?:</code>, то есть: <code class="pattern">(?:\.\d+)?</code>.
|
||||
|
||||
Итого, решение:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
function parse(expr) {
|
||||
var re = /(-?\d+(?:\.\d+)?)\s*([-+*\/])\s*(-?\d+(?:\.\d+)?)/;
|
||||
|
||||
var result = expr.match(re);
|
||||
|
||||
if (!result) return;
|
||||
result.shift();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
alert( parse("-1.23 * 3.45") ); // -1.23, *, 3.45
|
||||
```
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
# Разобрать выражение
|
||||
|
||||
Арифметическое выражение состоит из двух чисел и операции между ними, например:
|
||||
<ul>
|
||||
<li>`1 + 2`</li>
|
||||
<li>`1.2 * 3.4`</li>
|
||||
<li>`-3 / -6`</li>
|
||||
<li>-2 - 2`</li>
|
||||
</ul>
|
||||
|
||||
Список операций: `"+"`, `"-"`, `"*"` и `"/"`.
|
||||
|
||||
Также могут присутсововать пробелы вокруг оператора и чисел.
|
||||
|
||||
Напишите функцию, которая будет получать выражение и возвращать массив из трёх аргументов:
|
||||
<ol>
|
||||
<li>Первое число.</li>
|
||||
<li>Оператор.</li>
|
||||
<li>Второе число.</li>
|
||||
</ul>
|
151
10-regular-expressions-javascript/7-regexp-groups/article.md
Normal file
|
@ -0,0 +1,151 @@
|
|||
# Скобочные группы
|
||||
|
||||
Часть шаблона может быть заключена в скобки <code class="pattern">(...)</code>. Такие выделенные части шаблона называют "скобочными выражениями" или "скобочными группами".
|
||||
|
||||
У такого выделения есть два эффекта:
|
||||
<ol>
|
||||
<li>Он позволяет выделить часть совпадения в отдельный элемент массива при поиске через [String#match](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/match) или [RegExp#exec](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec).</li>
|
||||
<li>Если поставить квантификатор после скобки, то он применится *ко всей скобке*, а не всего лишь к одному символу.</li>
|
||||
</ol>
|
||||
|
||||
[cut]
|
||||
|
||||
## Пример
|
||||
|
||||
В примере ниже, шаблон <code class="pattern">(go)+</code> находит один или более повторяющихся <code class="pattern">'go'</code>:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
alert( 'Gogogo now!'.match(/(go)+/i ); // "Gogogo"
|
||||
```
|
||||
|
||||
Без скобок, шаблон <code class="pattern">/go+/</code> означал бы <code class="subject">g</code>, после которого идёт одна или более <code class="subject">o</code>, например: <code class="match">goooo</code>. А скобки "группируют" <code class="pattern">(go)</code> вместе.
|
||||
|
||||
|
||||
## Содержимое группы
|
||||
|
||||
Скобки нумеруются слева направо. Поисковой движок запоминает содержимое каждой скобки и позволяет обращаться к нему -- в шаблоне и строке замены и, конечно же, в результатах.
|
||||
|
||||
Например, найти HTML-тег можно шаблоном <code class="pattern"><.*?></code>.
|
||||
|
||||
После поиска мы захотим что-то сделать с результатом. Для удобства заключим содержимое `<...>` в скобки: <code class="pattern"><(.*?)></code>. Тогда оно будет доступно отдельно.
|
||||
|
||||
При поиске методом [String#match](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/match) в результирующем массиве будет сначала всё совпадение, а далее -- скобочные группы. В шаблоне <code class="pattern"><(.*?)></code> скобочная группа только одна:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = '<h1>Привет, мир!</h1>';
|
||||
var reg = /<(.*?)>/;
|
||||
|
||||
alert( str.match(reg) ); // массив: <h1>, h1
|
||||
```
|
||||
|
||||
Заметим, что метод [String#match](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/match) выдаёт скобочные группы только при поиске без флага `/.../g`. В примере выше он нашёл только первое совпадение <code class="match"><h1></code>, а закрывающий <code class="match"></h1></code> не нашёл, поскольку без флага `/.../g` ищется только первое совпадение.
|
||||
|
||||
Для того, чтобы искать и с флагом `/.../g` и со скобочными группами, используется метод [RegExp#exec](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec):
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = '<h1>Привет, мир!</h1>';
|
||||
var reg = /<(.*?)>/g;
|
||||
|
||||
var match;
|
||||
|
||||
while ((match = reg.exec(str)) !== null) {
|
||||
// сначала выведет первое совпадение: <h1>,h1
|
||||
// затем выведет второе совпадение: </h1>,/h1
|
||||
alert(match);
|
||||
}
|
||||
```
|
||||
|
||||
Теперь найдено оба совпадения <code class="pattern"><(.*?)></code>, каждое -- массив из полного совпадения и скобочных групп (одна в данном случае).
|
||||
|
||||
## Вложенные группы
|
||||
Скобки могут быть и вложенными. В этом случае нумерация также идёт слева направо.
|
||||
|
||||
Например, при поиске тега в <code class="subject"><span class="my"></code> нас может интересовать:
|
||||
|
||||
<ol>
|
||||
<li>Содержимое тега целиком: `span class="my"`.</li>
|
||||
<li>В отдельную переменную для удобства хотелось бы поместить тег: `span`.</li>
|
||||
<li>Также может быть удобно отдельно выделить атрибуты `class="my"`.</li>
|
||||
</ol>
|
||||
|
||||
Добавим скобки в регулярное выражение:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = '<span class="my">';
|
||||
|
||||
reg = /<(([a-z]+)\s*([^>]*))>/;
|
||||
|
||||
alert( str.match(reg) ); // <span class="my">, span, s
|
||||
```
|
||||
|
||||
Вот так выглядят скобочные группы:
|
||||
|
||||
<img src="regexp-nested-groups.svg">
|
||||
|
||||
На нулевом месте -- всегда совпадение полностью, далее -- группы. Нумерация всегда идёт слева направо, по открывающей скобке.
|
||||
|
||||
В данном случае получилось, что группа 1 включает в себя содержимое групп 2 и 3. Это совершенно нормальная ситуация, которая возникает, когда нужно выделить что-то отдельное внутри большей группы.
|
||||
|
||||
**Даже если скобочная группа необязательна и не входит в совпадение, соответствующий элемент массива существует (и равен `undefined`).**
|
||||
|
||||
Например, рассмотрим регэксп <code class="pattern">a(z)?(c)?</code>. Он ищет `"a"`, за которой не обязательно идёт буква `"z"`, за которой необязательно идёт буква `"c"`.
|
||||
|
||||
Если напустить его на строку из одной буквы `"a"`, то результат будет таков:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
match = 'a'.match(/a(z)?(c)?/)
|
||||
|
||||
alert( match.length ); // 3
|
||||
alert( match[0] ); // a
|
||||
alert( match[1] ); // undefined
|
||||
alert( match[2] ); // undefined
|
||||
```
|
||||
|
||||
Массив получился длины `3`, но все скобочные группы -- `undefined`.
|
||||
|
||||
А теперь более сложная ситуация, строка <code class="subject">ack</code>:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
match = 'ack'.match(/a(z)?(c)?/)
|
||||
|
||||
alert( match.length ); // 3
|
||||
alert( match[0] ); // ac, всё совпадение
|
||||
alert( match[1] ); // undefined, для (z)? ничего нет
|
||||
alert( match[2] ); // c
|
||||
```
|
||||
|
||||
Длина массива результатов по-прежнему `3`. Она постоянна. А вот для скобочной группы <code class="pattern">(z)?</code> в ней ничего нет, поэтому результат: `["ac", undefined, "c"]`.
|
||||
|
||||
## Исключение из запоминания через ?:
|
||||
|
||||
Бывает так, что скобки нужны, чтобы квантификатор правильно применился, а вот запоминать её в массиве не нужно.
|
||||
|
||||
Скобочную группу можно исключить из запоминаемых и нумеруемых, добавив в её начало <code class="pattern">?:</code>.
|
||||
|
||||
|
||||
Например, мы хотим найти <code class="pattern">(go)+</code>, но содержимое скобок (`go`) в отдельный элемент массива выделять не хотим.
|
||||
|
||||
Для этого нужно сразу после открывающей скобки поставить `?:`, то есть: <code class="pattern">(?:go)+</code>.
|
||||
|
||||
Например:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = "Gogo John!";
|
||||
*!*
|
||||
var reg = /(?:go)+ (\w+)/i;
|
||||
*/!*
|
||||
|
||||
var result = str.match(reg);
|
||||
|
||||
alert( result.length ); // 2
|
||||
alert( result[1] ); // John
|
||||
```
|
||||
|
||||
В примере выше массив результатов имеет длину `2` и содержит только полное совпадение и результат <code class="pattern">(\w+)</code>. Это удобно в тех случаях, когда содержимое скобок нас не интересует.
|
|
@ -0,0 +1,49 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="320px" height="130px" viewBox="0 0 320 130" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||
<!-- Generator: bin/sketchtool 1.3 (252) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>regexp-nested-groups.svg</title>
|
||||
<desc>Created with bin/sketchtool.</desc>
|
||||
<defs></defs>
|
||||
<g id="combined" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||
<g id="regexp-nested-groups.svg" sketch:type="MSArtboardGroup">
|
||||
<text id="<(([a-z]+)\s*([^>]*)" sketch:type="MSTextLayer" font-family="Consolas" font-size="24" font-weight="normal">
|
||||
<tspan x="20" y="75" fill="#8A704D"><</tspan>
|
||||
<tspan x="33.1953125" y="75" fill="#DC2022">((</tspan>
|
||||
<tspan x="59.5859375" y="75" fill="#8A704D">[a-z]+</tspan>
|
||||
<tspan x="138.757812" y="75" fill="#DC2022">)</tspan>
|
||||
<tspan x="151.953125" y="75" fill="#8A704D">\s*</tspan>
|
||||
<tspan x="191.539062" y="75" fill="#DC2022">(</tspan>
|
||||
<tspan x="204.734375" y="75" fill="#8A704D">[^>]*</tspan>
|
||||
<tspan x="270.710938" y="75" fill="#D0011B">))</tspan>
|
||||
<tspan x="297.101562" y="75" fill="#8A704D">></tspan>
|
||||
</text>
|
||||
<path d="M42.5,45.6458333 L42.5,29.3541667" id="Line" stroke="#D0011B" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M290.5,45.6458333 L290.5,29.3541667" id="Line-2" stroke="#D0011B" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M42.5,28.5 L290.5,28.5" id="Line" stroke="#D0011B" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M52.5,101.645833 L52.5,85.3541667" id="Line-5" stroke="#D0011B" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M145.5,101.645833 L145.5,85.3541667" id="Line-4" stroke="#D0011B" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M52.5,102.5 L145.5,102.5" id="Line-3" stroke="#D0011B" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
|
||||
<text id="1" sketch:type="MSTextLayer" font-family="Consolas" font-size="20" font-weight="normal" fill="#D0011B">
|
||||
<tspan x="29" y="44">1</tspan>
|
||||
</text>
|
||||
<text id="span-class="my"" sketch:type="MSTextLayer" font-family="Consolas" font-size="20" font-weight="normal" fill="#417505">
|
||||
<tspan x="82" y="26">span class="my"</tspan>
|
||||
</text>
|
||||
<text id="2" sketch:type="MSTextLayer" font-family="Consolas" font-size="20" font-weight="normal" fill="#D0011B">
|
||||
<tspan x="40" y="101">2</tspan>
|
||||
</text>
|
||||
<text id="span" sketch:type="MSTextLayer" font-family="Consolas" font-size="20" font-weight="normal" fill="#417505">
|
||||
<tspan x="73" y="117">span</tspan>
|
||||
</text>
|
||||
<path d="M197.5,101.645833 L197.5,85.3541667" id="Line-8" stroke="#D0011B" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M277.5,101.645833 L277.5,85.3541667" id="Line-7" stroke="#D0011B" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
|
||||
<path d="M197.5,102.5 L277.5,102.5" id="Line-6" stroke="#D0011B" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
|
||||
<text id="3" sketch:type="MSTextLayer" font-family="Consolas" font-size="20" font-weight="normal" fill="#D0011B">
|
||||
<tspan x="185" y="101">3</tspan>
|
||||
</text>
|
||||
<text id="class="my"" sketch:type="MSTextLayer" font-family="Consolas" font-size="20" font-weight="normal" fill="#417505">
|
||||
<tspan x="185" y="119">class="my"</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.9 KiB |
|
@ -0,0 +1,21 @@
|
|||
|
||||
Открывающий тег -- это <code class="pattern">\[(b|url|quote)\]</code>.
|
||||
|
||||
Для того, чтобы найти всё до закрывающего -- используем ленивый поиск <code class="pattern">[\s\S]*?</code> и обратную ссылку на открывающий тег.
|
||||
|
||||
Итого, получится: <code class="pattern">\[(b|url|quote)\][\s\S]*?\[/\1\]</code>.
|
||||
|
||||
В действии:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var re = /\[(b|url|quote)\][\s\S]*?\[\/\1\]/g;
|
||||
|
||||
var str1 = "..[url]http://ya.ru[/url]..";
|
||||
var str2 = "..[url][b]http://ya.ru[/b][/url]..";
|
||||
|
||||
alert( str1.match(re) ); // [url]http://ya.ru[/url]
|
||||
alert( str2.match(re) ); // [url][b]http://ya.ru[/b][/url]
|
||||
```
|
||||
|
||||
Для закрывающего тега `[/1]` понадобилось дополнительно экранировать слеш: `\[\/1\]`.
|
|
@ -0,0 +1,41 @@
|
|||
# Найдите пары тегов
|
||||
|
||||
ББ-тег имеет вид `[имя]...[/имя]`, где имя -- слово, одно из: `b`, `url`, `quote`.
|
||||
|
||||
Например:
|
||||
```
|
||||
[b]текст[/b]
|
||||
[url]http://ya.ru[/url]
|
||||
```
|
||||
|
||||
ББ-теги могут быть вложенными, но сам в себя тег быть вложен не может, например:
|
||||
|
||||
```
|
||||
Допустимо:
|
||||
[url] [b]http://ya.ru[/b] [/url]
|
||||
[quote] [b]текст[/b] [/quote]
|
||||
|
||||
Нельзя:
|
||||
[b][b]текст[/b][/b]
|
||||
```
|
||||
|
||||
Создайте регулярное выражение для поиска ББ-тегов и их содержимого.
|
||||
|
||||
Например:
|
||||
|
||||
```js
|
||||
var re = /* регулярка */
|
||||
|
||||
var str = "..[url]http://ya.ru[/url]..";
|
||||
alert( str.match(re) ); // [url]http://ya.ru[/url]
|
||||
```
|
||||
|
||||
Если теги вложены, то нужно искать самый внешний тег (при желании можно будет продолжить поиск в его содержимом):
|
||||
|
||||
```js
|
||||
var re = /* регулярка */
|
||||
|
||||
var str = "..[url][b]http://ya.ru[/b][/url]..";
|
||||
alert( str.match(re) ); // [url][b]http://ya.ru[/b][/url]
|
||||
```
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
# Обратные ссылки: \n и $n
|
||||
|
||||
Скобочные группы можно не только получать в результате.
|
||||
|
||||
На скобочные группы можно ссылаться как в самом паттерне, так и в строке замены.
|
||||
[cut]
|
||||
|
||||
## Группа в строке замены
|
||||
|
||||
Ссылки в строке замены имеют вид `$n`, где `n` -- это номер скобочной группы.
|
||||
|
||||
Вместо `$n` подставляется содержимое соответствующей скобки:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var name = "Александр Пушкин";
|
||||
|
||||
name = name.replace(/([а-яё]+) ([а-яё]+)/i, *!*"$2, $1"*/!*);
|
||||
alert( name ); // Пушкин, Александр
|
||||
```
|
||||
|
||||
В примере выше вместо <code class="pattern">$2</code> подставляется второе найденное слово, а вместо <code class="pattern">$1</code> -- первое.
|
||||
|
||||
## Группа в шаблоне
|
||||
|
||||
Выше был пример использования содержимого групп в строке замены. Это удобно, когда нужно реорганизовать содержимое или создать новое с использованием старого.
|
||||
|
||||
Но к скобочной группе можно также обратиться в самом поисковом шаблоне, ссылкой вида `\номер`.
|
||||
|
||||
Чтобы было яснее, рассмотрим это на реальной задаче -- необходимо найти в тексте строку в кавычках. Причём кавычки могут быть одинарными <code class="subject">'...'</code> или двойными <code class="subject">"..."</code> -- и то и другое должно искаться корректно.
|
||||
|
||||
Как такие строки искать?
|
||||
|
||||
Можно в регэкспе предусмотреть произвольные кавычки: <code class="pattern">`['"](.*?)['"]`</code>. Такой регэксп найдёт строки вида <code class="match">"..."</code>, <code class="match">'...'</code>, но он даст неверный ответ в случае, если одна кавычка ненароком оказалась внутри другой, как например в строке <code class="subject">"She's the one!"</code>:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
str = "He said: \"She's the one!\".";
|
||||
|
||||
reg = /['"](.*?)['"]/g;
|
||||
|
||||
// Результат не соответствует замыслу
|
||||
alert( str.match(reg) ); // "She'
|
||||
```
|
||||
|
||||
Как видно, регэксп нашёл открывающую кавычку <code class="match">"</code>, затем текст, вплоть до новой кавычки <code class="match">'</code>, которая закрывает соответствие.
|
||||
|
||||
Для того, чтобы попросить регэксп искать закрывающую кавычку -- такую же, как открывающую, мы обернём её в скобочную группу и используем обратную ссылку на неё:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
str = "He said: \"She's the one!\".";
|
||||
|
||||
reg = /(['"])(.*?)\1/g;
|
||||
|
||||
alert( str.match(reg) ); // "She's the one!"
|
||||
```
|
||||
|
||||
Теперь работает верно! Движок регулярных выражений, найдя первое скобочное выражение -- кавычку <code class="pattern">(['"])</code>, запоминает его и далее <code class="pattern">\1</code> означает "найти то же самое, что в первой скобочной группе".
|
||||
|
||||
Обратим внимание на два нюанса:
|
||||
|
||||
<ul>
|
||||
<li>Чтобы использовать скобочную группу в строке замены -- нужно использовать ссылку вида `$1`, а в шаблоне -- обратный слэш: `\1`.</li>
|
||||
<li>Чтобы в принципе иметь возможность обратиться к скобочной группе -- не важно откуда, она не должна быть исключена из запоминаемых при помощи `?:`. Скобочные группы вида `(?:...)` не участвуют в нумерации.</li>
|
||||
</ul>
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
Сначала неправильный способ.
|
||||
|
||||
Если перечислить языки один за другим через `|`, то получится совсем не то:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var reg = /Java|JavaScript|PHP|C|C\+\+/g;
|
||||
|
||||
var str = "Java, JavaScript, PHP, C, C++";
|
||||
|
||||
alert( str.match(reg) ); // Java,Java,PHP,C,C
|
||||
```
|
||||
|
||||
Как видно, движок регулярных выражений ищет альтернации в порядке их перечисления. То есть, он сначала смотрит, есть ли <code class="match">Java</code>, а если нет -- ищет <code class="match">JavaScript</code>.
|
||||
|
||||
Естественно, при этом <code class="match">JavaScript</code> не будет найдено никогда.
|
||||
|
||||
То же самое -- с языками <code class="match">C</code> и <code class="match">C++</code>.
|
||||
|
||||
Есть два решения проблемы:
|
||||
|
||||
<ol>
|
||||
<li>Поменять порядок, чтобы более длинное совпадение проверялось первым: <code class="pattern">JavaScript|Java|C\+\+|C|PHP</code>.</li>
|
||||
<li>Соединить длинный вариант с коротким: <code class="pattern">Java(Script)?|C(\+\+)?|PHP</code>.</li>
|
||||
</ol>
|
||||
|
||||
В действии:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var reg = /Java(Script)?|C(\+\+)?|PHP/g;
|
||||
|
||||
var str = "Java, JavaScript, PHP, C, C++";
|
||||
|
||||
alert( str.match(reg) ); // Java,JavaScript,PHP,C,C++
|
||||
```
|
|
@ -0,0 +1,6 @@
|
|||
# Найдите языки программирования
|
||||
|
||||
Существует много языков программирования, например Java, JavaScript, PHP, C, C++.
|
||||
|
||||
Напишите регулярное выражение, которое найдёт их все в строке "Java JavaScript PHP C++ C"
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
Решение задачи: <code class="pattern">/"(\\.|[^"\\])*"/g</code>.
|
||||
|
||||
То есть:
|
||||
<ul>
|
||||
<li>Сначала ищем кавычку <code class="pattern">"</code></li>
|
||||
<li>Затем, если далее слэш <code class="pattern">\\</code> (удвоение слэша -- техническое, для вставки в регэксп, на самом деле там один слэш), то после него также подойдёт любой символ (точка).</li>
|
||||
<li>Если не слэш, то берём любой символ, кроме кавычек (которые будут означать конец строки) и слэша (чтобы предотвратить одинокие слэши, сам по себе единственный слэш не нужен, он должен экранировать какой-то символ) <code class="pattern">[^"\\]</code></li>
|
||||
<li>...И так жадно, до закрывающей кавычки.</li>
|
||||
</ul>
|
||||
|
||||
В действии:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var re = /"(\\.|[^"\\])*"/g;
|
||||
var str = '.. "test me" .. "Скажи \\"Привет\\"!" .. "\\r\\n\\\\" ..';
|
||||
|
||||
alert( str.match(re) ); // "test me","Скажи \"Привет\"!","\r\n\\"
|
||||
```
|
|
@ -0,0 +1,26 @@
|
|||
# Найдите строки в кавычках
|
||||
|
||||
Найдите в тексте при помощи регэкспа строки в двойных кавычках <code class="subject">"..."</code>.
|
||||
|
||||
В строке поддерживается экранирование при помощи слеша -- примерно в таком же виде, как в обычных строках JavaScript. То есть, строка может содержать любые символы, экранированные слэшем, в частности: <code class="subject">\"</code>, <code class="subject">\n</code>, и даже сам слэш в экранированном виде: <code class="subject">\\</code>.
|
||||
|
||||
Здесь особо важно, что двойная кавычка после слэша не оканчивает строку, а считается её частью. В этом и состоит основная сложность задачи, которая без этого условия была бы элементарной.
|
||||
|
||||
Пример совпадающих строк:
|
||||
```js
|
||||
.. *!*"test me"*/!* .. (обычная строка)
|
||||
.. *!*"Скажи \"Привет\"!"*/!* ... (строка с кавычками внутри)
|
||||
.. *!*"\r\n\\"*/!* .. (строка со спец. символами и слэшем внутри)
|
||||
```
|
||||
|
||||
Заметим, что в JavaScript такие строки удобнее всего задавать в одинарных кавычках, и слеши придётся удвоить (в одинарных кавычках они являются экранирующими символами):
|
||||
|
||||
Пример задания тестовой строки в JavaScript:
|
||||
```js
|
||||
//+ run
|
||||
var str = ' .. "test me" .. "Скажи \\"Привет\\"!" .. "\\r\\n\\\\" .. ';
|
||||
|
||||
// эта строка будет такой:
|
||||
alert(str); // .. "test me" .. "Скажи \"Привет\"!" .. "\r\n\\" ..
|
||||
```
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
|
||||
Начало шаблона очевидно: ``pattern`<style`.
|
||||
|
||||
А вот дальше... Мы не можем написать просто ``pattern`<style.*?>`, так как ``match`<styler>` удовлетворяет этому регэкспу.
|
||||
|
||||
Нужно уточнить его. После ``match`<style` должен быть либо пробел, после которого может быть что-то ещё, либо закрытие тега.
|
||||
|
||||
На языке регэкспов: ``pattern`<style(>|\s.*?>)`.
|
||||
|
||||
В действии:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var re = /<style(>|\s.*?>)/g;
|
||||
|
||||
alert( "<style> <styler> <style test>".match(re) ); // <style>, <style test>
|
||||
```
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
# Найдите тег style
|
||||
|
||||
Напишите регулярное выражение, которое будет искать в тексте тег `<style>`. Подходят как обычный тег `<style>`, так и вариант с атрибутами `<style type="...">`.
|
||||
|
||||
Но регулярное выражение не должно находить `<styler>`!
|
||||
|
||||
Использование:
|
||||
|
||||
```js
|
||||
var re = ваш регэксп
|
||||
|
||||
alert( "<style> <styler> <style test>".match(re) ); // <style>, <style test>
|
||||
```
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
# Альтернация (или) |
|
||||
|
||||
Альтернация -- термин в регулярных выражениях, которому в русском языке соответствует слово "ИЛИ". Она обозначается символом вертикальной черты <code class="pattern">|</code> и позволяет выбирать между вариантами.
|
||||
|
||||
[cut]
|
||||
|
||||
Например, нам нужно найти языки программирования: HTML, PHP, Java и JavaScript.
|
||||
|
||||
Соответствующее регулярное выражение: <code class="pattern">html|php|java(script)?</code>.
|
||||
|
||||
Пример использования:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var reg = /html|php|css|java(script)?/gi
|
||||
|
||||
var str = "Сначала появился HTML, затем CSS, потом JavaScript"
|
||||
|
||||
alert( str.match(reg) ) // 'HTML', 'CSS', 'JavaScript'
|
||||
```
|
||||
|
||||
Мы уже знаем похожую вещь -- квадратные скобки. Они позволяют выбирать между символами, например <code class="pattern">gr[ae]y</code> найдёт <code class="match">gray</code>, либо <code class="match">grey</code>.
|
||||
|
||||
Альтернация работает уже не посимвольно, а на уровне фраз и подвыражений. Регэксп <code class="pattern">A|B|C</code> обозначает поиск одного из выражений: `A`, `B` или `C`, причём в качестве выражений могут быть другие, сколь угодно сложные регэкспы.
|
||||
|
||||
Для указания границ альтернации используют скобки `(...)`, например: <code class="pattern">before(XXX|YYY)after</code> будет искать <code class="match">beforeXXXafter</code> или <code class="match">beforeYYYafter</code>.
|
3
10-regular-expressions-javascript/index.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Регулярные выражения
|
||||
|
||||
Регулярные выражения -- мощный способ поиска и замены для строк.
|