Javascript正则表达式-同时向后看和向前看

Javascript正则表达式-同时向后看和向前看,javascript,regex,negative-lookahead,negative-lookbehind,Javascript,Regex,Negative Lookahead,Negative Lookbehind,我试图在JavaScript中创建一个正则表达式,该正则表达式与字符b匹配,前提是该正则表达式前面或后面没有字符a 显然,JavaScript正则表达式没有易于实现的反向查找功能,这使得任务变得困难。我想出了下面的一个,但它不起作用 "ddabdd".replace(new RegExp('(?:(?![a]b(?![a])))*b(?![a])', 'i'),"c"); 是我能想到的最好的了。这里,b不应该匹配,因为它前面有a,但它匹配 下面是一些我想要实现的例子 "ddbdd" match

我试图在JavaScript中创建一个正则表达式,该正则表达式与字符
b
匹配,前提是该正则表达式前面或后面没有字符
a

显然,JavaScript正则表达式没有易于实现的反向查找功能,这使得任务变得困难。我想出了下面的一个,但它不起作用

"ddabdd".replace(new RegExp('(?:(?![a]b(?![a])))*b(?![a])', 'i'),"c");
是我能想到的最好的了。这里,
b
不应该匹配,因为它前面有
a
,但它匹配

下面是一些我想要实现的例子

"ddbdd" matches the b
"b" matches the b
"ddb" matches the b
"bdd" matches the b
"ddabdd" or "ddbadd" does not match the b

似乎您可以使用包含字符串锚点开头或“b”前面的否定字符类的捕获组,同时使用否定前瞻断言“a”也不跟随。然后,您只需在替换调用中引用
$1
,以及替换字符串的其余部分

var s = 'ddbdd b ddb bdd ddabdd ddabdd ddbadd';
var r = s.replace(/(^|[^a])b(?!a)/gi, '$1c');
console.log(r); //=> "ddcdd c ddc cdd ddabdd ddabdd ddbadd"

<强>编辑: As@ nHaHTDH指出了关于连续字符的注释,可以考虑回调。

var s = 'ddbdd b ddb bdd ddabdd ddabdd ddbadd sdfbbfds';
var r = s.replace(/(a)?b(?!a)/gi, function($0, $1) {
    return $1 ? $0 : 'c';
});
console.log(r); //=> "ddcdd c ddc cdd ddabdd ddabdd ddbadd sdfccfds"

似乎您可以使用包含字符串锚点开头或“b”前面的否定字符类的捕获组,同时使用否定前瞻断言“a”也不跟随。然后,您只需在替换调用中引用
$1
,以及替换字符串的其余部分

var s = 'ddbdd b ddb bdd ddabdd ddabdd ddbadd';
var r = s.replace(/(^|[^a])b(?!a)/gi, '$1c');
console.log(r); //=> "ddcdd c ddc cdd ddabdd ddabdd ddbadd"

<强>编辑: As@ nHaHTDH指出了关于连续字符的注释,可以考虑回调。

var s = 'ddbdd b ddb bdd ddabdd ddabdd ddbadd sdfbbfds';
var r = s.replace(/(a)?b(?!a)/gi, function($0, $1) {
    return $1 ? $0 : 'c';
});
console.log(r); //=> "ddcdd c ddc cdd ddabdd ddabdd ddbadd sdfccfds"

在这种情况下,无法单独使用regex模拟look-behind的行为,因为字符串中可能存在连续的
b
,这需要look-behind的零宽度属性来检查前一个字符

由于“后视”中的条件非常简单,因此可以在“更换”功能中检查:

inputString.replace(/b(?!a)/gi, function ($0, idx, str) {
    if (idx == 0 || !/a/i.test(str[idx - 1])) { // Equivalent to (?<!a)
        return 'c';
    } else {
        return $0; // $0 is the text matched by /b(?!a)/
    }
});
inputString.replace(/b(?!a)/gi,函数($0,idx,str){

如果(idx==0 | |!/a/i.test(str[idx-1]){//等价于(?),那么在这种情况下,无法单独用正则表达式模拟look-behind的行为,因为字符串中可能存在连续的
b
,这需要look-behind的零宽度属性来检查前一个字符

由于“后视”中的条件非常简单,因此可以在“更换”功能中检查:

inputString.replace(/b(?!a)/gi, function ($0, idx, str) {
    if (idx == 0 || !/a/i.test(str[idx - 1])) { // Equivalent to (?<!a)
        return 'c';
    } else {
        return $0; // $0 is the text matched by /b(?!a)/
    }
});
inputString.replace(/b(?!a)/gi,函数($0,idx,str){

if(idx==0 | |!/a/i.test(str[idx-1]){//相当于(?您在这里真正想做的是为一种小型语言编写一个解析器。Regexp在一些解析任务上很好,但在很多方面很差(JS Regexp的功能有些不足)。您可能能够找到一个在特定情况下工作的regexp,然后当您的语法规则更改时,regexp可能很难或不可能更改以反映这一点。下面的简单程序的优点是可读性和可维护性。它完全按照它所说的做

function find_bs(str) {
    var indexes = [];
    for (var i = 0; i < str.length; i++) {
        if (str[i] === 'b' && str[i-1] !== 'a' && str[i+1] !== 'a')
            indexes.push(i);
    }
    return indexes;
}
您需要调整逻辑以处理字符串的开头和结尾

这是如何工作的


我们使用regexp查找整个
xbx
字符串。
b
的索引将是1加上匹配的索引,因此我们记录这一点。在进行下一次匹配之前,我们将
lastIndex
重置为
b
,它控制搜索将继续进行的起始点,因此它将作为第一个字符of任何以下潜在匹配。

您在这里真正想做的是为一种小型语言编写一个解析器。Regexp在某些解析任务上做得很好,但在许多方面做得不好(而且JS Regexp的功能有些不足)。您可能能够找到一个在特定情况下工作的regexp,然后当您的语法规则更改时,regexp可能很难或不可能更改以反映这一点。下面的简单程序的优点是可读性和可维护性。它完全按照它所说的做

function find_bs(str) {
    var indexes = [];
    for (var i = 0; i < str.length; i++) {
        if (str[i] === 'b' && str[i-1] !== 'a' && str[i+1] !== 'a')
            indexes.push(i);
    }
    return indexes;
}
您需要调整逻辑以处理字符串的开头和结尾

这是如何工作的


我们使用regexp查找整个
xbx
字符串。
b
的索引将是1加上匹配的索引,因此我们记录这一点。在进行下一次匹配之前,我们将
lastIndex
重置为
b
,它控制搜索将继续进行的起始点,因此它将作为第一个字符of任何以下潜在匹配。

(?:(?![a]b(?![a]))*
似乎没有多大意义。非捕获组不分组任何内容。只要您有可选(或重复)的环顾,你也可以省略它。可能是重复的我正在尝试创建一个可以开门的香蕉。@torazaburo:这不是一个好的重复目标,因为它会进一步混淆读者。请收回你的投票。
(?:(?![a]b(?![a]))*
似乎没有多大意义。非捕获组不分组任何内容。无论何时,只要有可选(或重复)的环顾,你也可以省略它。可能是重复的我正在试图创造一个可以开门的香蕉。@torazaburo:这不是一个好的重复目标,因为它会让读者更加困惑。请撤回你的投票。