Регулярные выражения и специальные символы

Регулярные выражения в javascript имеют особую краткую форму и стандартный PCRE-синтаксис.

Работают они через специальный объект RegExp.

Кроме того, у строк есть свои методы search,match,replace, но чтобы их понять - разберем-таки сначала RegExp.

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

/pattern/флаги
new RegExp("pattern"[, флаги])

pattern - регулярное выражение для поиска (о замене - позже), а флаги - строка из любой комбинации символов g(глобальный поиск), i(регистр неважен) и m(многострочный поиск).

Первый способ используется часто, второй - иногда. Например, два таких вызова эквивалентны:

var reg = /ab+c/i
var reg = new RegExp("ab+c", "i")

При втором вызове - т.к регулярное выражение в кавычках, то нужно дублировать \

// эквивалентны
re = new RegExp("\\w+")
re = /\w+/

При поиске можно использовать большинство возможностей современного PCRE-синтаксиса.

Свернуть/Развернуть таблицу

СимволЗначение
\Для обычных символов - делает их специальными. Например, выражение /s/ ищет просто символ 's'. А если поставить \ перед s, то /\s/ уже обозначает пробельный символ.И наоборот, если символ специальный, например *, то \ сделает его просто обычным символом "звездочка". Например, /a*/ ищет 0 или больше подряд идущих символов 'a'. Чтобы найти а со звездочкой 'a*' - поставим \ перед спец. символом: /a\*/.
^Обозначает начало входных данных. Если установлен флаг многострочного поиска ("m"), то также сработает при начале новой строки.Например, /^A/ не найдет 'A' в "an A", но найдет первое 'A' в "An A."
$Обозначает конец входных данных. Если установлен флаг многострочного поиска, то также сработает в конце строки.Например, /t$/ не найдет 't' в "eater", но найдет - в "eat".
*Обозначает повторение 0 или более раз. Например, /bo*/ найдет 'boooo' в "A ghost booooed" и 'b' в "A bird warbled", но ничего не найдет в "A goat grunted".
+Обозначает повторение 1 или более раз. Эквивалентно {1,}. Например, /a+/ найдет 'a' в "candy" и все 'a' в "caaaaaaandy".
?Обозначает, что элемент может как присутствовать, так и отсутствовать. Например, /e?le?/ найдет 'el' в "angel" и 'le' в "angle."Если используется сразу после одного из квантификаторов *, +, ?, или {}, то задает "нежадный" поиск (повторение минимально возможное количество раз, до ближайшего следующего элемента паттерна), в противоположность "жадному" режиму по умолчанию, при котором количество повторений максимально, даже если следующий элемент паттерна тоже подходит.Кроме того, ? используется в предпросмотре, который описан в таблице под (?=), (?!), и (?: ).
.(Десятичная точка) обозначает любой символ, кроме перевода строки: \n \r \u2028 or \u2029. (можно использовать [\s\S] для поиска любого символа, включая переводы строк). Например, /.n/ найдет 'an' и 'on' в "nay, an apple is on the tree", но не 'nay'.
(x)Находит x и запоминает. Это называется "запоминающие скобки". Например, /(foo)/ найдет и запомнит 'foo' в "foo bar." Найденная подстрока хранится в массиве-результате поиска или в предопределенных свойствах объекта RegExp: $1, ..., $9.Кроме того, скобки объединяют то, что в них находится, в единый элемент паттерна. Например, (abc)* - повторение abc 0 и более раз.
(?:x)Находит x, но не запоминает найденное. Это называется "незапоминающие скобки". Найденная подстрока не сохраняется в массиве результатов и свойствах RegExp.Как и все скобки, объединяют находящееся в них в единый подпаттерн.
x(?=y)Находит x, только если за x следует y. Например, /Jack(?=Sprat)/ найдет 'Jack', только если за ним следует 'Sprat'. /Jack(?=Sprat|Frost)/ найдет 'Jack', только если за ним следует 'Sprat' или 'Frost'. Однако, ни 'Sprat' nor 'Frost' не войдут в результат поиска.
x(?!y)Находит x, только если за x не следует y. Например, /\d+(?!\.)/ найдет число, только если за ним не следует десятичная точка. /\d+(?!\.)/.exec("3.141") найдет 141, но не 3.141.
x|yНаходит x или y. Например, /green|red/ найдет 'green' в "green apple" и 'red' в "red apple."
{n}Где n - положительное целое число. Находит ровно n повторений предшествующего элемента. Например, /a{2}/ не найдет 'a' в "candy," но найдет оба a в "caandy," и первые два a в "caaandy."
{n,}Где n - положительное целое число. Находит n и более повторений элемента. Например, /a{2,} не найдет 'a' в "candy", но найдет все 'a' в "caandy" и в "caaaaaaandy."
{n,m}Где n и m - положительные целые числа. Находят от n до m повторений элемента.
[xyz]Набор символов. Находит любой из перечисленных символов. Вы можете указать промежуток, используя тире. Например, [abcd] - то же самое, что [a-d]. Найдет 'b' в "brisket", а также 'a' и 'c' в "ache".
[^xyz]Любой символ, кроме указанных в наборе. Вы также можете указать промежуток. Например, [^abc] - то же самое, что [^a-c]. Найдет 'r' в "brisket" и 'h' в "chop."
[\b]Находит символ backspace. (Не путать с \b.)
\bНаходит границу слов (латинских), например пробел. (Не путать с [\b]). Например, /\bn\w/ найдет 'no' в "noonday"; /\wy\b/ найдет 'ly' в "possibly yesterday."
\BОбозначает не границу слов. Например, /\w\Bn/ найдет 'on' в "noonday", а /y\B\w/ найдет 'ye' в "possibly yesterday."
\cXГде X - буква от A до Z. Обозначает контрольный символ в строке. Например, /\cM/ обозначает символ Ctrl-M.
\dнаходит цифру из любого алфавита (у нас же юникод). Испльзуйте [0-9], чтобы найти только обычные цифры. Например, /\d/ или /[0-9]/ найдет '2' в "B2 is the suite number."
\DНайдет нецифровой символ (все алфавиты). [^0-9] - эквивалент для обычных цифр. Например, /\D/ или /[^0-9]/ найдет 'B' в "B2 is the suite number."
\f,\r,\nСоответствующие спецсимволы form-feed, line-feed, перевод строки.
\sНайдет любой пробельный символ, включая пробел, табуляцию, переводы строки и другие юникодные пробельные символы. Например, /\s\w*/ найдет ' bar' в "foo bar."
\SНайдет любой символ, кроме пробельного. Например, /\S\w*/ найдет 'foo' в "foo bar."
\tСимвол табуляции.
\vСимвол вертикальной табуляции.
\wНайдет любой словесный (латинский алфавит) символ, включая буквы, цифры и знак подчеркивания. Эквивалентно [A-Za-z0-9_]. Например, /\w/ найдет 'a' в "apple," '5' в "$5.28," и '3' в "3D."
\WНайдет любой не-(лат.)словесный символ. Эквивалентно [^A-Za-z0-9_]. Например, /\W/ и /[^$A-Za-z0-9_]/ одинаково найдут '%' в "50%."
\nгде n - целое число. Обратная ссылка на n-ю запомненную скобками подстроку. Например, /apple(,)\sorange\1/ найдет 'apple, orange,' в "apple, orange, cherry, peach.". За таблицей есть более полный пример.
\0Найдет символ NUL. Не добавляйте в конец другие цифры.
\xhhНайдет символ с кодом hh (2 шестнадцатиричных цифры)
\uhhhhНайдет символ с кодом hhhh (4 шестнадцатиричных цифры).

Чтобы просто проверить, подходит ли строка под регулярное выражение, используется метод test:

if ( /\s/.test("строка") ) {
...В строке есть пробелы!...
}

Метод exec возвращает массив и ставит свойства регулярного выражения.
Если совпадений нет, то возвращается null.

Например,

// Найти одну d, за которой следует 1 или более b, за которыми одна d
// Запомнить найденные b и следующую за ними d
// Регистронезависимый поиск
var myRe = /d(b+)(d)/ig;
var myArray = myRe.exec("cdbBdbsbz");

В результате выполнения скрипта будут такие результаты:

ОбъектСвойство/ИндексОписанияПример
myArray Содержимое myArray.["dbBd", "bB", "d"]
indexИндекс совпадения (от 0)1
inputИсходная строка.cdbBdbsbz
[0]Последние совпавшие символыdbBd
[1], ...[n]Совпадения во вложенных скобках, если есть. Число вложенных скобок не ограничено.[1] = bB
[2] = d
myRelastIndexИндекс, с которого начинать следующий поиск.5
ignoreCaseПоказывает, что был включен регистронезависимый поиск, флаг "i".true
globalПоказывает, что был включен флаг "g" поиска всех совпадений.true
multilineПоказывает, был ли включен флаг многострочного поиска "m".false
sourceТекст паттерна.d(b+)(d)

Если в регулярном выражении включен флаг "g", Вы можете вызывать метод exec много раз для поиска последовательных совпадений в той же строке. Когда Вы это делаете, поиск начинается на подстроке str, с индекса lastIndex. Например, вот такой скрипт:

var myRe = /ab*/g;
var str = "abbcdefabh";
while ((myArray = myRe.exec(str)) != null) {
var msg = "Found " + myArray[0] + ". ";
msg += "Next match starts at " + myRe.lastIndex;
print(msg);
}

Этот скрипт выведет следующий текст:

Found abb. Next match starts at 3
Found ab. Next match starts at 9

В следующем примере функция выполняет поиск по input. Затем делается цикл по массиву, чтобы посмотреть, есть ли другие имена.

Предполагается, что все зарегистрированные имена находятся в массиве А:

var A = ["Frank", "Emily", "Jane", "Harry", "Nick", "Beth", "Rick",
"Terrence", "Carol", "Ann", "Terry", "Frank", "Alice", "Rick",
"Bill", "Tom", "Fiona", "Jane", "William", "Joan", "Beth"];

function lookup(input)
{
var firstName = /\w+/i.exec(input);
if (!firstName)
{
print(input + " isn't a name!");
return;
}

var count = 0;
for (var i = 0; i < A.length; i++)
{
if (firstName[0].toLowerCase() == A[i].toLowerCase())
count++;
}
var midstring = (count == 1) ? " other has " : " others have ";
print("Thanks, " + count + midstring + "the same name!")
}

Следующие методы работают с регулярными выражениями из строк.

Все методы, кроме replace, можно вызывать как с объектами типа regexp в аргументах, так и со строками, которые автоматом преобразуются в объекты RegExp.

Так что вызовы эквивалентны:

var i = str.search(/\s/) var i = str.search("\\s")

При использовании кавычек нужно дублировать \ и нет возможности указать флаги, поэтому иногда бывает удобна и полная форма

var i = str.search(new RegExp("\\s","g"))

Возвращает индекс регулярного выражения в строке, или -1.

Если Вы хотите знать, подходит ли строка под регулярное выражение, используйте метод search(аналогично RegExp-методы test). Чтобы получить больше информации, используйте более медленный метод match(аналогичный методу RegExp exec).

Этот пример выводит сообщение, в зависимости от того, подходит ли строка под регулярное выражение.

function testinput(re, str){
if (str.search(re) != -1)
midstring = " contains ";
else
midstring = " does not contain ";
document.write (str + midstring + re.source);
}

Если в regexp нет флага g, то возвращает тот же результат, что regexp.exec(string).

Если в regexp есть флаг g, то возвращает массив со всеми совпадениями.

Чтобы просто узнать, подходит ли строка под регулярное выражение regexp, используйте regexp.test(string).

Если Вы хотите получить первый результат - попробуйте regexp.exec(string).

В следующем примере match используется, чтобы найти "Chapter", за которой следует 1 или более цифр, а затем цифры, разделенные точкой. В регулярном выражении есть флаг i, так что регистр будет игнорироваться.

str = "For more information, see Chapter 3.4.5.1";
re = /chapter (\d+(\.\d)*)/i;
found = str.match(re);
alert(found);

Скрипт выдаст массив из совпадений:

  • Chapter 3.4.5.1 - полностью совпавшая строка
  • 3.4.5.1 - первая скобка
  • .1 - внутренняя скобка

Следующий пример демонстрирует использование флагов глобального и регистронезависимого поиска с match. Будут найдены все буквы от А до Е и от а до е, каждая - в отдельном элементе массива.

var str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
var regexp = /[A-E]/gi;
var matches = str.match(regexp);
document.write(matches);
// matches = ['A', 'B', 'C', 'D', 'E', 'a', 'b', 'c', 'd', 'e']

Метод replace может заменять вхождения регулярного выражения не только на строку, но и на результат выполнения функции. Его полный синтаксис - такой:

var newString = str.replace(regexp/substr, newSubStr/function)
regexp
Объект RegExp. Его вхождения будут заменены на значение, которое вернет параметр номер 2
substr
Строка, которая будет заменена на newSubStr.
newSubStr
Строка, которая заменяет подстроку из аргумента номер 1.
function
Функция, которая может быть вызвана для генерации новой подстроки (чтобы подставить ее вместо подстроки, полученной из аргумента 1).

Метод replace не меняет строку, на которой вызван, а просто возвращает новую, измененную строку.

Чтобы осуществить глобальную замену, включите в регулярное выражение флаг "g".

Если первый аргумент - строка, то она не преобразуется в регулярное выражение, так что, например,

var ab = "a b".replace("\\s","..") // = "a b"

Вызов replace оставил строку без изменения, т.к искал не регулярное выражение \s, а строку "\s".

В строке замены могут быть такие спецсимволы:

PatternInserts
$$Вставляет "$".
$&Вставляет найденную подстроку.
$`Вставляет часть строки, которая предшествует найденному вхождению.
$'Вставляет часть строки, которая идет после найденного вхождения.
$n or $nnГде n или nn - десятичные цифры, вставляет подстроку вхождения, запомненную n-й вложенной скобкой, если первый аргумент - объект RegExp.

Если Вы указываете вторым параметром функцию, то она выполняется при каждом совпадении.

В функции можно динамически генерировать и возвращать строку подстановки.

Первый параметр функции - найденная подстрока. Если первым аргументом replace является объект RegExp, то следующие n параметров содержат совпадения из вложенных скобок. Последние два параметра - позиция в строке, на которой произошло совпадение и сама строка.

Например, следующий вызов replace возвратит XXzzzz - XX , zzzz.

function replacer(str, p1, p2, offset, s)
{
return str + " - " + p1 + " , " + p2;
}
var newString = "XXzzzz".replace(/(X*)(z*)/, replacer)

Как видите, тут две скобки в регулярном выражении, и потому в функции два параметра p1, p2.
Если бы были три скобки, то в функцию пришлось бы добавить параметр p3.

Следующая функция заменяет слова типа borderTop на border-top:

function styleHyphenFormat(propertyName)
{
function upperToHyphenLower(match)
{
return '-' + match.toLowerCase();
}
return propertyName.replace(/[A-Z]/, upperToHyphenLower);
}

Для общего понимания регулярных выражений можно почитать Статью в wikipedia.

Более подробно они описаны в книге (англ.) Beginning Regular Expressions.