Javascript での正規表現一致の n 番目のインスタンスの置換
-
09-06-2019 - |
質問
他のインスタンスに影響を与えることなく、文字列内の一致の単一インスタンスを識別して置換する正規表現関数を作成しようとしています。たとえば、次の文字列があります。
12||34||56
2 番目のパイプセットをアンパサンドに置き換えて、次の文字列を取得したいと思います。
12||34&&56
正規表現関数は、x 個のパイプを処理でき、n 番目のパイプセットを置換できる必要があるため、同じ関数を使用してこれらの置換を行うことができます。
23||45||45||56||67 -> 23&&45||45||56||67
23||34||98||87 -> 23||34||98&&87
パイプで文字列を分割/置換/連結するだけでよいことはわかっていますし、一致させることもできることも知っています。 /\|\|/
結果の配列を反復処理しますが、これを実行できる単一の式を作成できるかどうか知りたいと思っています。これは Javascript 用であるため、次を使用して実行時に正規表現を生成できることに注意してください。 eval()
, ただし、Perl 固有の正規表現命令を使用することはできません。
解決
これが機能するものです:
"23||45||45||56||67".replace(/^((?:[0-9]+\|\|){n})([0-9]+)\|\|/,"$1$2&&")
ここで、n は n 番目のパイプより小さいパイプです (もちろん、n = 0 の場合、最初の部分式は必要ありません)。
これを関数で実行したい場合は、次のようにします。
function pipe_replace(str,n) {
var RE = new RegExp("^((?:[0-9]+\\|\\|){" + (n-1) + "})([0-9]+)\|\|");
return str.replace(RE,"$1$2&&");
}
他のヒント
より汎用的な関数
この質問に遭遇しました。タイトルは非常に一般的ですが、受け入れられた回答は質問の特定の使用例のみを扱います。
より汎用的なソリューションが必要だったので、それを作成し、ここで共有したいと思いました。
使用法
この関数では、次の引数を渡す必要があります。
original
:検索している文字列pattern
:検索する文字列または RegExp のいずれか キャプチャーグループで. 。キャプチャ グループがないと、エラーがスローされます。これは、関数が呼び出すためです。split
元の文字列上で、そして 指定された RegExp にキャプチャ グループが含まれている場合にのみ、結果の配列には一致が含まれます。.n
:検索する順序。たとえば、2 番目の一致が必要な場合は、パスインします。2
replace
:一致を置換する文字列、または一致を取得して置換文字列を返す関数のいずれか。
例
// 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"
コード
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 つだけになるように簡素化できます。
String.prototype.replaceNthMatch = function(pattern, n, replace) {
// Same code as above, replacing "original" with "this"
};
これを行うと、次のように任意の文字列でメソッドを呼び出すことができます。
"foo-bar-foo".replaceNthMatch("foo", 2, "baz"); // "foo-bar-baz"
テストに合格する
以下は、この関数が合格する Jasmine テストです。
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 をサポートする必要がある場合は、頑張ってください。:)
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;
}
});
}