Особенности регулярных выражений в Javascript
Регулярные выражения в javascript немного странные. Вроде - перловые, обычные, но с подводными камнями, на которые натыкаются даже опытные javascript-разработчики.
Эта статья ставит целью перечислить неожиданные фишки и особенности RegExp в краткой и понятной форме.
Общую информацию о регулярных выражениях в javascript вы можете найти в статье Регулярные выражения.
Для поиска в многострочном режиме почти все модификации перловых регэкспов используют специальный multiline-флаг.
И javascript здесь не исключение.
Попробуем же сделать поиск и замену многострочного вхождения. Скажем, будем заменять [u] ... [/u] на тэг подчеркивания: <u>:
function bbtagit(text) {
text = text.replace(/\[u\](.*?)\[\/u\]/gim, '<u>$1</u>')
return text
}
var line = "[u]мой\n текст[/u]"
alert( bbtagit(line) )
Попробуйте запустить. Заменяет? Как бы не так!
Дело в том, что в javascript мультилайн режим (флаг m) влияет только на символы ^ и $, которые начинают матчиться с началом и концом строки, а не всего текста.
Точка по-прежнему - любой символ, кроме новой строки. В javascript нет флага, который устанавливает мультилайн-режим для точки. Для того, чтобы заматчить совсем что угодно - используйте [\s\S].
Работающий вариант:
function bbtagit(text) {
text = text.replace(/\[u\]([\s\S]*)\[\/u\]/gim, '<u>$1</u>')
return text
}
var line = "[u]мой\n текст[/u]"
alert( bbtagit(line) )
Это не совсем особенность, скорее фича, но все же достойная отдельного абзаца.
Все регулярные выражения в javascript - жадные. То есть, выражение старается отхватить как можно больший кусок строки.
Например, мы хотим заменить все открывающие тэги <a>. На что и почему - не так важно.
text = '1 <A href="#">...</A> 2'
text = text.replace(/<A(.*)>/, 'TEST')
alert(text)
При запуске вы увидите, что заменяется не открывающий тэг, а вся ссылка, выражение матчит ее от начала и до конца.
Это происходит из-за того, что точка-звездочка в "жадном" режиме пытается захватить как можно больше, в нашем случае - это как раз до последнего >.
Последний символ > точка-звездочка не захватывает, т.к. иначе не будет совпадения.
Как вариант решения используют квадратные скобки: [^>]:
text = '1 <A href="#">...</A> 2'
text = text.replace(/<A([^>]*)>/, 'TEST')
alert(text)
Это работает. Но самым удобным вариантом является переключение точки-звездочки в нежадный режим. Это осуществляется простым добавлением знака "?" после звездочки.
В нежадном режиме точка-звездочка пустит поиск дальше сразу, как только нашла совпадение:
text = '1 <A href="#">...</A> 2'
text = text.replace(/<A(.*?)>/, 'TEST')
alert(text)
В некоторых языках программирования можно переключить жадность на уровне всего регулярного выражения, флагом.
В javascript это сделать нельзя.. Вот такая особенность. А вопросительный знак после звездочки рулит - честное слово.
Иногда нужно в самом паттерне поиска обратиться к предыдущей его части.
Например, при поиске BB-тагов, то есть строк вида [u]...[/u], [b]...[/b] и [s]...[/s]. Или при поиске атрибутов, которые могут быть в одинарных кавычках или двойных.
Обращение к предыдущей части паттерна в javascript осуществляется как \1, \2 и т.п., бэкслеш + номер скобочной группы:
text = ' [b]a [u]b[/u] c [/b] '
var reg = /\[([bus])\](.*?)\[\//*u*/\1/*/u*/\]/
text = text.replace(reg, '<$1>$2</$1>')
alert(text)
Обращение к скобочной группе в строке замены идет уже через доллар: $1. Не знаю, почему, наверное так удобнее..
P.S. Понятно, что при таком способе поиска bb-тагов придется пропустить текст через замену несколько раз - пока результат не перестанет отличаться от оригинала.
Эти две задачи решаются в javascript принципиально по-разному.
Начнем с "простого".
Для замены всех вхождений используется метод String#replace.
Он интересен тем, что допускает первый аргумент - регэксп или строку.
Если первый аргумент - строка, то будет осуществлен поиск подстроки, без преобразования в регулярное выражение.
Попробуйте:
alert("2 ++ 1".replace("+", "*"))
Каков результат? Как, заменился только один плюс, а не два? Да, вот так.
Чтобы заменить все вхождения, String#replace придется использовать в режиме регулярного выражения.
В режиме регулярного выражения плюс придется заэкранировать, но зато replace заменит все вхождения (при указании флага g):
alert("2 ++ 1".replace(/\+/g, "*"))
Вот такая особенность работы со строкой.
Очень полезной особенностью replace является возможность работать с функцией вместо строки замены. Такая функция получает первым аргументом - все совпадение, а последующими аргументами - скобочные группы.
Следующий пример произведет операции вычитания:
var str = "count 36 - 26, 18 - 9"
str = str.replace(/(\d+) - (\d+)/g, function(a,b,c) { return b-c })
alert(str)
В javascript нет одного универсального метода для поиска всех совпадений.
Для поиска без запоминания скобочных групп - можно использовать String#match:
var str = "count 36-26, 18-9"
var re = /(\d+)-(\d+)/g
result = str.match(re)
for(var i=0; i<result.length; i++) alert(result[i])
Как видите, оно исправно ищет все совпадения (флаг 'g' у регулярного выражения обязателен), но при этом не запоминает скобочные группы. Эдакий "облегченный вариант".
В сколько-нибудь сложных задачах важны не только совпадения, но и скобочные группы. Чтобы их найти, предлагается использовать многократный вызов RegExp#exec.
Для этого регулярное выражение должно использовать флаг 'g'. Тогда результат поиска, запомненный в свойстве lastIndex объекта RegExp используется как точка отсчета для следующего поиска:
var str = "count 36-26, 18-9"
var re = /(\d+)-(\d+)/g
var res
while ( (res = re.exec(str)) != null) {
alert("Найдено " + res[0] + ": ("+ res[1]+") и ("+res[2]+")")
alert("Дальше ищу с позиции "+re.lastIndex)
}
Проверка while( (res = re.exec(str)) != null) нужна т.к. значение res = 0 является хорошим и означает, что вхождение найдено в самом начале строки (поиск успешен). Поэтому необходимо сравнивать именно с null.
Ну и напоследок - еще одна совсем оригинальная особенность регулярных выражений.
Вот - одна интересная функция.
Запустите ее один раз, запомните результат - и запустите еще раз.
function rere() {
var re1 = /0/, re2 = new RegExp('0')
alert([re1.foo, re2.foo])
re1.foo = 1
re2.foo = 1
}
rere()
В зависимости от браузера, результат первого запуска может отличаться от второго. На текущий момент, это так для Firefox, Opera. При этом в Internet Explorer все нормально.
С виду функция создает две локальные переменные и не зависит от каких-то внешних факторов.
Почему же разный результат?
Ответ кроется в стандарте ECMAScript, пункт 7.8.5:
Цитата...
A regular expression literal is an input element that is converted to a RegExp object (section 15.10)
when it is scanned. The object is created before evaluation of the containing program or function begins.
Evaluation of the literal produces a reference to that object; it does not create a new object.
То есть, простыми словами, литеральный регэксп не создается каждый раз при вызове var r = /regexp/.
Вместо этого браузер возвращает уже существующий объект, со всеми свойствами, оставшимися от предыдущего запуска.
В отличие от этого, new RegExp всегда создает новый объект, поэтому и ведет себя в примере по-другому.
Есть еще особенности?
Напишите в комментарии, и я добавлю их в статью.
|
Наверное, стоит упомянуть о lastIndex, из-за которого иногда получаются вот такие, не совсем очевидные, ситуации:
var toLowerCamelCase = function () { var expr = /-([a-z])/g; return function(prop) { return expr.test(prop) ? prop.replace(expr, function () { return arguments[1].toUpperCase(); }) : prop; }; }(); alert([ toLowerCamelCase('background-image'), toLowerCamelCase('background-image'), toLowerCamelCase('background-image'), toLowerCamelCase('background-image') ].join("\n"));Эффект можно наблюдать в IE и Chrome 4
Приведенный Вами в примере
аналогичен
со всеми вытекающими последствиями - будут обновляться свойства регэкспа (.lastIndex в том числе).
Вы используете замыкание и объявляете регэксп в локальной области видимости. Вы же для чего-то "спрятали" переменную
-- для многократного использования одного выражения
-- или чтобы не засорять общее пространство имен
-- или все вместе?
МСИЕ как раз ведет себя логично - многократное использование переменной в замыкании. А вот ФФ таки не логичен - с какой стати он реинициализирует переменную при каждом вызове функции?
регекспы яваскрипта позаимствовали синтаксис из перла, но реализовали только часть функционала. на сколько я знаю, своего они ничего не придумывали, и в таком случае скорее стоит говорить не об особенностях js-регекспов, а об их отличии от перловых.
а именно:
поддерживаются только три модификатора i,g,m
не поддерживаются модификаторы внутри регекспа (?i) (?-i)
не поддерживаются \A и \Z (начало и конец строки)
нет possessive quantifiers ?+ *+ ++
нет атомарной группировки (?>...)
нет lookbehind assertions (?<=...) (?...)
нет conditionals (?(condition)yes-pattern|no-pattern)
нет комментариев (?#comment)
нет рекурсивной конструкции (?R)
p.s. если не читали, рекомендую почитать книгу Фридла по регекспам - очень способствует пониманию регекспов.
http://www.amazon.com/Mastering-Regular-Expressions-Jeffrey-Friedl/dp/05...
В этой статье описаны не отсутствующие фичи, по сравнению с перлом, а особенности.
То есть фичи и способы решения задач, которые ЕСТЬ, но работают ИНАЧЕ, чем мы привыкли видеть.
Спасибо за рекомендации по поводу комментариев... Но дополнений и исправлений нет, есть только слова благодарности за такую информацию...
.*? - действительно пробивная штука
вопрос почти в тему:
так как выражение replace в js работает только 1 раз -
как посчитать на странице количество bb тегов, в частности, ([url=ссылка]описание[/url] ), и если можно, как заменить такое выражение на желаемый мной текст?
Да сложное дело java, а вы платную помощь не оказываете?
помощь оказываем всякую, если надо что то конкретное написать обращайся
При конкретной формулировке вопроса и адекватности спрашивающего.
А что мой вопрос от 19 января, 2010 - 19:33 был не адекватен?
Прочитайте статью еще раз, там появился раздел о поиске и замене. Надеюсь, он поможет в решении вашей задачи.
Подскажите, пожалуйста.
Делаю плагин autocomplete к jQuery. Не получается сделать проверку на вхождение и замену в строках с символами кириллицы.
Делаю так:
var re = new RegExp('\\b'+text,'gi'); if(re.search(str)) str.replace(re,'<strong>$1</strong>');где str — текст текущего элемент списка (в цикле обхожу все элементы),
text — это текст, содержащийся в поле ввода, т.е. подстрока.
С латиницей получается, с кириллицей — нет.
Ой, простите, перепутал немного.
Нашёл примерчик регулярного выражения из готового плагина. Сработало! Объясните его, пожалуйста.
var re = new RegExp("(?![^&;]+;)(?!<[^<>]*)("+text+")(?![^<>]*>)(?![^&;]+;)",'gi');Интересует само содержимое выражения.
дайте ссылку на данный плагин пожалуйста
\b ищет границу слов написанных только латиницей.
Подскажите как лучше решить задачу, если есть большой текст и надо произвести замену только одного вхождения подстроки, но при этом не первого (как будет без указания global), а последнего.
То есть, например есть строка "1xy2xy3xy4xy" и надо убрать последнее вхождение "xy", то есть получить "1xy2xy3xy4"
Подскажите, как это реализовать средствами javascript ?
Воспользуемся жадностью
var result = '1xy2xy3xy4xy'.replace(/([\s\S]*)xy/m, function(a,b){return b;})
можно вот эти слова перевести в java выражения?
привет
пока
как дела
пошол ты
здарова
что делаеш
Буду очень блогодарен
Для удаления тега, например span можно написать так:
x=x.replace(/<(\/?)span([^>]*)>/ig,'')
от тега останется только то, что в нём, например от:
Содержимое
останется только "Содержимое"
А как убрать всё целиком и тег и его содержимое?
то что искал 3 дня по бб кодам, только не давно начал работать с регами из-за это-го плохо понимал, статья супер, примеры идеальны
вы юзаете /i, /gi и т.п., но ничего о них не пишете
вы не находите что неплохо бы добавить их описание ?
или для них здесь есть отдельная страничка ? - тогда киньте ссылку
Найти все со скобками
Там у вас пример странный, вот например в Вашем же случае все скобочки найти можно просто: "count 36-26, 18-9".match( /(\d+)-(\d+)/g ) и вернется массив из найденых вхождений, в данном случае 2 элемента, почему Вы привели именно такой пример?
"...Чтобы их найти, предлагается использовать многократный вызов RegExp#exec."
Мне кажется тут говорится, что поиск всех "скобок" должен происходить в цикле, т.к. ".match" вернет только первое вхождение.
Объясните пожалуйста, как в маску передать переменную - строку.
Заранее спасибо за ответ.
А как вам это (почти как String.prototype.replace)
RegExp.prototype.reset = function() { this.lastIndex = 0; return this; }; RegExp.prototype.forEach = function(str, fun, scope) { this.reset(); for(var res = null; (res = this.exec(str)) != null;) if(fun.apply(scope, res) === false) break; return this; };>> Это не совсем особенность, скорее фича, но все же достойная отдельного абзаца.
Нежадный "?" - это не фича js-а. Эту возможность давно поддерживают и php, и perl наверное, и микрософтовская реализация регулярок.
Как можно заполнить массив найдеными числами. Делаю
var re = /\d+/g var res while ( (res = re.exec(str)) != null) { ar[i] = res[i]; alert(res[i]); i++; }Не получается, заполняется только первый элемент?
var str = "sdfsd23321 3asdfds 23-023 sdaf12d2d201m21"; //например такая строка var arr = []; arr = str.match(/\d+/g); // возьмём match и он сразу сам в массив всё запишет for (var i=0; i < arr.length; i++){ // и выводим для проверки alert(arr[i]); };var re = /\d+/g var res var ar = new Array(); while ( (res = re.exec(str)) != null) { ar.push(res[i]); alert(res[i]); //добавляемый элемент массива i++; } alert(ar); //показать массив с найденными числамиподправьте секцию "статические свойства" у меня кнопка "запустить" на однострочном коде выдаёт
rere() is not defined
firefox 6.0.2
Тогда уж заодно и поправить, что результат одинаковый и в FF7 и Opera11.51.
Пофиксили поведение.
Доброго времени суток.
Задача: заменить все двойные кавычки " в строке на пробелы, если они не являются частью выражения ";". Пытался так
var pattern = /"(?!;")/g;
var new_str = str.replace(pattern," ");
но данный вариант,удаляя кавычки, от ";" оставляет ";
Буду благодарен за помощь.
Все верно, ваше выражение /"(?!;")/ находит кавычку, если за ней не следует точка с запятой. В выражении ";" под этот поиск как раз попадает вторая кавычка, ведь за ней нет точки с запятой.
Привет.
Прошу помощи - поиск и замена накладываются при повторном применении....
param = "p2 and s3"
param.replace(/p/g,"models.price=").replace(/s/g,"models.season_ID=")
приводит к
modelmodels.season_ID=.price=2
и как быть, если надо
model.price=2 and models.season_ID=3
?
Можно предварительно заменить искомые подстроки на заведомо уникальные, а потом заменять уже эти уникальные на то, что надо. Например так:
В качестве замедомо уникальных строк (SOME_UNIQ_TEXT_1) можно использовать например COM GUID или MD5 хеш от текущего времени и еще чего-нибудь (стопроцентной гарантии уникальности конечно нет, но для данной задачи подойдет).
думай что пишешь! md5 блин....
уникальность можно получить банальным счетчиком
var string = 'some strings', z=0; string.replace(/s/g, function() {return z++;});и чтоб не было пересекающихся замен - делать замены одним выражением:
var param = "p2 and s3"; param.replace(/([sp])/g, function(i, r) { if (r == 's') return 'models.season_ID='; if (r == 'p') return 'models.price=' });По поводу статических свойств регулярных выражений:
В 5-ом ECMAScript эту "фичу" уже убрали - теперь при каждом выполнении литерала регулярного выражения создаётся новый объект RegExp.
Привет всем. Очень нужна помощь. Есть строка вида text.text.text - curr
в результате выполнения
получается
Беда в том, что в тексте может быть конструкция вида '/.' которая должна вопсприниматься как текст (text) и вести себя так же. Т.е. регулярка не должна срабатывать по точке если перед точкой есть '/'. Помогите пожалуйста. Вообще не могу придумать как это оформить
Доброго времени суток, уважаемые специалисты.
Впервые столкнулся с регэкспами... Пока обходился без него, но жизнь заставила).
Не могу сформировать шиблон для задачи:
Нужно проверить, подходит ли строка под шаблон ЧИСЛО.ЧИСЛО
На данный момент додумался вот до этого:
function CheckChain(Chain){ var validRegExp = /^(([0-9\-])+\.)+([0-9]{1,2})+$/; var rslt = Chain.match(validRegExp); return rslt;}Работает, но есть 2 НО:
1) ошибка при выполнении, если Chain=Null (не оч. критично)
2) под шаблон попадаюn также значения типа ЧИСЛО.ЧИСЛО.ЧИСЛО.ЧИСЛО (1.2.17.56), что неправильно.
Подскажите, пожалуйста, где я не прав, и как должен выглядеть шаблон. Буду благодарен за пояснения.
Заранее благодарю всех откликнувшихся.
"число.число.число" подходит, поскольку так разрешено:
сначала разрешается сочетание "число.число.": (([0-9\-])+\.)+ , т.е. один или несколько символов, являющихся числом или "-", за которыми обязательно стоит точка, и это сочетание повторяется один или несколько раз (т.е. 93.45.28234284. - это норм.),
а следом требуете еще число: ([0-9]{1,2})+, т.е. одна или две цифры, повторяющиеся один или несколько раз,
в итоге "число.число.число" вполне подходит, точно также, как что-нибудь вроде этого: --9273---38-328-----.-----.344598452378274339
если нужно, чтоб строка соответствовала шаблону "число.число" и ничего кроме этой последовательности не содержала, то: /^[0-9]+\.[0-9]+$/
если в качестве первого допускается любое, а в качестве второго только однозначное или двузначное число, тогда так: /^[0-9]+\.[0-9]{1,2}$/
если при этом нужно запомнить только цифры, то: /^([0-9]+)\.([0-9]{1,2})$/
А как сделать так, чтобы я нажал на текст и открылось такое окно, но с моим текстом?
Если я не ошибаюсь, то в разделе "Точка и перенос строки" допущена ошибка во второй регулярке.
/\[u\](.*?)\[\/u\]/gim - здесь установлен нежадный поиск
/\[u\]([\s\S]*)\[\/u\]/gim - а вот тут забыли этот нежадный поиск установить и в результате получится инфаркт для чайника))) Правильно будет /\[u\]([\s\S]*?)\[\/u\]/gim
Здравствуйте.
Столкнулся с проблемой, не работает правильно регулярное выражение.
Есть строка page/:cat/:name/sort/:subcat/my/:id
Мне нужно из неё получить код между ":" и "/"
Пробую так:
var str = "page/:cat/:name/sort/:subcat/my/:id"; var myA; if((myA = /:(\w+)+/gi.exec(str)) != null) { console.log(myA); }Но, этот код возвращает только первое совпадение "cat", а нужно что бы вернуло все: cat, name, subcat, id
Решил проблему, нужно было циклом пройтись.
Вот возможно кому-то понадобится:
var str = "page/:cat/:name/sort/:subcat/my/:id"; var myA; var re = /:(\w+)/gi while((myA = re.exec(key)) != null) { console.log(myA[1]); }Точнее:
var str = "page/:cat/:name/sort/:subcat/my/:id"; var myA; var re = /:(\w+)/gi while((myA = re.exec(str)) != null) { console.log(myA[1]); }Опечатался
Уважаемая администрация, код не запускается.
function rere() {
var re1 = /0/, re2 = new RegExp('0')
alert([re1.foo, re2.foo])
re1.foo = 1
re2.foo = 1
}
rere()
К тому же, в FF он работает нормально.
"выражение матчит ее от начала и до конца" - а без жаргонизмов-кАлек можно писать, более литературно.
> str = str.replace(/(\d+) - (\d+)/g, function(a,b,c) { return b-c })
А почему здесь в регулярном выражении - 2 аргумента - (\d+) и (\d+),
а в функции 3 аргумента - a, b, c?
Прошу прощения. Ответ нашёл в описании:
> Первый параметр всегда содержит полную совпавшую подстроку