Rubyで正規表現の一致部分に対する置換
Rubyで正規表現の一致部分($1、$2、...)に対する置換をしたいのだが、できる方法が見つからない。
やりやいことは、例えば以下のような置換。
<div id="XXX" class="YYY" ~>~ ↓divでclassがYYYの行に対してidとclassの後ろにAAAを追加する。 ↓/<div\s+id=\"(\w+)\"\s+class=\"(YYY)\"/の$1と$2のマッチした部分を置換したい。 <div id="XXX-AAA" class="YYY-AAA" ~>~
正規表現で置換部分が1つだけ、かつ後方参照を使わなければ、以下でできる。
self[regexp, nth] = val
× nthが複数必要な場合には対応できない。
× 後方参照が必要な場合も対応できない。
例えば、文字列で一致した一部をupcaseで置き換える場合はうまくいかない。
# ruby -e "str='abc'; str[/a(b)c/,1]=$1.upcase" -e:1:in `<main>': undefined method `upcase' for nil:NilClass (NoMethodError)
当たり前だけど、先に評価すれば$1が設定されて、置き換えはできる。
# ruby -e "str='abc'; if /a(b)c/===str then; str[/a(b)c/,1]=$1.upcase; end; p str"
"aBc"
すでにやり方は確立されていそうだが、探しても見つからないので、簡単なメソッドを定義して実現してみる。
def replace_matches(str, re, vals) m = re.match(str) if m then (m.size-1).downto(1)do |i| next unless vals[i-1] ofs = m.offset(i) str[ofs[0], ofs[1]-ofs[0]] = eval(vals[i-1]) end end return str end
第1引数`str'は置換対象の文字列。
第2引数`re'は置換するための正規表現(後方参照する部分は重ねてはならない)。
第3引数`vals'は置換文字列の式で以下になる。
- 文字列の配列で指定する。
- 文字列は後でevalで評価するので式を書く。変換しない場合はnil。
- $1、$2、...はevalのときの後方参照で評価されるので、第2引数`re'の後方参照の指定に一致する。
返却値は変換後の文字列。一致しない場合は元の文字列。
require用にrm.rbに保存して実行して意図どおりの結果が出力されることを確認。
# ruby -r "./rm" -e "str='abc'; p replace_matches(str, /a(b)c/, [%Q{$1.upcase}])" "aBc"
複数の場合も問題なし。$3を$1を使って置換するのもOK。
# ruby -r "./rm" -e "str='abc'; p replace_matches(str, /(a)(b)(c)/, [nil,%Q{$2.ord.to_s},%Q{'%02X' % $1.hex}])" "a980A"
なお、後方参照を後方から置換しているので、MatchDataのoffsetがずれない。
- 前方から置換した場合
-
offset => [[0,1],[1,2],[2,3]] abc => abc(変換なし) abc => a98c a98c => a90Ac
- 後方から置換した場合
-
offset => [[0,1],[1,2],[2,3]] abc => ab0A ab0A => a980A a980A => a980A(変換なし)