正则表达式否定后视在 JavaScript 中无效

IT技术 javascript regex regex-lookarounds
2021-02-15 02:37:56

考虑:

var re = /(?<=foo)bar/gi;

这是 Plunker 中无效的正则表达式。为什么?

2个回答

2020 年更新: Javascript 实现开始原生支持正则表达式lookbehinds一种用于建议草案正则表达式向后断言,接受对ECMA-262规范草案为2021的ECMAScript,被实施在V8Irregexp铬6 2+(释放二○一七年十月一十七日)和已经通过一个拾取垫片层Firefox 78+ 中的 IrregexpESR2020 年 6 月30日发布)。其他 JS 解释器将跟随。

在此处查看更详细的支持列表


实现后视的传统解决方法

JavaScript 缺乏对(positive) 和(negative) 之类的则表达式lookbehinds 的支持,但这并不意味着您仍然不能在 JavaScript 中实现这种逻辑。(?<=…)(?<!…)

匹配(非全局)

正向后视匹配:

// from /(?<=foo)bar/i
var matcher = mystring.match( /foo(bar)/i );
if (matcher) {
  // do stuff with matcher[1] which is the part that matches "bar"
}

固定宽度负回顾匹配:

// from /(?<!foo)bar/i
var matcher = mystring.match( /(?!foo)(?:^.{0,2}|.{3})(bar)/i );
if (matcher) {
  // do stuff with matcher[1] ("bar"), which does not follow "foo"
}

负向后视可以在没有全局标志的情况下完成,但只能使用固定宽度,并且必须计算该宽度(这可能会因交替而变得困难)。使用(?!foo).{3}(bar)会更简单且大致等效,但它不会匹配以“rebar”开头的.行,因为无法匹配换行符,因此我们需要替换上面的代码以匹配字符 4 之前具有“bar”的行。

如果您需要可变宽度,请使用以下全局解决方案并breakif的末尾放置 a (此限制是相当普遍的。 .NETVIM,和JGsoft唯一的正则表达式引擎宽度回顾后发。支持可变 PCREPHP,和Perl中被限制为固定的宽度。 Python中需要一个备用module的正则表达式来支持这一点。这就是说,以下解决方法的逻辑应该适用于支持正则表达式的所有语言。)

匹配(全局)

当您需要在给定字符串(g修饰符,全局匹配)matcher中的每个匹配上循环时,您必须在每次循环迭代中重新定义变量,并且您必须使用RegExp.exec()在循环之前创建的 RegExp ),因为String.match()对全局修饰符的解释不同,并且会造成无限循环!

全球正面回顾:

var re = /foo(bar)/gi;  // from /(?<=foo)bar/gi
while ( matcher = re.exec(mystring) ) {
  // do stuff with matcher[1] which is the part that matches "bar"
}

“东西”当然可以包括填充一个数组以供进一步使用。

全局否定回顾:

var re = /(foo)?bar/gi;  // from /(?<!foo)bar/gi
while ( matcher = re.exec(mystring) ) {
  if (!matcher[1]) {
    // do stuff with matcher[0] ("bar"), which does not follow "foo"
  }
}

请注意,在某些情况下,这不能完全代表负面的后视。考虑/(?<!ba)ll/g匹配Fall ball bill balll llama. 它只会找到所需的四个匹配项中的三个,因为当它解析时balll,它会在 处找到ball然后继续一个字符l llama这只发生在末尾的部分匹配可能会干扰另一端的部分匹配(balll中断(ba)?llfoobarbar很好(foo)?bar)时,唯一的解决方案是使用上述固定宽度方法。

更换

Mimicking Lookbehind in JavaScript是一篇很好的文章,描述了如何做到这一点。
它甚至还有一个后续,指向一系列在 JS 中实现这一点的短函数

实现lookbehindString.replace()要容易得多,因为您可以创建一个匿名函数作为替代并处理该函数中的lookbehind逻辑。

这些适用于第一场比赛,但可以通过仅添加g修饰符来实现全局化

正向后视替换:

// assuming you wanted mystring.replace(/(?<=foo)bar/i, "baz"):
mystring = mystring.replace( /(foo)?bar/i,
  function ($0, $1) { return ($1 ? $1 + "baz" : $0) }
);

这需要目标字符串并替换barwith 的实例,baz只要它们跟随foo如果$1匹配则匹配并且三元运算符 ( ?:) 返回匹配的文本和替换文本(但不是bar部分)。否则,三元运算符返回原始文本。

负回顾替换:

// assuming you wanted mystring.replace(/(?<!foo)bar/i, "baz"):
mystring = mystring.replace( /(foo)?bar/i,
  function ($0, $1) { return ($1 ? $0 : "baz") }
);

这本质上是一样的,但由于它是一个负面的后视,它在$1缺失时起作用(我们不需要在$1 + "baz"这里,因为我们知道$1是空的)。

这与其他动态宽度负向后视解决方法具有相同的警告,并且通过使用固定宽度方法类似地修复。

关于固定宽度负后视匹配:模式(?!foo)(?:^.{0,2}|.{3})(bar)最好这样写:((?:^.{0,2}|(?!foo).{3})(bar)另外,想象一下,你必须在以 开头的字符串中处理foo/oar而不是foo/bar)。fooar
2021-04-24 02:37:56
@CasimiretHippolyte – 是的,将鼠标悬停在 PHP 链接上,您会看到我提到它使用 libpcre。您确实可以在固定宽度的后视中使用交替,只要交替的宽度都相同,但交替中的不同宽度不适用于所有引擎。我没有审查你的其他评论,但在全球比赛中向前看可能会出现迭代问题。
2021-04-29 02:37:56
原始问题在其他地方列出了改进(对问题的评论和另一个答案)。我也回答了完善的版本这里在旧版本的这个答案(向下滚动到“您的具体使用情况”),但之后删除它使答案更简洁,更适用于实际问题。
2021-05-11 02:37:56
关于全局匹配前瞻中的注释:为了避免干扰问题,(?<!ba)ll转换为(ba)?ll:一个简单的解决方法是只消耗一个位置并使用前瞻:(ba)?(?=ll).
2021-05-11 02:37:56
“PCRE、PHP 和 Perl 仅限于固定宽度”:PHP 使用 PCRE 正则表达式引擎。另请注意,固定宽度子模式的交替是可能的:(?<=ab|abc|abcd)
2021-05-13 02:37:56

这是一种在 JS 中使用 DOM 解析 HTML 字符串并仅在标签之外执行替换的方法:

var s = '<span class="css">55</span> 2 >= 1 2 > 1';
var doc = document.createDocumentFragment();
var wrapper = document.createElement('myelt');
wrapper.innerHTML = s;
doc.appendChild( wrapper );

function textNodesUnder(el){
  var n, walk=document.createTreeWalker(el,NodeFilter.SHOW_TEXT,null,false);
  while(n=walk.nextNode())
  {
       if (n.parentNode.nodeName.toLowerCase() === 'myelt')
      		n.nodeValue =  n.nodeValue.replace(/>=?/g, "EQUAL"); 
  }
  return el.firstChild.innerHTML;
} 
var res = textNodesUnder(doc);
console.log(res);
alert(res);

众所周知,JS 中的 HTML 应该使用 DOM 解析器处理,并且 regex 应该只针对 tex 节点运行。请参阅RegEx 匹配除 XHTML 自包含标签之外的开放标签
2021-04-22 02:37:56
谢谢。你能让这个演示在 regex101 中工作吗?regex101.com/r/fH0nF3/1
2021-04-30 02:37:56
>=?正则表达式的演示这里是
2021-05-08 02:37:56
抱歉,我需要更复杂的测试用例。请看这个
2021-05-10 02:37:56