Javascript - MultiLine RegExp: lastIndex застрял на новых строках? - PullRequest
8 голосов
/ 19 декабря 2008

Некоторый контекст

С Javascript: полное руководство :

Если regexp является глобальным регулярным выражением, то exec() ведет себя немного более сложным образом. Он начинает поиск string с позиции символа, указанной в свойстве lastIndex regexp. Когда он находит совпадение, он устанавливает lastIndex в положение первого символа после совпадения.

Думаю, любой, кто регулярно работает с javascript RegExps, узнает этот отрывок. Однако я обнаружил странное поведение в этом методе.

Проблема

Рассмотрим следующий код:

>> rx = /^(.*)$/mg

>> tx = 'foo\n\nbar'

>> rx.exec(tx)
[foo,foo]
>> rx.lastIndex
3
>> rx.exec(tx)
[,]
>> rx.lastIndex
4
>> rx.exec(tx)
[,]
>> rx.lastIndex
4
>> rx.exec(tx)
[,]
>> rx.lastIndex
4

RegExp, похоже, застревает во второй строке и не увеличивает свойство lastIndex. Кажется, это противоречит Книге носорогов . Если я сам установлю его следующим образом, он продолжится и в конечном итоге вернет значение NULL, как и ожидалось, но, похоже, я не должен был это делать.

>> rx.lastIndex = 5
5
>> rx.exec(tx)
[bar,bar]
>> rx.lastIndex
8
>> rx.exec(tx)
null

Заключение

Очевидно, я могу увеличивать свойство lastIndex каждый раз, когда совпадение является пустой строкой. Однако, будучи любознательным типом, я хочу знать, почему он не увеличивается методом exec. Почему не так?

Примечания

Я наблюдал такое поведение в Chrome и Firefox. Кажется, это происходит только при наличии соседних строк новой строки.

[править]

Tomalak говорит ниже, что изменение шаблона на /^(.+)$/gm приведет к тому, что выражение не застрянет, но пустая строка игнорируется. Можно ли это изменить, чтобы соответствовать строке? Спасибо за ответ Томалак !

[править]

Использование следующего шаблона и использование группы 1 работает для всех строк, которые я могу придумать. Еще раз спасибо Томалак .

/^(.*)((\r\n|\r|\n)|$)/gm

[править]

Предыдущий шаблон возвращает пустую строку. Однако, если вам не нужны пустые строки, Tomalak дает следующее решение, которое я считаю более чистым.

/^(.*)[\r\n]*/gm

[править]

Оба предыдущих решения застряли на завершающих символах новой строки, поэтому вам придется либо убрать их, либо увеличить lastIndex вручную.

[править]

Я нашел отличную статью, подробно описывающую проблемы кросс-браузеров с lastIndex в Flagrant Badassery . Помимо потрясающего названия блога, статья дала мне более глубокое понимание проблемы, а также хорошее кросс-браузерное решение. Решение заключается в следующем:

var rx = /^/gm,
    tx = 'A\nB\nC',
    m;

while(m = rx.exec(tx)){
    if(!m[0].length && rx.lastIndex > m.index){
        --rx.lastIndex;
    }

    foo();

    if(!m[0].length){
        ++rx.lastIndex;
    }
}

Ответы [ 2 ]

7 голосов
/ 19 декабря 2008

Проблема в том, что точка в

^(.*)$

не соответствует символам новой строки, но с помощью переключателя "m" вы делаете "^" и "$" привязкой к символам новой строки. Это означает, что «ничто» между "\n" и "\n" может быть успешно сопоставлено с "(.*)".

Поскольку это совпадение имеет нулевую ширину, свойство lastIndex не может быть расширено. Попробуйте:

^(.+)$

РЕДАКТИРОВАТЬ: Чтобы соответствовать пустым строкам, сделайте это:

^(.*)\n?     // remove all \r characters beforehand

или

^(.*)(?:\r\n|\n\r|\n|\r)?  // all possible CR/LF combinations, but *once* at most

... и просто перейдите к группе матчей 1.

2 голосов
/ 25 декабря 2008

Проблема с lastIndex заключается в том, что реализация JavaScript, соответствующая стандарту буквы, устанавливает для него смещение следующего символа после совпадения. Таким образом, для регулярных выражений, таких как ваше, которые допускают совпадения нулевой длины, exec () застревает в бесконечном цикле, когда найдено совпадение нулевой длины. Следующая попытка сопоставления начнется в той же позиции, где найдено такое же совпадение нулевой длины.

Обычно движки регулярных выражений решают эту проблему, пропуская один символ, когда найдено совпадение нулевой длины. Кстати, Internet Explorer делает то же самое.

Я подробно писал об этом в прошлом: Остерегайтесь совпадений нулевой длины

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...