正则表达式否定后向在 JavaScript 中无效 [重复]

Posted

技术标签:

【中文标题】正则表达式否定后向在 JavaScript 中无效 [重复]【英文标题】:Regex negative lookbehind not valid in JavaScript [duplicate] 【发布时间】:2016-05-10 14:27:44 【问题描述】:

考虑:

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

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

【问题讨论】:

因为JS不支持lookbehinds。使用var re = /foo(bar)/gi;真正的问题是什么? 顺便说一句,这是积极的。这是负面的.. (?&lt;!foo) 正如 Wiktor 所说,javascript 不支持后视。 我想匹配 >= 和 > 但不包括 > 内跨度。样本数据为 55 2 >= 1 2 > 1 这里是示例。 regex101.com/r/fH0nF3/1 该文本来自哪里?它在 DOM 内部吗? 【参考方案1】:

2020 年更新: Javascript 实现开始原生支持 regular expression lookbehinds。 RegExp Lookbehind Assertions 的提案草案已被 ECMAScript 2021 的 ECMA-262 draft specs 接受,已在 Chrome 62+(2017 年 10 月 17 日发布)中实现 in V8 的 Irregexp,并且具有已通过 shim layer 获取 Firefox 78+ 中的 Irregexp(ESR,于 2020 年 6 月 30 日发布)。其他 JS 解释器将跟随。

See more detailed support listings here.


实现后视的传统解决方法

JavaScript 缺乏对 regular expression lookbehinds 的支持,例如 (?&lt;=…)(正)和 (?&lt;!…)(负),但这并不意味着您仍然不能在 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"

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

如果您需要可变宽度,请使用以下全局解决方案并在 if 节的末尾放置一个 break。 (此限制很常见。.NET、vim 和 JGsoft 是支持可变宽度后视的 only 正则表达式引擎。PCRE、php 和 Perl 仅限于固定宽度。 Python 需要 alternate regex module 来支持这一点。也就是说,下面解决方法的逻辑应该适用于支持正则表达式的所有语言。)

匹配(全局)

当您需要循环给定字符串中的每个匹配项(g 修饰符,全局匹配)时,您必须在每次循环迭代中重新定义 matcher 变量,并且必须使用 RegExp.exec()(使用 RegExp创建了before the loop) 因为String.match() 解释了全局修饰符differently 并会创建一个无限循环!

全球正面回顾:

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"

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

全局否定后视:

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"
  

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

更换

Mimicking Lookbehind in JavaScript 是一篇很棒的文章,描述了如何做到这一点。 它甚至有一个后续指向 collection of short functions 在 JS 中实现这一点。

String.replace() 中实现lookbehind 要容易得多,因为您可以创建anonymous function 作为替换并处理该函数中的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) 
);

这将获取目标字符串并将bar 的实例替换为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 是空的)。

这与其他动态宽度负回溯解决方法具有相同的警告,并且通过使用固定宽度方法进行了类似的修复。

【讨论】:

原始问题在别处列出了改进(问题和另一个答案的cmets)。我还在此答案的旧版本中回答了精炼版本here(向下滚动到“您的特定用例”),但后来将其删除以使答案更简洁,更适用于实际问题。 关于全局匹配前瞻中的注释:为避免干扰问题,将(?&lt;!ba)ll转换为(ba)?ll:一个简单的解决方法包括只消耗一个位置并使用前瞻:(ba)?(?=ll). 关于Fixed widthnegative lookbehind match:模式(?!foo)(?:^.0,2|.3)(bar)最好这样写:(?:^.0,2|(?!foo).3)(bar)(此外,想象一下,而不是foo/bar 你必须在一个以fooar 开头的字符串中处理foo/oar "PCRE、PHP 和 Perl 仅限于固定宽度":PHP 使用 PCRE 正则表达式引擎。另请注意,固定宽度子模式的交替是可能的:(?&lt;=ab|abc|abcd) @CasimiretHippolyte – 是的,将鼠标悬停在 PHP 链接上,您会看到我提到过它使用 libpcre。您确实可以在固定宽度的后视中使用交替,只要交替都具有相同的宽度,但交替中的不同宽度不适用于所有引擎。我还没有审查过你的其他 cmets,但在全球比赛中的前瞻可能会遇到迭代问题。【参考方案2】:

这是一种在 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);

【讨论】:

谢谢。你能让演示在 regex101 中工作吗? regex101.com/r/fH0nF3/1 &gt;=? 正则表达式的演示? Here it is 对不起,我需要更复杂的测试用例。请看this 众所周知,JS 中的 HTML 应该使用 DOM 解析器来处理,而正则表达式应该只针对 tex 节点运行。见RegEx match open tags except XHTML self-contained tags

以上是关于正则表达式否定后向在 JavaScript 中无效 [重复]的主要内容,如果未能解决你的问题,请参考以下文章

python里使用正则表达式的后向搜索肯定模式

python里使用正则表达式的后向搜索肯定模式

python里使用正则表达式的后向搜索肯定模式

如何在 JavaScript 字符串替换中否定匹配正则表达式? [复制]

正则表达式之基础

非固定长度的正则表达式负回溯