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