English 中文(简体)
JavaScript 中的负回溯形式
原标题:
  • 时间:2009-03-13 03:50:38
  •  标签:

在 JavaScript 正则表达式中是否有实现负向环视的方式?我需要匹配不以特定字符集开头的字符串。

似乎我无法找到一个能够做到这一点,而且如果匹配的部分出现在字符串的开头,则会失败。负向回溯似乎是唯一的答案,但JavaScript没有。

这是我想要工作的正则表达式,但它却没有:

(?<!([abcdefg]))m 的中文翻译是:不匹配任何字符集合(a、b、c、d、e、f 或 g)的前面的小写字母“m”。

这样它就会和“Jim”或“M”中的“M”相匹配,但不会和“Jam”相匹配。

最佳回答

回顾断言已经在 2018 年被接受并进入 ECMAScript 规范。 (Note: This translation assumes the context is programming/technical, and uses terminology commonly seen in Chinese tech publications.)

Positive lookbehind usage:

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

Negative lookbehind usage:

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

平台支持:

问题回答

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

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

2018年之前的答案

由于Javascript支持< a href =“https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#special-negated-look-ahead”rel =”noreferrer“>负向先行断言,一种实现方式是:

  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: Ø

示例二:

根据@neaumusic的评论(匹配max-height而不是line-height,使用的标记为height):

test([ max-height ,  line-height ], /thgieh(?!(-enil))/)

产出:

max-height true token: height
line-height false token: Ø

假设您想要查找所有没有前缀为unsignedint:

支持负回顾:

(?<!unsigned )int

没有负向回望支持:

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

基本上的想法是获取前面 n 个字符,然后排除负面环视匹配,同时匹配没有前面 n 个字符的情况。(其中 n 是后顾长度)。

因此,在这个问题中的正则表达式是:

(?<!([abcdefg]))m

会翻译为:

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

你可能需要尝试使用捕获组来找到你感兴趣或想要替换特定部分的字符串的确切位置。

米乔加的策略适用于您的特定情况,但不适用于一般情况:

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,但匹配了第二对。但是通过匹配前两个 l,然后将该匹配视为假阳性而忽略,正则表达式引擎从该匹配的结束处继续,忽略假阳性中的任何字符。

使用

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

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

(?:[^a-g])m

这将匹配每一个未被任何那些字母之一预先标记的m

这是我在 Node.js 8 中实现 str.split(/(?<!^)@/) 的方法(因为它不支持后置断言):

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会更清晰明了。

i guess that in terms of performances it must be pretty harsh: all those pointless replacements of into , this str.length-1 times, plus here manual replacement by doer, which means a lot of slicing... probably in this specific above case that could be grouped, by cutting the string only once into pieces around where we want to insert [match] and .join()ing it with [match] itself.

另一件事是我不知道它如何处理更复杂的情况,也就是假后顾的复杂值...长度可能是最难获取的数据。

checker 中,如果 $behind 存在多种非期望值的可能性,则我们需要使用另一个正则表达式对其进行测试(最好在 checker 外部缓存(创建)的正则表达式对象,以避免每次调用 checker 时创建相同的正则表达式对象),以确定它是否是我们要避免的情况。

希望我表达得清楚;如果没有,请不要犹豫,我会努力的。 :)

使用您的实例,如果您想要替换 m,例如将其转换为大写字母 M,您可以在捕获组中使用否定集。

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

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

([^a-g])会匹配在a-g范围之外的任何字符,并将其存储在第一捕获组中,因此您可以使用$1访问它。

所以我们在jim中找到im,并用iM替换它,结果为jiM

如前所述,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 
s = "" + s + """;
  var match = reg.exec(s);
  while(match) {
    var start = match.index + match[1].length;
    out += "
Whole match: " + match[0] + ", starts at: " + match.index
        +  ". Desired match: " + match[2] + ", starts at: " + start + ".";   
    match = reg.exec(s);
  }
  out += "
Resulting string after statement s.replace(reg, "$1*$2*")
"
         + 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个字符长。





相关问题