👨‍💻

#30 正規表現でマッチした文字列を順に置換したい

2024/08/14に公開

はじめに

前回、ネストのあるjsonの文字列の置換についてとりあげましたが、今回はその番外編として、正規表現でマッチした文字列をリストに沿って置換する方法について備忘録としてまとめていきたいと思います。

今回やりたい内容は

  1. 正規表現でマッチした文字列の一部を残し、その他のマッチした部分は削除する
  2. 拡張子を「jpg」から「png」に置換する

です。

使用するjson

前回使用したjsonをベースに、いくつか情報を追加しました。

    {
      "data": {
        "id": {
          "num": "1"
        },
        "group": {
          "name": "ことわざ",
          "folder": {
            "nodes": [
              {
                "id": 1,
                "name": "動物のことわざ",
                "notes": {
                  "totalCount": 2,
                  "nodes": [
                    {
                      "id": 1,
                      "title": "猫に小判/豚に真珠/牛に経文",
                      "content": "01-価値のわからない者<img title=‘cat.jpg’ date=‘2023/04/06’ num='12'>に価値のあるもの<img title=‘koban.jpg’ date=‘2023/04/07’ num='1'>を見せても、無意味だということ<img title=‘pig.jpg’ date=‘2023/08/06’ num='7'>"
                    },
                    {
                      "id": 2,
                      "title": "雪に白鷺/闇夜のカラス",
                      "content": "02-目立たないことのたとえ"
                    }
                  ]
                }
              },
              {
                "id": 2,
                "name": "植物のことわざ",
                "notes": {
                  "totalCount": 3,
                  "nodes": [
                    {
                      "id": 1,
                      "title": "矯めるなら若木のうち(類)鉄は熱いうちに打て",
                      "content": "01-早いうちに矯正しておくことが大切だということ<img title=‘man.jpg’ date=‘2022/11/06’ num='6'>"
                    },
                    {
                      "id": 2,
                      "title": "灯心で竹の根を掘る",
                      "content": "02-やっても無益なことのたとえ"
                    },
                    {
                      "id": 3,
                      "title": "御茶を挽く",
                      "content": "03-仕事<img title=‘tea.jpg’ date=‘2023/09/18’ num='11'>がないこと"
                    }
                  ]
                }
              }
            ]
          }
        }
      }
    }

今回は<img title=‘xxx.jpg’ date=‘yyyy/mm/dd’ num='x'>の内、titleだけを残すようにしていきたいと思います。

上のjsonであれば、「価値のわからない者<img title=‘cat.jpg’ date=‘2023/04/06’ num='12'>に(省略)」を「価値のわからない者<img title='cat.png'>(省略)」に置換する、というイメージです。

使用する関数について

今回ポイントとなる関数は以下の通りです。
それぞれの動きについて、簡単に説明していきます。

re.findall

Pythonの標準ライブラリであるreモジュールを用いることで、正規表現の処理を行うことができます。

その中でもre.findall()は、マッチするすべての部分文字列をリストで返します。

また、正規表現中の特定の表現を()で囲んでグルーピングすると、グルーピング内の正規表現にマッチする各要素の文字列をタプルとするリストが返されます。

re.search

reモジュールのひとつで、文字列すべてを対象として正規表現にマッチするかを検索します。ただし、文字列中に複数のマッチがある場合は、最初のマッチ部分のみを返します。

span()

reモジュールでマッチ部分として返されたマッチオブジェクトに対して使用することで、その文字列を抽出したり、文字列の位置を取得したりすることができます。

span()を用いることで、マッチした文字列の先頭と末尾をタプルで返します。

[x:y]

スライスを用いることによって、リストや文字列、タプルなどの一部を選択して取得することができます。

選択範囲の開始位置をstart、終了位置をstopとした時、[start:stop]と表され、start >= X > stopの範囲が取得されます。

[:stop]で先頭からstopまで、[start:]でstartから末尾までが選択されます。

titleを配列にして取り出す

今回は、前回記事の完成コードに追加する形で処理を行うことにしました。
前回分の置換処理後のcontentが格納されている変数reContentを対象の文字列としています。

    #title名をグルーピングしてリストにする
    imgList = re.findall(<img title=\’([[a-z]*[0-9]*]*).img)\’ date=\’[0-9]{4}/[0-9]{2}/[0-9]{2}\’ num=\’[0-9]*\’>, reContent)
    
    #imgListの中身
    [‘cat’, ‘koban’, ‘pig’]
    []
    [‘man’]
    []
    [‘tea’]

文字列の先頭からマッチするかを調べる

    #文字列の先頭から末尾までで最初にマッチする箇所を検索
    imgPos = re.search(<img title=\’[[a-z]*[0-9]*]*.img)\’ date=\’[0-9]{4}/[0-9]{2}/[0-9]{2}\’ num=\’[0-9]*\’>, reContent)
    
    #imgPosの中身(一部抜粋)
    <re.Match object; span=(9, 57), match="<img title='cat.jpg' date='2023/04/06' num='12'>">

マッチした文字列の前後で切り分けて置換

上のコードのimgPosを用いてマッチした箇所の前後で切り分け、for文でimgTitleの値を入れ込みます。今回の方法ではre.subやreplaceは使いません。

    for imgTitle in imgList
        imgPos = re.search(<img title=\’[[a-z]*[0-9]*]*.img)\’ date=\’[0-9]{4}/[0-9]{2}/[0-9]{2}\’ num=\’[0-9]*\’>, reContent)
    #マッチ箇所の位置を取得
        span = imgPos.span()
    #文字列の先頭からspan[0]まで、span[1]から末尾までで文字列を切り分ける
        reContent = reContent[:span[0]] +<img title=\’’ + imgTitle +.png\’>+ reContent[span[1]:]

    #reContentの中身
        価値のわからない者<img title='cat.png'>に価値のあるもの<img title='koban.png'>を見せても、無意味だということ<img title='pig.png'>
        目立たないことのたとえ
        人も木も若いうちに矯正しておくことが大切だということ<img title='man.png'>
        やっても無益なことのたとえ
        仕事<img title='tea.png'><img title='tea.png'>がないこと

全体のコード

前回の実装を踏まえたコードの内容が以下になります。

    import json
    import re
    
    article  =  open( 'sample.json', 'r' ) 
    data  =  json.load( article )
    
    for  groupCount  in  range(2)
        for  i  in  range(3):
            totalCount  =  data["data"]["group"]["folder"]["nodes"][groupCount]["notes"]["totalCount"]
            if  i  ==  totalCount:
                break
            beforeTitle  =  data["data"]["group"]["folder"]["nodes"][groupCount]["notes"]["nodes"][i]["title"]
            afterTitle  =  beforeTitle.replace('/','OR')
            content  =  data["data"]["group"]["folder"]["nodes"][groupCount]["notes"]["nodes"][i]["content"]
            reContent = re.sub('[0-9]+-', '', content)
    #ここから本記事の内容
                imgList = re.findall(<img title=\’([[a-z]*[0-9]*]*).img)\’ date=\’[0-9]{4}/[0-9]{2}/[0-9]{2}\’ num=\’[0-9]*\’>, reContent)
                for imgTitle in imgList
                    imgPos = re.search(<img title=\’[[a-z]*[0-9]*]*.img)\’ date=\’[0-9]{4}/[0-9]{2}/[0-9]{2}\’ num=\’[0-9]*\’>, reContent)
                    span = imgPos.span()
                    reContent = reContent[:span[0]] +<img title=\’’ + imgTitle +.png\’>+ reContent[span[1]:]
    #ここまで本記事の内容
                f = open(afterTitle, 'w')
                f.write(reContent)
                f.close

おわりに

今回は、reモジュールやマッチオブジェクトを用いた文字列の取得や切り分けによる置換処理について取り上げました。
今回紹介したもの以外にも様々なreモジュールの関数があるので、興味のある方はぜひ調べてみてください。

以上です。閲覧ありがとうございました。

Discussion