Javascript: отрицательный взгляд за эквивалентом? - PullRequest
124 голосов
/ 13 марта 2009

Есть ли способ достичь эквивалента негативного взгляда за в регулярных выражениях javascript? Мне нужно сопоставить строку, которая не начинается с определенного набора символов.

Кажется, я не могу найти регулярное выражение, которое делает это без сбоев, если соответствующая часть найдена в начале строки. Отрицательные взгляды, кажется, единственный ответ, но у javascript его нет.

EDIT: Это регулярное выражение, с которым я хотел бы работать, но это не так:

(?<!([abcdefg]))m

Таким образом, это будет соответствовать 'm' в 'jim' или 'm', но не 'jam'

Ответы [ 12 ]

78 голосов
/ 05 июля 2012

Поскольку Javascript поддерживает негативный взгляд , один из способов сделать это:

  1. обратный ввод строки

  2. совпадение с обратным регулярным выражением

  3. перевернуть и переформатировать совпадения


const reverse = s => s.split('').reverse().join('');

const test = (stringToTests, reversedRegexp) => stringToTests
  .map(reverse)
  .forEach((s,i) => {
    const match = reversedRegexp.test(s);
    console.log(stringToTests[i], match, 'token:', match ? reverse(reversedRegexp.exec(s)[0]) : 'Ø');
  });

Пример 1:

После вопроса Эндрю-Энсли:

test(['jim', 'm', 'jam'], /m(?!([abcdefg]))/)

Выходы:

jim true token: m
m true token: m
jam false token: Ø

Пример 2:

После комментария @neaumusic (соответствует max-height, но не line-height, токен height):

test(['max-height', 'line-height'], /thgieh(?!(-enil))/)

Выходы:

max-height true token: height
line-height false token: Ø
52 голосов
/ 30 ноября 2014

Предположим, вы хотите найти все int, которым не предшествует unsigned:

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

(?<!unsigned )int

Без поддержки негативного просмотра:

((?!unsigned ).{9}|^.{0,8})int

В основном идея состоит в том, чтобы захватить n предшествующих символов и исключить совпадение с отрицательным прогнозом, но также сопоставить случаи, когда нет предшествующих n символов. (где n - длина оглядки назад).

Итак, регулярное выражение в вопросе:

(?<!([abcdefg]))m

будет переводиться на:

((?!([abcdefg])).|^)m

Возможно, вам придется поиграть с группами захвата, чтобы найти точное место интересующей вас строки, или вы хотите заменить определенную часть чем-то другим.

41 голосов
/ 13 марта 2009

Стратегия Mijoja работает для вашего конкретного случая, но не в целом:

js>newString = "Fall ball bill balll llama".replace(/(ba)?ll/g,
   function($0,$1){ return $1?$0:"[match]";});
Fa[match] ball bi[match] balll [match]ama

Вот пример, где цель состоит в том, чтобы соответствовать двойному l, но не если ему предшествует "ba". Обратите внимание на слово «balll» - истинный взгляд сзади должен был подавить первые 2 л, но соответствовать 2-й паре. Но сопоставляя первые 2 л и затем игнорируя это совпадение как ложное срабатывание, механизм регулярных выражений исходит из end этого совпадения и игнорирует все символы в ложном положительном результате.

34 голосов
/ 20 мая 2018

Утверждения за кадром получил принято в спецификации ECMAScript в 2018 году. Это было реализовано в V8 и , поставляемых без Флаги с Google Chrome v62 и Node.js v6 за флагом и v9 без флага . Итак, если вы разрабатываете для среды только для Chrome (например, Electron ) или Node , вы можете начать использовать lookbehinds сегодня!

Положительный взгляд за использование:

console.log(
  "$9.99  €8.47".match(/(?<=\$)\d+(\.\d*)?/) // Matches "9.99"
);

Отрицательный взгляд за использование:

console.log(
  "$9.99  €8.47".match(/(?<!\$)\d+(?:\.\d*)/) // Matches "8.47"
);

Поддержка на других платформах:

  • Mozilla Firefox работает над этим: отслеживается здесь .
  • Microsoft Edge тоже работает над этим: отслеживается здесь (голос пользователя предложение ).
34 голосов
/ 13 марта 2009

Используйте

newString = string.replace(/([abcdefg])?m/, function($0,$1){ return $1?$0:'m';});
9 голосов
/ 23 августа 2013

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

(?:[^a-g])m

... что соответствует каждому m НЕ , которому предшествует любая из этих букв.

1 голос
/ 19 апреля 2015

следуя идее Mijoja и опираясь на проблемы, выявленные JasonS, у меня появилась эта идея; Я немного проверил, но не уверен в себе, поэтому было бы здорово проверить кого-то более опытного, чем я, в js regex :)

var re = /(?=(..|^.?)(ll))/g
         // matches empty string position
         // whenever this position is followed by
         // a string of length equal or inferior (in case of "^")
         // to "lookbehind" value
         // + actual value we would want to match

,   str = "Fall ball bill balll llama"

,   str_done = str
,   len_difference = 0
,   doer = function (where_in_str, to_replace)
    {
        str_done = str_done.slice(0, where_in_str + len_difference)
        +   "[match]"
        +   str_done.slice(where_in_str + len_difference + to_replace.length)

        len_difference = str_done.length - str.length
            /*  if str smaller:
                    len_difference will be positive
                else will be negative
            */

    }   /*  the actual function that would do whatever we want to do
            with the matches;
            this above is only an example from Jason's */



        /*  function input of .replace(),
            only there to test the value of $behind
            and if negative, call doer() with interesting parameters */
,   checker = function ($match, $behind, $after, $where, $str)
    {
        if ($behind !== "ba")
            doer
            (
                $where + $behind.length
            ,   $after
                /*  one will choose the interesting arguments
                    to give to the doer, it's only an example */
            )
        return $match // empty string anyhow, but well
    }
str.replace(re, checker)
console.log(str_done)

мой личный вывод:

Fa[match] ball bi[match] bal[match] [match]ama

принцип заключается в том, чтобы вызывать checker в каждой точке строки между любыми двумя символами, когда эта позиция является начальной точкой:

--- любая подстрока с размером ненужного (здесь 'ba', то есть ..) (если этот размер известен; в противном случае, возможно, сделать это будет сложнее)

--- --- или меньше этого, если это начало строки: ^.?

и, после этого,

--- что нужно искать (здесь 'll').

При каждом вызове checker будет проверяться, не является ли значение до ll тем, что нам не нужно (!== 'ba'); если это так, мы вызываем другую функцию, и именно эта (doer) будет вносить изменения в str, если целью является эта или, в более общем смысле, то она получит во входных данных необходимые данные. вручную обработать результаты сканирования str.

здесь мы меняем строку, поэтому нам нужно было отслеживать разницу в длине, чтобы сместить места, заданные replace, все рассчитано на str, что само по себе никогда не меняется.

, поскольку примитивные строки являются неизменяемыми, мы могли бы использовать переменную str для хранения результата всей операции, но я подумал, что пример, уже усложненный заменами, будет более понятным с другой переменной (str_done) .

Я предполагаю, что с точки зрения производительности это должно быть довольно резким: все эти бессмысленные замены '' в '', this str.length-1 раз, плюс здесь ручная замена делателем, что означает много нарезки ... вероятно, в этом конкретном вышеупомянутом случае, который можно сгруппировать, разрезая строку только один раз на части, где мы хотим вставить [match] и .join(), добавляя ее непосредственно в [match].

Другое дело, что я не знаю, как он справится с более сложными случаями, то есть со сложными значениями для фальшивого вида сзади ... длина, пожалуй, самая проблемная для получения данных.

и, в checker, в случае множественных возможностей нежелательных значений для $ позади, мы должны будем выполнить тест на нем с еще одним регулярным выражением (которое лучше кэшировать (создавать) вне checker, избегать создания одного и того же объекта регулярного выражения при каждом вызове для checker), чтобы знать, действительно ли это то, чего мы стремимся избежать.

надеюсь, я был ясен; если не стесняйтесь, я постараюсь лучше. :)

0 голосов
/ 01 апреля 2019

Вот как я достиг str.split(/(?<!^)@/) для Node.js 8 (который не поддерживает просмотр назад):

str.split('').reverse().join('').split(/@(?!$)/).map(s => s.split('').reverse().join('')).reverse()

Работает? Да (Юникод не проверен). Неприятно? Да.

0 голосов
/ 16 февраля 2018

Используя ваш случай, , если вы хотите заменить m чем-то, например, преобразовав его в верхний регистр M, вы можете отменить набор в группе захвата.

соответствует ([^a-g])m, заменить на $1M

"jim jam".replace(/([^a-g])m/g, "$1M")
\\jiM jam

([^a-g]) будет соответствовать любому символу (не ^) в диапазоне a-g и сохранит его в первой группе захвата, так что вы можете получить к нему доступ с помощью $1.

Итак, мы находим im в jim и заменяем его на iM, что приводит к jiM.

0 голосов
/ 12 апреля 2016

Это может помочь, в зависимости от контекста:

Это соответствует m в jim, но не в jam:

"jim jam".replace(/[a-g]m/g, "").match(/m/g)
...