如何只匹配前面有偶数个 % 的数字?

How to match only those numbers which have an even number of `%`s preceding them?

我想捕捉出现在字符串中任意位置的数字,并将它们替换为“(.+)”。

但我只想捕获前面有偶数个 % 的数字。如果周围的字符被捕获,不用担心:我们可以使用捕获组来过滤掉数字。

我想不出 ECMAscript 正则表达式。

这里是游乐场:

abcd %1 %%2 %%%3 %%%%4 efgh

abcd%12%%34%%%666%%%%11efgh

一个成功的捕捉将表现如下:


我尝试过的事情:


如果你意识到了,第三次尝试就差不多成功了。唯一的问题是在操场的第二行。 其实我想表达的意思是:

如果数字前面有偶数个 % 且满足以下任一条件,则匹配该数字:

有没有办法匹配字符的缺失
这就是我在第三次尝试中使用 [=14=] 尝试做的事情。

我不知道 ECMAScript,但以下文档有答案:

ECMAScript regex

搜索负前瞻,这将导致类似这样的结果:

(?!%)(([%]{2})*\d+)

...其中 (?!%) 表示前面没有 % 文字。

您可以使用 (?:[^%\d]|^|\b(?=%))(?:%%)*(\d+) 作为模式,您的号码将存储在第一个捕获组中。这也处理以零个 % 字符开头的数字。

这将匹配偶数个 % 符号,如果它们前面有:

  • 既不是 % 也不是数字(因此我们不需要捕获 % 之前的最后一个数字,因为这不适用于像 %%1%%2 这样的链)
  • 字符串的开头
  • 一个单词边界(因此任何单词字符),对于上面提到的链

你可以看到它的实际效果here

问题

您想要一个具有负无限宽度后视的正则表达式:

(?<=(^|[^%])(?:%%)*)\d+

这是.NET regex demo

在ES7中不支持,需要使用特定语言的方式和简化的正则表达式来匹配数字序列前的任意数量的%/(%*)(\d+)/g然后在里面检查replace百分号是否为偶数回调,并进行处理

JavaScript

与其尝试模拟可变宽度的后视,不如使用 JS 方法:

var re = /(%*)(\d+)/g;          // Capture into Group 1 zero or more percentage signs
var str = 'abcd %1 %%2 %%%3 %%%%4 efgh<br/><br/>abcd%12%%34%%%666%%%%11efgh';
var res = str.replace(re, function(m, g1, g2) { // Use a callback inside replace
  return (g1.length % 2 === 0) ? g1 + '(.+)' : m; // If the length of the %s is even
});                             // Return Group 1 + (.+), else return the whole match
document.body.innerHTML = res;

如果数字前必须至少有 2 个 %,请使用 /(%+)(\d+)/g 正则表达式模式,其中 %+ 匹配至少 1 个(或更多)百分号。

转换为 C++

同样的算法也可以用在C++中。唯一的问题是 std::regex_replace 中没有对回调方法的内置支持。可以手动添加,这样使用:

#include <iostream>
#include <cstdlib>
#include <string>
#include <regex>
using namespace std;

template<class BidirIt, class Traits, class CharT, class UnaryFunction>
std::basic_string<CharT> regex_replace(BidirIt first, BidirIt last,
    const std::basic_regex<CharT,Traits>& re, UnaryFunction f)
{
    std::basic_string<CharT> s;

    typename std::match_results<BidirIt>::difference_type
        positionOfLastMatch = 0;
    auto endOfLastMatch = first;

    auto callback = [&](const std::match_results<BidirIt>& match)
    {
        auto positionOfThisMatch = match.position(0);
        auto diff = positionOfThisMatch - positionOfLastMatch;

        auto startOfThisMatch = endOfLastMatch;
        std::advance(startOfThisMatch, diff);

        s.append(endOfLastMatch, startOfThisMatch);
        s.append(f(match));

        auto lengthOfMatch = match.length(0);

        positionOfLastMatch = positionOfThisMatch + lengthOfMatch;

        endOfLastMatch = startOfThisMatch;
        std::advance(endOfLastMatch, lengthOfMatch);
    };

    std::sregex_iterator begin(first, last, re), end;
    std::for_each(begin, end, callback);

    s.append(endOfLastMatch, last);

    return s;
}

template<class Traits, class CharT, class UnaryFunction>
std::string regex_replace(const std::string& s,
    const std::basic_regex<CharT,Traits>& re, UnaryFunction f)
{
    return regex_replace(s.cbegin(), s.cend(), re, f);
}

std::string my_callback(const std::smatch& m) {
  if (m.str(1).length() % 2 == 0) {
    return m.str(1) + "(.+)";
  } else {
    return m.str(0);
  }
}

int main() {
    std::string s = "abcd %1 %%2 %%%3 %%%%4 efgh\n\nabcd%12%%34%%%666%%%%11efgh";
    cout << regex_replace(s, regex("(%*)(\d+)"), my_callback) << endl;

    return 0;
}

参见IDEONE demo

特别感谢回拨代码转到John Martin