Python でタグ付き正規表現を replace する(後方参照を用いたreplace)

たとえば以下のような文字列があったとして、

"hoge **fuga** test piyo"

この **fuga** 部分を <strong>fuga</strong> に置き換えたいとする。

さてどうやる?

アプローチ

まず対象は正規表現と探すとして、問題は 置換後の文字列をどうやって書くか である。ここは「ヒットしたパターンそのもの(ここでは fuga )を引用したい」のではなかろうか。

そういうのを実現するのが タグ付き正規表現 とか 後方参照 とか呼ばれる技術で、要するに () でマッチした部分を \1 とか \2 とかで参照できる機能。正規表現ライブラリはたいていサポートしていて、Python の re もサポートしていたので、使ってみた。

スクリプト

何とかできた。 match オブジェクト との格闘だったかな。

import re

def get_all_taginfo(original_string, re_pattern):
    compiled_obj = re.compile(re_pattern)
    iters = compiled_obj.finditer(original_string)

    all_taginfo = []

    for matchobj in iters:
        matched_string = None
        tags = []

        total = len(matchobj.groups())

        startpos, endpos = matchobj.span()
        matched_string = original_string[startpos:endpos]

        for idx in range(total):
            tag = matchobj.groups()[idx]
            tags.append(tag)

        taginfo = {
            'matched_string' : matched_string,
            'tags'           : tags,
        }
        all_taginfo.append(taginfo)

    return all_taginfo

def replace_bold_pattern(original_string):
    pattern = r'\*\*([^\*]+)\*\*'

    all_taginfo = get_all_taginfo(original_string, pattern)

    replaced_string = original_string

    for taginfo in all_taginfo:
        tag1 = taginfo['tags'][0]

        before = taginfo['matched_string']
        after = '<strong>{:}</strong>'.format(tag1)

        replaced_string = replaced_string.replace(before, after)

    return replaced_string

def replace_all(original_string):
    replaced_string = original_string
    replaced_string = replace_bold_pattern(replaced_string)
    return replaced_string

original = "hoge **fuga** test piyo"
after = replace_all(original)
print(after) # hoge <strong>fuga</strong> test piyo

スクリプト要約

まずは finditer() でマッチオブジェクトを全部集めてくる。

置換前文字列はマッチオブジェクトの span() を使えば取れる。

置換後文字列はマッチオブジェクトの groups() から後方参照を全部取ってきた後、適当に整形すればいい。