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

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

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

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

(?<!([abcdefg]))m

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

Ответы

Ответ 1

Утверждения "за кадром" были приняты в спецификации ECMAScript <201> в 2018 году. Пока что они реализованы только в V8. Итак, если вы разрабатываете для среды только для Chrome (например, Electron) или Node, вы можете начать использовать "lookbehind" сегодня!

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

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

Ответ 2

Начиная с 2018 года, утверждения "Lookhehind" являются частью спецификации языка ECMAScript.

// positive lookbehind
(?<=...)
// negative lookbehind
(?<!...)

Ответ до 2018 года

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

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

Ответ 3

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

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

(?<!unsigned )int

Без поддержки отрицательного внешнего вида:

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

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

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

(?<!([abcdefg]))m

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

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

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

Ответ 4

Стратегия 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

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

Ответ 5

Использование

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

Ответ 6

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

(?:[^a-g])m

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

Ответ 7

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

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

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

Ответ 8

следуя идее Миджои, и, опираясь на проблемы, выявленные 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 */



        /*  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 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].

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

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

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

Ответ 9

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

match ([^a-g])m, замените на $1M

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

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

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

Ответ 10

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

Бьюсь об заклад, в моей голове нет способа найти регулярное выражение без взгляда назад, которое точно дает результат. Все, что вы можете сделать, это работать с группами. Предположим, у вас есть регулярное выражение (?<!Before)Wanted, где Wanted - это регулярное выражение, которое вы хотите сопоставить, а Before - это регулярное выражение, которое отсчитывает то, что не должно предшествовать совпадению. Лучшее, что вы можете сделать, это отменить регулярное выражение Before и использовать регулярное выражение NotBefore(Wanted). Желаемый результат - первая группа $1.

В вашем случае Before=[abcdefg] который легко отрицать NotBefore=[^abcdefg]. Таким образом, регулярное выражение будет [^abcdefg](m). Если вам нужна позиция Wanted, вы должны также сгруппировать NotBefore, чтобы желаемым результатом была вторая группа.

Если совпадения шаблона Before имеют фиксированную длину n, то есть если шаблон не содержит повторяющихся токенов, вы можете избежать отрицания шаблона Before и использовать регулярное выражение (?!Before).{n}(Wanted), но все же должны использовать первую группу или использовать регулярное выражение (?!Before)(.{n})(Wanted) и использовать вторую группу. В этом примере шаблон Before самом деле имеет фиксированную длину, а именно 1, поэтому используйте регулярное выражение (?![abcdefg]).(m) или (?![abcdefg])(.)(m). Если вас интересуют все совпадения, добавьте флаг g, посмотрите мой фрагмент кода:

function TestSORegEx() {
  var s = "Donald Trump doesn't like jam, but Homer Simpson does.";
  var reg = /(?![abcdefg])(.{1})(m)/gm;
  var out = "Matches and groups of the regex " + 
            "/(?![abcdefg])(.{1})(m)/gm in \ns = \"" + s + "\"";
  var match = reg.exec(s);
  while(match) {
    var start = match.index + match[1].length;
    out += "\nWhole match: " + match[0] + ", starts at: " + match.index
        +  ". Desired match: " + match[2] + ", starts at: " + start + ".";   
    match = reg.exec(s);
  }
  out += "\nResulting string after statement s.replace(reg, \"$1*$2*\")\n"
         + s.replace(reg, "$1*$2*");
  alert(out);
}

Ответ 11

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

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

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

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

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

Ответ 12

Если вам нужна совместимость с браузерами в 2019 году, то все, что вы можете сделать, это использовать регулярные выражения без обратной связи. Это может сработать, но я не уверен, что он делает то же самое, если m находится в начале строки, я до сих пор редко использовал lookbehind в JS по очевидным причинам.

([^abcdefg])m

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

match.index += match[1].length;
match[0] = match[0].slice(match[1].length);

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

Ответ 13

/(?![abcdefg])[^abcdefg]m/gi да это трюк.

Ответ 14

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

Это соответствует m в jim, но не застревание:

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