用Java替换正则表达式匹配的第n个实例

用Java替换正则表达式匹配的第n个实例

Replacing the nth instance of a regex match in Javascript

我正在尝试编写一个正则表达式函数,该函数将识别和替换字符串中匹配项的单个实例,而不影响其他实例。 例如,我有以下字符串:

1
12||34||56

我想用&符替换第二组管道以获取此字符串:

1
12||34&&56

regex函数需要能够处理x数量的管道,并允许我替换第n组管道,因此我可以使用相同的函数进行以下替换:

1
2
3
23||45||45||56||67 -> 23&&45||45||56||67

23||34||98||87 -> 23||34||98&&87

我知道我可以在管道上拆分/替换/合并字符串,而且我也可以在/\\|\\|/上进行匹配并遍历结果数组,但是我很想知道是否可以编写一个 表达式可以做到这一点。 请注意,这将是针对Javascript的,因此可以在运行时使用eval()生成正则表达式,但是无法使用任何Perl特定的正则表达式指令。


更通用的功能

我遇到了这个问题,尽管标题很笼统,但可接受的答案仅处理该问题的特定用例。

我需要一个通用的解决方案,所以我写了一个,并认为我会在这里分享。

用法

此函数要求您向其传递以下参数:

  • original:您要搜索的字符串
  • pattern:要搜索的字符串或带有捕获组的RegExp。没有捕获组,它将引发错误。这是因为该函数在原始字符串上调用split,并且仅当提供的RegExp包含捕获组时,结果数组才会包含匹配项。
  • n:要查找的顺序出现;例如,如果您想要第二场比赛,请传入2
  • replace:要么是替换匹配的字符串,要么是将接受匹配并返回替换字符串的函数。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Pipe examples like the OP's
replaceNthMatch("12||34||56", /(\\|\\|)/, 2, '&&') //"12||34&&56"
replaceNthMatch("23||45||45||56||67", /(\\|\\|)/, 1, '&&') //"23&&45||45||56||67"

// Replace groups of digits
replaceNthMatch("foo-1-bar-23-stuff-45", /(\\d+)/, 3, 'NEW') //"foo-1-bar-23-stuff-NEW"

// Search value can be a string
replaceNthMatch("foo-stuff-foo-stuff-foo","foo", 2, 'bar') //"foo-stuff-bar-stuff-foo"

// No change if there is no match for the search
replaceNthMatch("hello-world","goodbye", 2,"adios") //"hello-world"

// No change if there is no Nth match for the search
replaceNthMatch("foo-1-bar-23-stuff-45", /(\\d+)/, 6, 'NEW') //"foo-1-bar-23-stuff-45"

// Passing in a function to make the replacement
replaceNthMatch("foo-1-bar-23-stuff-45", /(\\d+)/, 2, function(val){
  //increment the given value
  return parseInt(val, 10) + 1;
}); //"foo-1-bar-24-stuff-45"

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
  var replaceNthMatch = function (original, pattern, n, replace) {
    var parts, tempParts;

    if (pattern.constructor === RegExp) {

      // If there's no match, bail
      if (original.search(pattern) === -1) {
        return original;
      }

      // Every other item should be a matched capture group;
      // between will be non-matching portions of the substring
      parts = original.split(pattern);

      // If there was a capture group, index 1 will be
      // an item that matches the RegExp
      if (parts[1].search(pattern) !== 0) {
        throw {name:"ArgumentError", message:"RegExp must have a capture group"};
      }
    } else if (pattern.constructor === String) {
      parts = original.split(pattern);
      // Need every other item to be the matched string
      tempParts = [];

      for (var i=0; i < parts.length; i++) {
        tempParts.push(parts[i]);

        // Insert between, but don't tack one onto the end
        if (i < parts.length - 1) {
          tempParts.push(pattern);
        }
      }
      parts = tempParts;
    }  else {
      throw {name:"ArgumentError", message:"Must provide either a RegExp or String"};
    }

    // Parens are unnecessary, but explicit. :)
    indexOfNthMatch = (n * 2) - 1;

  if (parts[indexOfNthMatch] === undefined) {
    // There IS no Nth match
    return original;
  }

  if (typeof(replace) ==="function") {
    // Call it. After this, we don't need it anymore.
    replace = replace(parts[indexOfNthMatch]);
  }

  // Update our parts array with the new value
  parts[indexOfNthMatch] = replace;

  // Put it back together and return
  return parts.join('');

  }

另一种定义方式

该函数最不吸引人的部分是它需要4个参数。通过将其作为方法添加到String原型,可以简化为仅需要3个参数,如下所示:

1
2
3
String.prototype.replaceNthMatch = function(pattern, n, replace) {
  // Same code as above, replacing"original" with"this"
};

如果这样做,则可以在任何字符串上调用该方法,如下所示:

1
"foo-bar-foo".replaceNthMatch("foo", 2,"baz"); //"foo-bar-baz"

通过考试

以下是此功能通过的Jasmine测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
describe("replaceNthMatch", function() {

  describe("when there is no match", function() {

    it("should return the unmodified original string", function() {
      var str = replaceNthMatch("hello-there", /(\\d+)/, 3, 'NEW');
      expect(str).toEqual("hello-there");
    });

  });

  describe("when there is no Nth match", function() {

    it("should return the unmodified original string", function() {
      var str = replaceNthMatch("blah45stuff68hey", /(\\d+)/, 3, 'NEW');
      expect(str).toEqual("blah45stuff68hey");
    });

  });

  describe("when the search argument is a RegExp", function() {

    describe("when it has a capture group", function () {

      it("should replace correctly when the match is in the middle", function(){
        var str = replaceNthMatch("this_937_thing_38_has_21_numbers", /(\\d+)/, 2, 'NEW');
        expect(str).toEqual("this_937_thing_NEW_has_21_numbers");
      });

      it("should replace correctly when the match is at the beginning", function(){
        var str = replaceNthMatch("123_this_937_thing_38_has_21_numbers", /(\\d+)/, 2, 'NEW');
        expect(str).toEqual("123_this_NEW_thing_38_has_21_numbers");
      });

    });

    describe("when it has no capture group", function() {

      it("should throw an error", function(){
        expect(function(){
          replaceNthMatch("one_1_two_2", /\\d+/, 2, 'NEW');
        }).toThrow('RegExp must have a capture group');
      });

    });


  });

  describe("when the search argument is a string", function() {

    it("should should match and replace correctly", function(){
      var str = replaceNthMatch("blah45stuff68hey", 'stuff', 1, 'NEW');
      expect(str).toEqual("blah45NEW68hey");
    });

  });

  describe("when the replacement argument is a function", function() {

    it("should call it on the Nth match and replace with the return value", function(){

      // Look for the second number surrounded by brackets
      var str = replaceNthMatch("foo[1][2]", /(\\[\\d+\\])/, 2, function(val) {

        // Get the number without the [ and ]
        var number = val.slice(1,-1);

        // Add 1
        number = parseInt(number,10) + 1;

        // Re-format and return
        return '[' + number + ']';
      });
      expect(str).toEqual("foo[1][3]");

    });

  });

});

可能无法在IE7中使用

这段代码在IE7中可能会失败,因为该浏览器使用正则表达式错误地分割了字符串,如此处所述。 [在IE7上握拳)。我相信这是解决方案;如果您需要支持IE7,祝您好运。 :)


这是可行的:

1
"23||45||45||56||67".replace(/^((?:[0-9]+\\|\\|){n})([0-9]+)\\|\\|/,"$1$2&&")

其中n是小于第n个管道的那个(当然,如果n = 0,则不需要该第一个子表达式)

如果您想要一个函数来执行此操作:

1
2
3
4
function pipe_replace(str,n) {
   var RE = new RegExp("^((?:[0-9]+\\\\|\\\\|){" + (n-1) +"})([0-9]+)\\|\\|");
   return str.replace(RE,"$1$2&&");
}

1
2
3
4
5
6
7
8
9
10
11
12
function pipe_replace(str,n) {
    m = 0;
    return str.replace(/\\|\\|/g, function (x) {
        //was n++ should have been m++
        m++;
        if (n==m) {
            return"&&";
        } else {
            return x;
        }
    });
}


推荐阅读

    excel怎么用乘法函数

    excel怎么用乘法函数,乘法,函数,哪个,excel乘法函数怎么用?1、首先用鼠标选中要计算的单元格。2、然后选中单元格后点击左上方工具栏的fx公

    笔记本无法识别鼠标usb设备怎么办

    笔记本无法识别鼠标usb设备怎么办,鼠标,无法识别,本文目录笔记本无法识别鼠标usb设备怎么办usb鼠标连接电脑显示无法识别该设备是怎么回事

    excel中乘法函数是什么?

    excel中乘法函数是什么?,乘法,函数,什么,打开表格,在C1单元格中输入“=A1*B1”乘法公式。以此类推到多个单元。1、A1*B1=C1的Excel乘法公式

    如何肉眼识别山寨U盘

    如何肉眼识别山寨U盘,,核心提示:市面上每种颜色的U盘,大多是非品牌名称,那么如何识别这些仿仿U盘呢让我们分享下面的秘密。1,用肉眼看到的山寨

    标准差excel用什么函数?

    标准差excel用什么函数?,函数,标准,什么,在数据单元格的下方输入l标准差公式函数公式“=STDEVPA(C2:C6)”。按下回车,求出标准公差值。详细

    字符库快捷键|字符串快捷键

    字符库快捷键|字符串快捷键,,1. 字符串快捷键1、单行注释单行注释是 #Mac的快捷键是 command+/windows的快捷键是 Ctrl + /2、多行注

    excel常用函数都有哪些?

    excel常用函数都有哪些?,函数,哪些,常用,1、SUM函数:SUM函数的作用是求和。函数公式为=sum()例如:统计一个单元格区域:=sum(A1:A10)  统计多个