Javascript:否定的后向等效?


141

有没有办法实现负面的回望在javascript正则表达式中?我需要匹配一个不以特定字符集开头的字符串。

如果在字符串的开头找到匹配的部分,似乎无法找到执行此操作的正则表达式。负向后看似是唯一的答案,但是javascript没有答案。

编辑:这是我想工作的正则表达式,但它不:

(?<!([abcdefg]))m

因此它将与“ jim”或“ m”中的“ m”匹配,但与“ jam”不匹配


考虑发布正则表达式,因为它看起来带有负向外观;这样可以使响应更容易。
Daniel LeCheminant 09年

1
那些想追踪采用隐秘方式的人,
WiktorStribiżew,

@WiktorStribiżew:在2018年规范中添加了后向功能。Chrome支持它们,但是Firefox仍未实现该规范
Lonnie Best,

这还需要后面看看吗?那(?:[^abcdefg]|^)(m)呢 就像"mango".match(/(?:[^abcdefg]|^)(m)/)[1]
slebetman

Answers:


57

后向断言得到了接受ECMAScript规范在2018年。

正向后方用法:

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

负向后使用:

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

平台支持:


2
有没有保鲜纸?
Killy

1
@Killy据我所知,我怀疑是否还会有,因为创建一个可能非常不切实际(IE用JS编写完整的Regex实现)
Okku,

如何使用babel插件,是否可以将其编译为ES5或已支持的ES6?
Stefan J

1
@IlpoOksanen我想你的意思是扩展RegEx实现..这就是polyfills的作用..而且用JavaScript编写逻辑没有错
neaumusic

1
你在说什么?几乎所有提案都受其他语言的启发,并且它们总是会喜欢在惯用JS和向后兼容的情况下有意义的其他语言的语法和语义进行匹配。我想我很清楚地说过,2017年的规范中接受了正向和负面的回溯,2017年也给出了链接。此外,我详细描述了哪些平台实现了该规范以及其他平台的状态-甚至从那时起对其进行了更新。自然,这不是我们将要看到的最后一个Regexp功能
Okku,

83

自2018年以来,Lookbehind Assertions已成为ECMAScript语言规范的一部分

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

回答2018年之前

由于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:

以下@ 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: Ø

36
这种方法的问题在于,当您同时进行前瞻和后

3
您能显示一个有效的例子吗,说我想比赛,max-height但我不想比赛line-height,我只想比赛是height
neaumusic 2015年

如果任务是替换两个连续的相同符号(且不超过2个),且之前没有某个符号,则无济于事。''(?!\()将替换''(''test'''''''test另一端的撇号,从而留下(''test'NNNtest而不是(''testNNN'test
WiktorStribiżew'16

60

假设您要查找所有int不以unsigned:结尾的对象:

支持负向后看:

(?<!unsigned )int

不支持负面的后顾之忧:

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

基本上,想法是抓住n个前面的字符,并排除具有负前瞻性的匹配,但也要匹配没有前面n个字符的情况。(其中n是向后看的长度)。

所以正则表达式有问题:

(?<!([abcdefg]))m

将转换为:

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

您可能需要与捕获组一起使用,以找到您感兴趣的字符串的确切位置,或者您想用其他东西替换特定部分。


2
这应该是正确的答案。请参阅:"So it would match the 'm' in 'jim' or 'm', but not 'jam'".replace(/(j(?!([abcdefg])).|^)m/g, "$1[MATCH]") 退货"So it would match the 'm' in 'ji[MATCH]' or 'm', but not 'jam'" 这非常简单并且有效!
Asrail 2015年

41

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”-后面的真实位置应该抑制了前两个l,但匹配了第二对。但是,通过匹配前2个l,然后忽略该匹配为误报,正则表达式引擎将从该匹配的结尾开始,并忽略误报内的任何字符。


5
啊,你是对的。但是,这比我以前要紧密得多。我可以接受,直到出现更好的情况为止(例如javascript实际上实现lookbehinds)。
安德鲁·恩斯利

33

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

10
这不会做任何事情:newString将始终等于string。为什么这么多投票?
MikeM 2013年

@MikeM:因为重点只是演示一种匹配技术。
错误

57
@bug。不执行任何操作的演示是一种奇怪的演示。答案似乎就好像只是复制和粘贴而对它的工作原理没有任何了解。因此,缺乏附带的解释,并且无法证明任何东西都已经匹配。
MikeM 2013年

2
@MikeM:SO的规则是,如果它按照书面形式回答问题,那是正确的。OP没有指定用例
缺陷

7
这个概念是正确的,但是是的,并不是很好。尝试在JS控制台中运行它 "Jim Jam Momm m".replace(/([abcdefg])?m/g, function($0, $1){ return $1 ? $0 : '[match]'; });。它应该返回Ji[match] Jam Mo[match][match] [match]。但也请注意,正如杰森(Jason)下文所述,在某些情况下它可能会失败。
西蒙东

11

您可以通过否定字符集来定义一个非捕获组:

(?:[^a-g])m

...将与每个m NOT开头的那些字母相匹配。


2
我认为比赛实际上也将覆盖前面的角色。
2013年

4
^这是真的。角色类代表...一个角色!您的所有非捕获组正在做的事情不是在替换上下文中提供该值。您的表情不是说“每一个都不以任何字母开头”,而是说“每一个以不
等于

5
为使答案也能解决原始问题(字符串的开头),它还必须包含一个选项,因此生成的正则表达式将为(?:[^a-g]|^)m。有关运行示例,请参见regex101.com/r/jL1iW6/2
Johny Skovdal '16

使用空逻辑并不总是具有理想的效果。
GoldBishop

2

这是我str.split(/(?<!^)@/)为Node.js 8(不支持后向)实现的:

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

作品?是的(unicode未经测试)。不愉快?是。


1

遵循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,再加上动手进行手动替换,这意味着需要大量切片……在上述特定情况下,可能通过仅将字符串切成一小段的方式将其分组,使其围绕我们要插入的位置[match]并与.join()[match]自身对齐。

另一件事是,我不知道它将如何处理更复杂的情况,也就是说,伪造后视的复杂值……长度可能是最有问题的数据。

并且,在中checker,如果存在$ behind不必要的值的多种可能性,我们将不得不使用另一个正则表达式(要在外部缓存(创建))进行测试checker,以避免生成相同的正则表达式对象在每次调用checker)时都知道这是否是我们想要避免的。

希望我已经清楚了;如果不犹豫,我会尽力而为。:)


1

如果您要替换 m为某些内容,例如将其转换为大写,使用您的案例M,则可以否定捕获组中的集合。

匹配([^a-g])m,替换为$1M

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

([^a-g])将匹配范围内的任何char not(^a-g,并将其存储在第一个捕获组中,因此您可以使用进行访问$1

因此我们发现imjim,取而代之的是iM其结果jiM


1

如前所述,JavaScript现在允许回溯。在较旧的浏览器中,您仍然需要解决方法。

我敢打赌,如果没有向后看就能找到准确传递结果的正则表达式,那是无法找到的。您所能做的就是与小组合作。假设您有一个regex (?<!Before)Wanted,其中Wanted您要匹配Before的正则表达式是,该regex计算出在匹配之前不应该包含的内容。您能做的最好的事情就是否定正则表达式Before并使用正则表达式NotBefore(Wanted)。期望的结果是第一组$1

就您而言Before=[abcdefg],这很容易被否定NotBefore=[^abcdefg]。因此正则表达式将是[^abcdefg](m)。如果需要的位置Wanted,则必须分组NotBefore,以便所需的结果是第二组。

如果Before模式的匹配项具有固定的长度n,也就是说,如果模式不包含重复的标记,则可以避免取反该Before模式并使用正则表达式(?!Before).{n}(Wanted),但是仍然必须使用第一个组或使用正则表达式(?!Before)(.{n})(Wanted)并使用第二个表达式组。在此示例中,图案Before实际上具有固定长度,即1,因此请使用regex (?![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);
}

0

这有效地做到了

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

搜索并替换示例

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

请注意,负向后搜索字符串必须为1个字符长,这样才能正常工作。


1
不完全的。在“ Jim”中,我不要“ i”;只是“ m”。而"m".match(/[^a-g]m/)yeilds null为好。在这种情况下,我也想要“ m”。
Andrew Ensley

-1

/(?![abcdefg])[^abcdefg]m/gi 是的,这是一个把戏。


5
该检查(?![abcdefg])是完全多余的,因为[^abcdefg]已经做好了防止这些字符匹配的工作。
nhahtdh

2
这将与没有前面字符的'm'匹配。
Andrew Ensley 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.