Javascript: отрицательный эквивалент lookbehind?

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

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

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

(?<!([abcdefg]))m

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

11 ответов


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

положительное использование lookbehind:

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

отрицательное использование lookbehind:

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

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

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

как Javascript поддерживает отрицательный lookahead, один безопасный способ сделать это:

Пусть говорят, что вы хотите сделать lookbehind, как это

(?<!([abcdefg]))m
  1. обратный строку, чтобы соответствовать
  2. примените свой шаблон "reversed" с помощью lookahead (будьте осторожны с обратным выражением соответствия внутри lookahead, в этом случае он остается неизменным)

    m(?!([abcdefg]))
    
  3. reverse все сопоставленные жетоны

примеры:

я определяю следующие функции:

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:

следующий вопрос @andrew-ensley:

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: Ø

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

С поддержкой отрицательного взгляда сзади:

(?<!unsigned )int

без поддержки отрицательного взгляда сзади:

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

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

поэтому regex в вопрос:

(?<!([abcdefg]))m

перевести на:

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

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


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

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

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


использовать

newString = string.replace(/([abcdefg])?m/, function(,){ return ?:'m';});

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

(?:[^a-g])m

...который будет соответствовать каждому m не предшествовать любые из этих букв.


следуя идее 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 в каждой точке строки между любыми двумя символами, когда эта позиция является начальной точкой:

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

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

и после этого

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

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

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

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

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

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

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

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


это эффективно делает это

"jim".match(/[^a-g]m/)
> ["im"]
"jam".match(/[^a-g]m/)
> null

поиск и замена примера

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

обратите внимание, что отрицательная строка поиска должна быть длиной 1 символ, чтобы это работало.


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

матч ([^a-g])m заменить на M

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

([^a-g]) будет соответствовать любой символ не(^) в


/(?![abcdefg])[^abcdefg]m/gi да, это уловка.


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

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

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