Особенности регулярных выражений в 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, из-за которого иногда получаются вот такие, не совсем очевидные, ситуации:
Эффект можно наблюдать в 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. Не получается сделать проверку на вхождение и замену в строках с символами кириллицы.
Делаю так:
где str — текст текущего элемент списка (в цикле обхожу все элементы),
text — это текст, содержащийся в поле ввода, т.е. подстрока.
С латиницей получается, с кириллицей — нет.
Ой, простите, перепутал немного.
Нашёл примерчик регулярного выражения из готового плагина. Сработало! Объясните его, пожалуйста.
Интересует само содержимое выражения.
дайте ссылку на данный плагин пожалуйста
\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)
>> Это не совсем особенность, скорее фича, но все же достойная отдельного абзаца.
Нежадный "?" - это не фича js-а. Эту возможность давно поддерживают и php, и perl наверное, и микрософтовская реализация регулярок.
Как можно заполнить массив найдеными числами. Делаю
Не получается, заполняется только первый элемент?
подправьте секцию "статические свойства" у меня кнопка "запустить" на однострочном коде выдаёт
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 блин....
уникальность можно получить банальным счетчиком
и чтоб не было пересекающихся замен - делать замены одним выражением:
По поводу статических свойств регулярных выражений:
В 5-ом ECMAScript эту "фичу" уже убрали - теперь при каждом выполнении литерала регулярного выражения создаётся новый объект RegExp.
Привет всем. Очень нужна помощь. Есть строка вида text.text.text - curr
в результате выполнения
получается
Беда в том, что в тексте может быть конструкция вида '/.' которая должна вопсприниматься как текст (text) и вести себя так же. Т.е. регулярка не должна срабатывать по точке если перед точкой есть '/'. Помогите пожалуйста. Вообще не могу придумать как это оформить
Доброго времени суток, уважаемые специалисты.
Впервые столкнулся с регэкспами... Пока обходился без него, но жизнь заставила).
Не могу сформировать шиблон для задачи:
Нужно проверить, подходит ли строка под шаблон ЧИСЛО.ЧИСЛО
На данный момент додумался вот до этого:
Работает, но есть 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
Мне нужно из неё получить код между ":" и "/"
Пробую так:
Но, этот код возвращает только первое совпадение "cat", а нужно что бы вернуло все: cat, name, subcat, id
Решил проблему, нужно было циклом пройтись.
Вот возможно кому-то понадобится:
Точнее:
Опечатался
Уважаемая администрация, код не запускается.
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?
Прошу прощения. Ответ нашёл в описании:
> Первый параметр всегда содержит полную совпавшую подстроку