This commit is contained in:
Ilya Kantor 2014-10-26 22:10:13 +03:00
parent 06f61d8ce8
commit f301cb744d
2271 changed files with 103162 additions and 0 deletions

View file

@ -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
```
Как видим, то же самое, разве что для регэкспа использован метод [:String#search(reg)], а для строки [:String#indexOf(substr)]. Но это соответствие лишь кажущееся. Очень скоро мы усложним регулярные выражения, и тогда появятся отличия.
[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>С регом `/ЛЮ/` вызов вернул `-1`, что означает "не найдено" (то же соглашение, что и `indexOf`),</li>
<li>С регом `/ЛЮ/i` вызов нашёл совпадение на позиции 2, так как стоит флаг `i`, а значит "лю" тоже подходит.</li>
</ol>
Следующий, пожалуй, самый важный флаг -- это `g`.
Мы рассмотрим его в следующей секции, вместе с основными методами поиска по регулярным выражениям в JavaScript.
## Итого
<ul>
<li>Регулярное выражение состоит из шаблона и необязательных флагов `g`, `i` и `m`.</li>
<li>Поиск по регулярному выражению <code class="pattern">/регэксп/</code> без флагов и спец. символов, которые мы изучим далее -- это то же самое, что и обычный поиск подстроки в строке.</li>
<li>Метод строки `str.search(regexp)` возвращает индекс, на котором найдено совпадение.</li>
</ul>

View file

@ -0,0 +1,376 @@
# Методы RegExp и String
В JavaScript методы для работы с регулярными выражениями есть в классе `RegExp`, а также встроены в обычные строки `String`
Всего этих методов немного, поэтому мы сначала рассмотрим их по отдельности, а затем -- рецепты по решению стандартных задач с ними.
[cut]
## Методы строк
### str.search(regexp)
Этот метод мы уже видели.
Он возвращает позицию первого совпадения или `-1`, если ничего не найдено.
```js
//+ run
var str = "Люблю регэкспы я, но странною любовью";
alert( str.search( *!*/лю/i*/!* ) ); // 0
```
**Ограничение метода `search` -- он всегда ищет только первое совпадение.**
Нельзя заставить `search` искать дальше первого совпадения, такой синтаксис попросту не предусмотрен. Но есть другие методы, которые это умеют.
### str.match(regexp) без флага g, скобочные выражения
Метод `str.match` работает по-разному, в зависимости от наличия или отсутствия флага `g`, поэтому сначала мы разберём вариант, когда его нет.
В этом случае `str.match(regexp)` находит только одно, первое совпадение.
Результат вызова -- это массив, состоящий из этого совпадения, с дополнительными свойствами `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 (часть совпадения, соответствующая скобкам)
// также есть свойства result.index, result.input
```
Благодаря флагу `i` поиск не обращает внимание на регистр буквы, поэтому находит <code class="match">javascript</code>. При этом часть строки, соответствующая <code class="pattern">SCRIPT</code>, выделена в отдельный элемент массива. Позже мы используем это для поиска с заменой.
### str.match(regexp) с флагом 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], которые будет рассмотрен далее.
[warn header="В случае, если совпадений не было, `match` возвращает `null`"]
Обратите внимание, это важно -- если `match` не нашёл совпадений, он возвращает не пустой массив, а именно `null`.
Это важно иметь в виду, чтобы не попасть в такую ловушку:
```js
//+ run
var str = "Ой-йой-йой";
alert( str.match( /лю/gi ).length ) // ошибка! нет свойства length у null
```
[/warn]
### str.split(regexp|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(regexp, newSubStr|function)
Швейцарский нож для работы со строками, поиска и замены любого уровня сложности.
Его простейшее применение -- поиск и замена подстроки в строке, вот так:
```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>
<tr>
<th>Спецсимволы</th>
<th>Действие в строке замены</th>
</tr>
<tr>
<td>`$$`</td>
<td>Вставляет `"$"`.</td>
</tr>
<tr>
<td>`$&`</td>
<td>Вставляет всё найденное совпадение.</td>
</tr>
<tr>
<td><code>$&#096;</code></td>
<td>Вставляет часть строки до совпадения.</td>
</tr>
<tr>
<td>
<code>$'</code>
</td>
<td>Вставляет часть строки после совпадения.</td>
</tr>
<tr>
<td>
<code>$*n*</code>
</td>
<td>где `n` -- цифра или двузначное число, обозначает `n-ю` по счёту скобку, если считать слева-направо.</td>
</tr>
</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 аргумента.
Используем это, чтобы вывести полную информацию о совпадениях:
```js
//+ run
// вывести и заменить все совпадения
function replacer(str, offset, s) {
alert("Найдено: " + str + " на позиции: " + offset + " в строке: " + s);
return ":"
}
var result = "ОЙ-Ой-ой".replace( /ой/gi, replacer);
alert('Результат: ' + result);
```
Со скобочными выражениями:
```js
//+ run
function replacer(str, name, surname, offset, s) {
return surname +", " + name;
}
alert( str.replace( /(Василий) (Пупкин)/ , replacer) ) // Пупкин, Василий
```
Функция для замены -- это самое мощное средство, какое только может быть. Она владеет всей информацией о совпадении и имеет доступ к замыканию, поэтому может всё.
## Методы объектов RegExp
Регэкспы являются объектами класса [:RegExp].
У них есть два основных метода.
### regexp.test(str)
Проверяет, есть ли хоть одно совпадение в строке `str`. Возвращает `true/false`.
Работает, по сути, так же, как и проверка `str.search(regexp) != -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)
Этот метод -- самое мощное, что только есть для поиска с использованием регулярных выражений.
Он ведёт себя по-разному, в зависимости от того, есть ли у регэкспа флаг `g`.
<ul>
<li>Если флага `g` нет, то `regexp.exec(str)` ищет и возвращает первое совпадение, является полным аналогом вызова `str.match(regexp)`.</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]
## Итого, рецепты
Методы становятся гораздо понятнее, если разбить их использование по задачам, которые нужны в реальной жизни.
<ul>
<li>Для поиска только одного совпадения:
<dl>
<dt>Найти позицию первого совпадения</dt>
<dd>`str.search(regexp)`</dd>
<dt>Найти само совпадение</dt>
<dd>`str.match(regexp)`</dd>
<dt>Проверить, есть ли хоть одно совпадение</dt>
<dd>`regexp.test(str)`, также можно использовать `str.search(regexp) != -1`</dd>
<dt>Найти совпадение с нужной позиции</dt>
<dd>`regexp.exec(str)`, начальную позицию поиска задать в `regexp.lastIndex`</dd>
</dl>
</li>
<li>Для поиска всех совпадений:
<dl>
<dt>Найти массив совпадений</dt>
<dd>`str.match(regexp)`, с флагом `g`</dd>
<dt>Получить все совпадения, с подробной информацией о каждом</dt>
<dd>`regexp.exec(str)` с флагом `g`, в цикле</dd>
</dl>
</li>
<li>Дополнительно:
<dl>
<dt>Для поиска-и-замены</dt>
<dd>`str.replace(regexp, str|func)`</dd>
<dt>Для разбивки строки на части</dt>
<dd>`str.split(regexp)`</dd>
</dl>
</li>
</ul>
Далее мы перейдём к более подробному изучению синтаксиса регулярных выражений.

View file

@ -0,0 +1,152 @@
# Символьные классы
Рассмотрим задачу -- есть телефонный номер `"+7(903)-123-45-67"`, и нам нужно найти в этой строке цифры, а остальные символы нас не интересуют.
**Для поиска символов определённого вида, в регулярных выражениях предусмотрены "классы символов".**
[cut]
Класс символов -- это, в первую очередь, специальное обозначение.
Например, в данном случае нам нужен класс "произвольная цифра", он обозначается `\d`.
Это обозначение вставляется в паттерн наравне с остальными символами. При поиске под него подходит любая цифра.
Пример ниже ищет все цифры в строке:
```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
```
Есть и другие классы. Самые полезные:
<dl>
<dt>`\d` (от английского "digit" - "цифра")</dt>
<dd>Цифра, символ от `0` до `9`.</dd>
<dt>`\s` (от английского "space" - "пробел")</dt>
<dd>Пробельный символ, включая табы, переводы строки и т.п.</dd>
<dt>`\w` (от английского "word" -- "слово") </dt>
<dd>Символ латинского алфавита или цифра или подчёркивание `'_'`</dd>
</dl>
**Регулярное выражение обычно содержит одновременно и обычные символы и классы**:
Например, найдём строку `CSS` с любой цифровой версией:
```js
//+ run
var str = "Стандарт CSS4 - наше будущее";
var reg = /CSS\d/
alert( str.match(reg) );
```
Несколько классов в одном регэкспе:
```js
//+ run
showMatch( "Я люблю HTML5!", /\s\w\w\w\w\d/ ); // 'HTML5'
```
Совпадение (каждому классу в регэкспе соответствует один символ результата):
<img src="love_html5.png">
**Также существуют обратные символьные классы:**
<dl>
<dt>`\D`</dt>
<dd>Не-цифра, любой символ кроме `\d`</dd>
<dt>`\S`</dt>
<dd>Не-пробел, любой символ кроме `\s`.</dd>
<dt>`\W`</dt>
<dd>Символ, не принадлежащий латиннице, а также не буква и не подчёркивание, алфавиту, т.е. любой кроме `\w`</dd>
</dl>
Например, мы хотим получить из телефона <code class="subject">+7(903)-123-45-67</code> только цифры.
Есть два способа сделать это.
<ol>
<li>Первый -- найти все цифры и объединить их:
Например:
```js
//+ run
var str = "+7(903)-123-45-67";
var digits = str.match( /\d/g ).join("");
alert(digits); // 79031234567
```
</li>
<li>Второй -- найти все НЕцифры и удалить их из строки:
```js
//+ run
var str = "+7(903)-123-45-67";
alert( str.replace(/\D/g, "") ); // 79031234567
```
Второй способ короче, не правда ли?
</li>
</ol>
**Регулярное выражение может также содержать стандартные спецсимволы строк, такие как `\n, \t` и другие.**
Они являются обычными символами. Отличить их от классов очень просто -- для классов зарезервированы другие буквы.
[warn header="Пробелы важны!"]
Обычно мы не обращаем внимание на пробелы. Для нашего взгляда строки <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 нет пробелов
```
[/warn]
Особым классом символов является точка `"."`.
**В регулярном выражении, точка <code class="pattern">"."</code> обозначает *любой символ*, кроме перевода строки**:
```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/) ); // нет совпадений, так как для точки нет символа
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View file

@ -0,0 +1,46 @@
# Экранирование специальных символов
В регулярных выражениях есть и другие символы, имеющие особый смысл.
Они используются, чтобы расширить возможности поиска.
Вот их полный список: <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>. Поэтому его тоже нужно экранировать: <code><code>'\/'</code></code>**.
Так выглядит поиск слэша `'/'`:
```js
//+ run
alert( "/".match( /\// ) ); // '/'
```
Ну и, наконец, если нам нужно найти сам обратный слэш `\`, то его нужно просто задублировать.
Так выглядит поиск обратного слэша `"\"`:
```js
//+ run
alert( "1\2".match( /\\/ ) ); // '\'
```

View file

@ -0,0 +1,149 @@
# Наборы и диапазоны [...]
Если в регулярном выражении нескольки символов или символьных классов заключены в квадратные скобки `[…]`, то это означает "искать любой символ из указанных в `[…]`".
Например, <code class="pattern">[еао]</code> означает любой символ из этих трёх: `'а'`, `'е'`, или `'о'`.
[cut]
Можно использовать квадратные скобки вместе с обычными символами.
Например:
```js
//+ run
// найти [г или т], а затем "оп"
alert( "Гоп-стоп".match( /[гт]оп/gi ) ); // "Гоп", "топ"
```
Несмотря на то, что в квадратных скобках несколько символов, в совпадении должен присутствовать *ровно один* из них.
Поэтому в примере ниже нет результатов:
```js
//+ run
// найти "В", затем [у или а], затем "ля"
alert( "Вуаля".match( /В[уа]ля/ ) ); // совпадений нет
```
Совпадение было бы, если бы после `"Ву"` шло сразу `"ля"`, например <code class="subject">Вуля</code> или если бы `"Ва"` сопровождалось `"ля"`, то есть <code class="subject">Валя</code>.
**Квадратные скобки могут также содержать *диапазоны символов*.**
Например, <code class="pattern">[a-z]</code> -- произвольный символ от `a` до `z`, <code class="pattern">[0-5]</code> -- цифра от `0` до `5`.
В примере ниже мы будем искать `"x"`, после которого идёт два раза `[0-9A-F]` -- цифра или буква от 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>, но оно не подошло, потому что буквы в нём маленькие, а в диапазоне -- большие.
Если хочется искать и его тоже, можно добавить в скобки диапазон `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 ) ); // "@", "."
```
**В квадратных скобках большинство специальных символов можно использовать без экранирования.**
Обычно, если мы хотим искать именно точку, а не любой символ, или именно `"\d"`, а не произвольную цифру, то мы используем экранирование: указываем `\.` или `\\d`.
Но квадратные скобки подразумевают поиск одного символа из нескольких или из диапазона. Использование некоторых специальных символов внутри них не имело бы смысла, и они воспринимаются как обычные символы.
Это касается символов:
<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>, если не находится в начале квадратных скобок, то есть кроме <code class="pattern">[\^..]</code>.</li>
<li>а также открывающая квадратная скобка <code class="pattern">'['</code></li>
</ul>
То есть, точка `"."` в квадратных скобках означает не "любой символ", а обычную точку.
Например, регэксп <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) ); // тоже работает: +, -
```

View file

@ -0,0 +1,9 @@
Символ `.` является специальным, значит его надо экранировать.
Регулярное выражение:
```js
//+ run
var reg = /\.{3,}/g;
alert("Привет!... Как дела?.....".match(reg)); // ..., .....
```

View file

@ -0,0 +1,14 @@
# Как найти многоточие... ?
[importance 5]
Напишите регулярное выражения для поиска многоточий: трёх или более точек подряд.
Проверьте его:
```js
var reg = /ваше выражение/g;
alert("Привет!... Как дела?.....".match(reg)); // ..., .....
```

View file

@ -0,0 +1,47 @@
# Цифровые квантификаторы {n}
Рассмотрим задачу -- взять телефон вида `+7(903)-123-45-67` и найти все числа в нём. То есть, нас интересует результат вида `7, 903, 123, 45, 67`.
Нечто похожее мы уже делали ранее -- мы искали цифры. Для этого было достаточно класса `\d`. Но здесь нужно искать *числа* -- последовательности из 1 или более цифр.
**Количество повторений символа можно указать с помощью числа в фигурных скобках: `{n}`.**
<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>Для того, чтобы найти, например, числа размером от трёх до пяти знаков, нужно указать границы в фигурных скобках: `\d{3,5}`
```js
//+ run
alert( "Мне не 12, а 1234 года".match( /\d{3,5}/ ) ); // "1234"
```
Последнее значение можно и не указывать. Тогда выражение `\d{3,}` найдет числа, длиной от трех знаков:
```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
```

View file

@ -0,0 +1,113 @@
# Квантификаторы +, * и ?
Для самые часто востребованных квантификаторов есть специальные короткие обозначения.
[cut]
<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">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">/&lt;[a-z]+&gt;/i</code></dt>
<dd>Пример:
```js
//+ run
alert( "<BODY> ... </BODY>".match ( /<[a-z]+>/gi ) ); // <BODY>
```
Это регулярное выражение ищет символ <code class="pattern">'&lt;'</code>, за которым идут одна или более букв английского алфавита, и затем <code class="pattern">'&gt;'</code>.
</dd>
<dt>Открывающий HTML-тег без атрибутов (лучше): <code class="pattern">/&lt;[a-z][a-z0-9]*&gt;/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">/&lt;\/?[a-z][a-z0-9]*&gt;/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">&lt;\w+&gt;</code>.
Так как класс `\w` означает "любая цифра или английская буква или _`, то под такой шаблон подойдут и не теги, например <code class="match">&lt;_&gt;</code>, однако он гораздо проще, чем <code class="pattern">&lt;[a-z][a-z0-9]*&gt;</code>.
Подойдёт ли нам <code class="pattern">&lt;\w+&gt;</code> или нужно использовать именно <code class="pattern">&lt;[a-z][a-z0-9]*&gt;</code>?
В реальной жизни допустимы оба варианта. Ответ на подобные вопросы зависит от того, насколько реально важна точность и насколько сложно потом будет отфильтровать лишние совпадения (если появятся).
[/smart]

View file

@ -0,0 +1,179 @@
# Жадный и ленивый режимы
Теперь время залезть "под капот" регулярных выражений и посмотреть, как происходит поиск.
Это понимание необходимо для того, чтобы искать что-либо сложнее чем <code class="pattern">/\d+/</code>.
[cut]
Для примера рассмотрим задачу, которая часто возникает в типографике -- заменить кавычки вида `"..."` на "кавычки-лапки": `«...»`.
Шаблон, который мы будем использовать для поиска подстрок в кавычках, будет выглядеть так:
```js
//+ run
var reg = /".+"/g;
var str = 'a "witch" and her "broom" is one';
alert( str.match(reg) );
```
Запустите этот пример...
Упс! Он не работает! Вместо того, чтобы найти два совпадения <code class="match">"witch"</code> и <code class="match">"broom"</code>, он находит одно: <code class="match">"witch" and her "broom"</code>.
Это как раз тот случай, когда *жадность* -- причина всех зол.
## Алгоритм поиска
Движок регулярных выражений пытается проверить строку на соответствие шаблону, начиная с самой первой, нулевой позиции. Если не получается, он идёт вперёд и пытается найти с 1й позиции и так далее.
Чтобы сделать происходящее максимально наглядным и понятным, проследим, что именно он делает для паттерна <code class="pattern">".+"</code>.
<ol>
<li>Первый символ шаблона -- это кавычка <code class="pattern">"</code>.
Движок регулярных выражений пытается найти её на 0й позиции, но там совсем другой символ, поэтому на 0й позиции соответствия явно нет.
Далее он переходит 1ю, 2ю позицию в исходной строке и, наконец, обнаруживает кавычку на 3й позиции:
<img src="witch_greedy1.png">
</li>
<li>Кавычка найдена, далее движок проверяет, есть ли соответствие для остальной части паттерна.
В данном случае следующий символ паттерна -- `.` (точка). Она обозначает "любой символ", так что следующая буква строки <code class="match">'w'</code> вполне подходит:
<img src="witch_greedy2.png">
</li>
<li>Далее "любой символ" повторяется, так как стоит квантификатор <code class="pattern">.+</code>. Движок регулярных выражений берёт один символ за другим, до тех пор, пока у него это получается.
В данном случае это означает "до конца строки":
<img src="witch_greedy3.png">
</li>
<li>Итак, текст закончился, движок регулярных выражений больше не может найти "любой символ", он закончил строить соответствие для <code class="pattern">.+</code> и очень рад по этому поводу.
Следующий символ шаблона -- это кавычка. Её тоже необходимо найти, чтобы соответствие было полным. А тут -- беда, ведь поисковый текст завершился!
Движок регулярных выражений понимает, что, наверное, взял многовато <code class="pattern">.+</code> и начинает отступать обратно ("фаза бэктрекинга" -- backtracking на англ.).
Иными словами, он сокращает текущее совпадение на один символ:
<img src="witch_greedy4.png">
После этого он ещё раз пытается подобрать соответствие для остатка паттерна. Но кавычка <code class="pattern">'"'</code> не совпадает с <code class="subject">'e'</code>.</li>
<li>...Так что движок уменьшает число повторений <code class="pattern">.+</code> ещё раз:
<img src="witch_greedy5.png">
Кавычка <code class="pattern">'"'</code> не совпадает с <code class="subject">'n'</code>. Опять неудача.</li>
<li>Движок продолжает отступать, он уменьшает количество повторений точки <code class="pattern">'.'</code> до тех пор, пока остаток паттерна не совпадёт:
<img src="witch_greedy6.png">
</li>
<li>Мы получили результат. Так как у регэкспа есть флаг `g`, то поиск продолжится, однако это произойдёт после первого совпадения и не даст новых результатов.</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">/".+?"/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
```
Чтобы в точности понять, что происходим, разберём в деталях, как ищется <code class="pattern">".+?"</code>.
<ol>
<li>Первый шаг -- тот же, кавычка <code class="pattern">'"'</code> найдена на 3й позиции:
<img src="witch_greedy1.png">
</li>
<li>Второй шаг -- тот же, находим произвольный символ <code class="pattern">'.'</code>:
<img src="witch_greedy2.png">
</li>
<li>А вот дальше -- так как стоит ленивый режим работы `+`, то движок пытается повторять точку (произвольный символ) *минимальное количество раз*.
Так что он тут же пытается проверить, достаточно ли повторить 1 раз -- и для этого пытается найти соответствие остальной части шаблона, то есть <code class="pattern">'"'</code>:
<img src="witch_lazy3.png">
Нет, один раз повторить недостаточно. В данном случае, символ `'i' != '"'`, но если бы оставшаяся часть паттерна была бы более сложной -- алгоритм остался бы тем же. Если остаток шаблона не находится -- увеличиваем количество повторений.
</li>
<li>Движок регулярных выражений увиличивает количество повторений точки на одно и пытается найти соответствие остатку шаблона ещё раз:
<img src="witch_lazy4.png">
Опять неудача. Тогда поисковой движок увеличивает количество повторений ещё и ещё...
</li>
<li>Только на 5м шаге поисковой движок наконец находит соответствие для остатка паттерна:
<img src="witch_lazy5.png">
</li>
<li>Так как поиск происходит с флагом `g`, то он продолжается с конца текущего совпадения, давая ещё один результат:
<img src="witch_lazy6.png">
</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>Далее идёт пробел, и в игру вступает <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>.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -0,0 +1,126 @@
# Группы
Часть шаблона может быть заключена в скобки <code class="pattern">(...)</code>. Такие выделенные части шаблона называют "скобочными выражениями" или "скобочными группами".
У такого выделения есть два эффекта:
<ol>
<li>Он позволяет выделить часть совпадения в отдельный элемент массива при поиске через [:String#match] или [: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>.
**Скобки нумеруются слева направо. Поисковой движок запоминает содержимое каждой скобки и позволяет обращаться к нему, в том числе -- в шаблоне и строке замены.**
Например, найти HTML-тег можно шаблоном <code class="pattern">&lt;.*?&gt;</code>. Скорее всего, после поиска мы захотим что-то сделать с результатом, и нас будет интересовать содержимое `<...>`.
Для удобства заключим его в скобки: <code class="pattern">&lt;(.*?)&gt;</code>. Тогда содержимое скобок можно будет получить отдельно.
Используем метод [:String#match]. В результирующем массиве будет сначала всё совпадение, а далее -- скобочные группы, в данном случае -- только одна:
```js
//+ run
var str = '<h1>Привет, мир!</h1>'
var reg = /<(.*?)>/
alert( str.match(reg) ) // массив: <h1>, h1
```
Для поиска всех совпадений, как мы обсуждали ранее, используется метод [:RegExp#exec].
**Скобки могут быть и вложенными. В этом случае нумерация также идёт слева направо.**
Например, в строке <code class="subject">&lt;span class="my"&gt;</code> нас может интересовать отдельно тег `span` и, для примера, его первая буква.
Добавим скобки в регулярное выражение:
```js
//+ run
var str = '<span class="my">';
reg = /<(([a-z])[a-z0-9]*).*?>/;
alert( str.match(reg) ); // <span class="my">, span, s
```
Вот так выглядят скобочные группы:
<img src="groups.png">
На нулевом месте -- всегда совпадение полностью, далее -- группы. Их вложенность означает всего лишь, что группа 1 содержит группу 2. Нумерация всегда идёт слева направо, по открывающей скобке.
**Даже если скобочная группа необязательна и не входит в совпадение, соответствующий элемент массива существует (и равен `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> в ней ничего нет.
**Скобочную группу можно исключить из запоминаемых и нумеруемых, добавив в её начало <code class="pattern">?:</code>**
Бывает так, что скобки нужны, чтобы квантификатор правильно применился, а вот запоминать её в массиве не нужно. Тогда мы просто ставим сразу после открывающей скобки `?:`
В примере ниже есть скобочная группа <code class="pattern">(go-?)</code>, которая сама по себе не интересна, но входит в результаты:
```js
//+ run
var str = "Go-go John!";
*!*
var reg = /(go-?)* (\w+)/i;
*/!*
var result = str.match(reg);
alert( result[0] ); // Go-go John
alert( result[1] ); // go
alert( result[2] ); // John
```
Исключим её из запоминаемых:
```js
//+ run
var str = "Go-go John!";
*!*
var reg = /(?:go-?)* (\w+)/i;
*/!*
var result = str.match(reg);
alert( result[0] ); // Go-go John
alert( result[1] ); // John
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

View file

@ -0,0 +1,52 @@
# Обратные ссылки \\n и $n
На скобочные группы можно ссылаться как в самом паттерне, так и в строке замены.
[cut]
Ссылки в строке замены мы уже видели: они имеют вид `$n`, где `n` -- это номер скобочной группы. Вместо `$n` подставляется содержимое соответствующей скобки:
```js
//+ run
var name = "Александр Пушкин";
name = name.replace(/([а-яё]+) ([а-яё]+)/i, "$2, $1");
alert(name); // Пушкин, Александр
```
К скобочной группе можно также обратиться в самом шаблоне.
Рассмотрим это в реальном примере -- необходимо найти строку в кавычках. Эта строка может быть в одинарных кавычках <code class="subject">'...'</code> или в двойных <code class="subject">"..."</code> -- не важно, в каких именно, но открывающая и закрывающая кавычки должны быть одинаковыми.
Как такие строки искать? Регэксп <code class="pattern">`['"](.*?)['"]`</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"
```
Теперь работает верно!
Обратим внимание на два нюанса:
<ul>
<li>В строке замены ссылка на первую скобочную группу выглядит как `$1`, а в шаблоне нужно использовать `\1`.</li>
<li>Чтобы обращаться к скобочной группе -- не важно откуда, она не должна быть исключена из запоминаемых при помощи `?:`, то есть `(?:['"])` не подошло бы.</li>
</ul>

View file

@ -0,0 +1,82 @@
# Чёрная дыра бэктрекинга
Некоторые регулярные выражения, с виду являясь простыми, могут выполняться оооочень долго, и даже подвешивать браузер.
[cut]
Например, попробуйте пример ниже в Chrome или IE (осторожно, подвесит браузер!):
```js
//+ run
alert( '123456789012345678901234567890z'.match(/(\d+)*$/) );
```
Некоторые движки регулярных выражений (Firefox) справляются с таким регэкспом, а некоторые (IE, Chrome) -- нет.
В чём же дело, что не так с регэкспом?
Да с регэкспом-то всё так, синтаксис вполне допустимый. Проблема в том, как выполняется поиск по нему.
Для краткости рассмотрим более короткую строку: <code class="subject">1234567890z</code>:
<ol>
<li>Первым делом, движок регэкспов пытается найти <code class="pattern">\d+</code>. Плюс <code class="pattern">+</code> является жадным по умолчанию, так что он хватает все цифры, какие может.
Затем движок пытается применить звёздочку вокруг скобок <code class="pattern">(\d+)*</code>, но больше цифр нет, так что звёздочка не даёт повторений.
После этого в паттерне остаётся <code class="pattern">$</code>, а в тексте -- символ <code class="subject">z</code>.
<img src="bad_backtrack_greedy1.png">
Так как соответствия нет, то жадный плюс <code class="pattern">+</code> отступает на один символ (бэктрекинг, зелёная стрелка на рисунке выше).
</li>
<li>После бэктрекинга, <code class="pattern">\d+</code> содержит всё число, кроме последней цифры. Затем движок снова пытается найти совпадение, уже с новой позиции (`9`).
Звёздочка <code class="pattern">(\d+)*</code> теперь может быть применена -- она даёт ещё одно число <code class="match">9</code>:
<img src="bad_backtrack_greedy11.png">
Движок пытается найти `$`, но это ему не удаётся -- на его пути опять `z`:
<img src="bad_backtrack_greedy2.png">
Так как совпадения нет, то поисковой движок отступает назад ещё раз.
</li>
<li>Теперь первое число <code class="pattern">\d+</code> будет содержать 8 цифр, а остаток строки <code class="subject">90</code> становится вторым <code class="pattern">\d+</code>:
<img src="bad_backtrack_greedy3.png">
Увы, всё ещё нет соответствия для <code class="pattern">$</code>.
Поисковой движок снова должен отступить назад. При этом последний жадный квантификатор отпускает символ. В данном случае это означает, что укорачивается второй <code class="pattern">\d+</code>, до одного символа <code class="subject">9</code>.
</li>
<li>Теперь движок регулярных выражений снова может применить звёздочку и находит третье число <code class="pattern">\d+</code>:
<img src="bad_backtrack_greedy4.png">
...И снова неудача. Второе и третье <code class="pattern">\d+</code> отступили по-максимуму, так что сокращается снова первое число.
</li>
<li>Теперь есть 7 цифр в первом <code class="pattern">\d+</code>. Поисковой движок видит место для второго <code class="pattern">\d+</code>, теперь уже с позиции 8:
<img src="bad_backtrack_greedy5.png">
Так как совпадения нет, второй <code class="pattern">\d+</code> отступает назад....
</li>
<li>...И так далее, легко видеть, что поисковой движок будет перебирать *все возможные комбинации* <code class="pattern">\d+</code> в числе. А их много.</li>
</ol>
На этом месте умный читатель может воскликнуть: "Бэктрекинг? Давайте включим ленивый режим -- и не будет никакого бэктрекинга!"
Что ж, заменим <code class="pattern">\d+</code> на <code class="pattern">\d+?</code> и посмотрим (аккуратно, может подвесить браузер):
```js
//+ run
alert( '123456789012345678901234567890z'.match(/(\d+?)*$/) );
```
Не помогло!
**Ленивые регулярные выражения делают то же самое, но в обратном порядке.**
Просто подумайте о том, как будет в этом случае работать поисковой движок.
Некоторые движки регулярных выражений, например Firefox, содержат хитрые проверки, в дополнение к алгоритму выше, которые позволяют избежать бесконечного перебора или кардинально ускорить его, но все движки и не всегда.

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

View file

@ -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="match">gr<b>a</b>y</code> и <code class="match">gr<b>e</b>y</code>, можно использовать <code class="pattern">gr(a|e)y</code> или <code class="pattern">gr[ae]y</code>, но не <code class="pattern">gra|ey</code>. Последний регэксп применит альтернацию к подвыражениям: <code class="pattern">gra</code> (ИЛИ) <code class="pattern">ey</code>.

View file

@ -0,0 +1,62 @@
# Начало ^ и конец $ строки, многострочный режим
Знак каретки <code class="pattern">'^'</code> и доллара <code class="pattern">'$'</code> имеют в регулярном выражении особый смысл. Их называют "якорями" (anchor - англ.).
[cut]
Каретка <code class="pattern">^</code> совпадает в начале текста, а доллар <code class="pattern">$</code> -- в конце.
**Якоря являются не символами, а проверками. Они совпадают на "позиции".**
Это очень важное отличие по сравнению с символьными классами. Если движок регулярных выражений видит <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 ) // null (в начале строки чисел нет)*!*
```
Якоря используют одновременно, чтобы указать, что паттерн должен охватывать текст с начала и до конца. Обычно это требуется при валидации.
Например, мы хотим проверить, что в переменной `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>, то укажем якоря по обе стороны от него:
```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, дробь!
```

View file

@ -0,0 +1,59 @@
# Многострочный режим, флаг "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">$</code> ведёт себя точно так же.
Следующий пример находит последнее слово в строке:
TODO: указать на коренное отличие $ от \n: доллар не матчит символ, а \n матчит!!!!
```js
//+ run
showMatch(
'1st: *!*John*!*\n' +
'2nd: *!*Mary*/!*\n' +
'33rd: *!*Peter*/!*', /\w+$/gm ) // John, Mary, Peter
```
Please note that <code class="pattern">$</code> as well as <code class="pattern">^</code> doesn't add <code class="match">\n</code> to the match. They only check that the position is right.
[summary]
<ul>
<li>The caret <code class="pattern">'^'</code> matches the position at the the text start. *In multiline mode* it also matches after the newline symbol.</li>
<li>The dollar <code class="pattern">'$'</code> matches the position at the text end. *In multiline mode* it also matches before the newline symbol.</li>
</ul>
**For both anchors, the regexp engine only checks the position, and doesn't match a character.**
[/summary]

View file

@ -0,0 +1,69 @@
# Word boundary
Another position check is a *word boundary* <code class="pattern">\b</code>. It doesn't match a character, but matches in situations when a wordly character follows a non-wordly or vice versa. A "non-wordly" may also be text start or end.
[cut]
For example, <code class=pattern">\bdog\b</code> matches a standalone <code class="subject">dog</code>, not <code class="subject">doggy</code> or <code class="subject">catdog</code>:
```js
//+ run
showMatch( "doggy catdog dog", /\bdog\b/ ) // "dog"
```
Here, <code class="match">dog</code> matches, because the previous char is a space (non-wordly), and the next position is text end.
Normally, <code class="pattern">\w{4}</code> matches 4 consequent word characters.
If the word is long enough, it may match multiple times:
```js
//+ run
showMatch( "Boombaroom", /\w{4}/g) // 'Boom', 'baro'
```
Appending <code class="pattern">\b</code> causes <code class="pattern">\w{4}\b</code> to match only at word end:
```js
//+ run
showMatch( "Because life is awesome", /\w{4}\b/g) // 'ause', 'life', 'some'
```
**The word boundary <code class="pattern">\b</code> like <code class="pattern">^</code> and <code class="pattern">$</code> doesn't match a char. It only performs the check.**
Let's add the check from another side, <code class="pattern">\b\w{4}\b</code>:
```js
//+ run
showMatch( "Because life is awesome", /\b\w{4}\b/g) // 'life'
```
Now there is only one result <code class="match">life</code>.
<ol>
<li>The regexp engine matches first word boundary <code class="pattern">\b</code> at zero position:
<img src="boundary1.png">
</li>
<li>Then it successfully matches <code class="pattern">\w{4}</code>, but fails to match finishing <code class="pattern">\b</code>.
<img src="boundary2.png">
So, the match at position zero fails.
</li>
<li>The search continues from position 1, and the closest <code class="pattern">\b</code> is right after <code class="subject">Because</code> (position 9):
<img src="boundary3.png">
Now <code class="pattern">\w{4}</code> doesn't match, because the next character is a space.
</li>
<li>The search continues, and the closest <code class="pattern">\b</code> is right before <code class="subject">life</code> at position 11.
<img src="boundary4.png">
Finally, <code class="pattern">\w{4}</code> matches and the position check <code class="pattern">\b</code> after it is positive. We've got the result.
</li>
<li>The search continues after the match, but doesn't yield new results.</li>
</ol>
**The word boundary check <code class="pattern">/\b/</code> works only for words in latin alphabet,** because it is based on <code class="pattern">\w</code> as "wordly" chars. Sometimes that's acceptable, but limits the application range of the feature.
And, for completeness..
**There is also an inverse check <code class="pattern">\B</code>, meaning a position other than <code class="pattern">\b</code>.** It is extremely rarely used.

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

View file

@ -0,0 +1,7 @@
# Practice
Here you found tasks which help in understanding regexp construction principles.
[cut]
The tutorial is not finished. Till now, we have it up to this part...

View file

@ -0,0 +1,5 @@
# Задачи-сироты по регекспам

View file

@ -0,0 +1,9 @@
# Регулярные выражения [в работе]
Регулярные выражения -- мощный способ поиска и замены строк, который используется в самых разных языках, включая, конечно, JavaScript.
У меня здесь для вас две новости.
<ol>
<li>Первая плохая -- в JavaScript они на редкость убогие, хуже большинства существующих языков.</li>
<li>Вторая хорошая -- зато нам будет проще их изучить.</li>
</ol>