有没有一种方法可以在 JavaScript 正则表达式中实现相当于否定的向后查找?我需要匹配一个不以特定字符集开头的字符串。
如果在字符串的开头找到匹配的部分,我似乎无法找到执行此操作而不会失败的正则表达式。负向回溯似乎是唯一的答案,但 JavaScript 没有。
这是我想要工作的正则表达式,但它没有:
(?<!([abcdefg]))m
所以它会匹配'jim'或'm'中的'm',但不匹配'jam'
有没有一种方法可以在 JavaScript 正则表达式中实现相当于否定的向后查找?我需要匹配一个不以特定字符集开头的字符串。
如果在字符串的开头找到匹配的部分,我似乎无法找到执行此操作而不会失败的正则表达式。负向回溯似乎是唯一的答案,但 JavaScript 没有。
这是我想要工作的正则表达式,但它没有:
(?<!([abcdefg]))m
所以它会匹配'jim'或'm'中的'm',但不匹配'jam'
自 2018 年以来,Lookbehind Assertions成为ECMAScript 语言规范的一部分。
// positive lookbehind
(?<=...)
// negative lookbehind
(?<!...)
2018年前的回答
由于 Javascript 支持负前瞻,一种方法是:
反转输入字符串
与反向正则表达式匹配
反转并重新格式化匹配
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: Ø
Lookbehind Assertions在2018 年被ECMAScript 规范接受。
积极的后视用法:
console.log(
"$9.99 €8.47".match(/(?<=\$)\d+\.\d*/) // Matches "9.99"
);
负后视用法:
console.log(
"$9.99 €8.47".match(/(?<!\$)\d+\.\d*/) // Matches "8.47"
);
平台支持:
假设您要查找int
前面没有的所有内容unsigned
:
支持消极的后视:
(?<!unsigned )int
不支持负面回顾:
((?!unsigned ).{9}|^.{0,8})int
基本上想法是抓住 n 个前面的字符并排除匹配与否定前瞻,但也匹配没有前面 n 个字符的情况。(其中 n 是后视的长度)。
所以有问题的正则表达式:
(?<![abcdefg])m
将转化为:
([^abcdefg]|^)m
((?![abcdefg]).|^)m
您可能需要使用捕获组来找到您感兴趣的字符串的确切位置,或者您想用其他东西替换特定部分。
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”,则不匹配。注意“球”这个词——真正的后视应该抑制了前 2 个 l's 但与第二对匹配。但是通过匹配前 2 个 l,然后将该匹配作为误报忽略,正则表达式引擎从该匹配的末尾继续,并忽略误报中的任何字符。
采用
newString = string.replace(/([abcdefg])?m/, function($0,$1){ return $1?$0:'m';});
您可以通过否定字符集来定义非捕获组:
(?:[^a-g])m
...这将匹配前面有任何这些字母的每个m
NOT 。
这是我str.split(/(?<!^)@/)
为 Node.js 8 实现的(不支持后视):
str.split('').reverse().join('').split(/@(?!$)/).map(s => s.split('').reverse().join('')).reverse()
作品?是的(未经测试的unicode)。不愉快?是的。
遵循 Mijoja 的想法,借鉴 JasonS 暴露的问题,我有了这个想法;我检查了一下,但不确定自己,所以在 js 正则表达式中由比我更专家的人进行验证会很棒:)
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
时间,加上这里由 doer 手动替换,这意味着很多切片......可能在上述特定情况下可能被分组,通过将字符串仅切割一次,在我们想要插入的位置周围将其[match]
与自身.join()
一起 ing 。[match]
另一件事是我不知道它将如何处理更复杂的情况,即假后视的复杂值......长度可能是最有问题的数据。
并且,在 中checker
,如果 $behind 有多种不需要的值的可能性,我们将不得不使用另一个正则表达式对其进行测试(checker
最好在外部缓存(创建),以避免创建相同的正则表达式对象在每次调用checker
) 时,了解它是否是我们试图避免的。
希望我已经清楚了;如果不犹豫,我会努力的更好。:)
使用您的情况,如果您想 m
用某些东西替换,例如将其转换为大写M
,您可以否定捕获组中的设置。
匹配([^a-g])m
,替换为$1M
"jim jam".replace(/([^a-g])m/g, "$1M")
\\jiM jam
([^a-g])
将匹配范围内的任何 char not( ^
) a-g
,并将其存储在第一个捕获组中,因此您可以使用$1
.
所以我们找到im
injim
并将其替换iM
为jiM
.
如前所述,JavaScript 现在允许后视。在较旧的浏览器中,您仍然需要一种解决方法。
我敢打赌,如果没有后视,就没有办法找到一个正则表达式来准确地提供结果。您所能做的就是与小组合作。假设您有一个 regex (?<!Before)Wanted
,Wanted
您要匹配Before
的 regex 是哪里,并且是计算不应该在匹配之前的内容的 regex。您能做的最好的事情就是否定正则表达式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);
}
这有效地做到了
"jim".match(/[^a-g]m/)
> ["im"]
"jam".match(/[^a-g]m/)
> null
搜索和替换示例
"jim jam".replace(/([^a-g])m/g, "$1M")
> "jiM jam"
请注意,负的后视字符串必须为 1 个字符长才能正常工作。
/(?![abcdefg])[^abcdefg]m/gi
是的,这是一个技巧。