言語処理100本ノック-26:強調マークアップの除去

[言語処理100本ノック 2015](http://www.cl.ecei.tohoku.ac.jp/nlp100/)[「第3章: 正規表現」](http://www.cl.ecei.tohoku.ac.jp/nlp100/#ch3)の[26本目「強調マークアップの除去」](http://www.cl.ecei.tohoku.ac.jp/nlp100/#sec26)記録です。
今回から28本目までは正規表現でマークアップ除去をしていきます。今回は**`sub`関数を使った除去(置換)**を学びます。

# 参考リンク

|リンク|備考|
|:--|:--|
|[026.強調マークアップの除去.ipynb]()|回答プログラムのGitHubリンク|
|[素人の言語処理100本ノック:26](https://qiita.com/segavvy/items/f6d0f3d6eee5acc33c58)|多くのソース部分のコピペ元|
|[ゼロから覚えるPython正規表現の基本とTips](https://qiita.com/FukuharaYohei/items/459f27f0d7bbba551af7)|当ノックで学習した内容を整理しました|
|[正規表現 HOWTO](https://docs.python.org/ja/3/howto/regex.html#regex-howto)|Python公式の正規表現How To|
|[re --- 正規表現操作](https://docs.python.org/ja/3/library/re.html)|Python公式のreパッケージ説明|
|[Help:早見表](https://ja.wikipedia.org/wiki/Help:%E6%97%A9%E8%A6%8B%E8%A1%A8)|Wikipediaの代表的なマークアップの早見表|

# 環境

|種類|バージョン|内容|
|:--|:--|:--|
|OS|Ubuntu18.04.01 LTS|仮想で動かしています|
|pyenv|1.2.15|複数Python環境を使うことがあるのでpyenv使っています|
|Python|3.6.9|pyenv上でpython3.6.9を使っています<br>3.7や3.8系を使っていないことに深い理由はありません<br>パッケージはvenvを使って管理しています|

上記環境で、以下のPython追加パッケージを使っています。通常のpipでインストールするだけです。

|種類|バージョン|
|:--|:--|
|pandas|0.25.3|

# [第3章: 正規表現](http://www.cl.ecei.tohoku.ac.jp/nlp100/#ch3)
## 学習内容
> Wikipediaのページのマークアップ記述に正規表現を適用することで，様々な情報・知識を取り出します．
>> 正規表現, JSON, Wikipedia, InfoBox, ウェブサービス

## ノック内容
> Wikipediaの記事を以下のフォーマットで書き出したファイル[jawiki-country.json.gz](http://www.cl.ecei.tohoku.ac.jp/nlp100/data/jawiki-country.json.gz)がある．

> - 1行に1記事の情報がJSON形式で格納される
> - 各行には記事名が"title"キーに，記事本文が"text"キーの辞書オブジェクトに格納され，そのオブジェクトがJSON形式で書き出される
> - ファイル全体はgzipで圧縮される

> 以下の処理を行うプログラムを作成せよ．

### [26. 強調マークアップの除去](http://www.cl.ecei.tohoku.ac.jp/nlp100/#sec26)
> 25の処理時に，テンプレートの値からMediaWikiの強調マークアップ（弱い強調，強調，強い強調のすべて）を除去してテキストに変換せよ（参考: [マークアップ早見表](http://ja.wikipedia.org/wiki/Help:%E6%97%A9%E8%A6%8B%E8%A1%A8)）．

### 課題補足(「強調マークアップ」について)
[Help:早見表](https://ja.wikipedia.org/wiki/Help:%E6%97%A9%E8%A6%8B%E8%A1%A8)によると「強調マークアップ」は以下の3種類です。

|種類|書式|例|
|:-:|:--|:--|
|他との区別（斜体）|シングルクォート2つで囲む|''他との区別''|
|強調（太字）|シングルクォート3つで囲む|'''強調'''|
|斜体と強調|シングルクォート5つで囲む|'''''斜体と強調'''''|

ファイル内の以下の部分を正規表現で抽出します。今回の対象には「強調（太字）」しかないようです。

```text:ファイル内の「強調マークアップ」部分抜粋
"|確立形態4 = 現在の国号「'''グレートブリテン及び北アイルランド連合王国'''」に変更\n
```

# 回答
## 回答プログラム [026.強調マークアップの除去.ipynb]()

```python
from collections import OrderedDict
from pprint import pprint
import re

import pandas as pd

def extract_by_title(title):
    df_wiki = pd.read_json('jawiki-country.json', lines=True)
    return df_wiki[(df_wiki['title'] == title)]['text'].values[0]

wiki_body = extract_by_title('イギリス')

basic = re.search(r'''
                    ^\{\{基礎情報.*?\n  #検索語句(\はエスケープ処理)、非キャプチャ、非貪欲
                    (.*?)              #任意の文字列
                    \}\}               #検索語句(\はエスケープ処理)
                    $                  #文字列の末尾
                    ''', wiki_body, re.MULTILINE+re.VERBOSE+re.DOTALL)

templates = OrderedDict(re.findall(r'''
                          ^\|         # \はエスケープ処理、非キャプチャ
                          (.+?)       # キャプチャ対象(key)、非貪欲
                          \s*         # 空白文字0文字以上
                          =           # 検索語句、非キャプチャ
                          \s*         # 空白文字0文字以上
                          (.+?)       # キャプチャ対象(Value)、非貪欲
                          (?:         # キャプチャ対象外のグループ開始
                            (?=\n\|)  # 改行(\n)+'|'の手前(肯定の先読み)
                          | (?=\n$)   # または、改行(\n)+終端の手前(肯定の先読み)
                          )           # キャプチャ対象外のグループ終了
                         ''', basic.group(1), re.MULTILINE+re.VERBOSE+re.DOTALL))

# マークアップ除去
def remove_markup(string):
    
    # 強調マークアップの除去
    replaced = re.sub(r'''
                       (\'{2,5})   # 2〜5個の'（マークアップの開始）
                       (.*?)       # 任意の1文字以上（対象の文字列）
                       (\1)        # 1番目のキャプチャと同じ（マークアップの終了）
                       ''', r'\2', string, flags=re.MULTILINE+re.VERBOSE)
    return replaced

for i, (key, value) in enumerate(templates.items()):
    replaced = remove_markup(value)
    templates[key] = replaced
    
    # 変わったものを表示
    if value != replaced:
        print(i, key) 
        print('変更前\t', value)
        print('変更後\t', replaced)
        print('----')

pprint(templates)
```

## 回答解説
今回のメインは以下の部分です。[`sub`関数](https://qiita.com/FukuharaYohei/items/459f27f0d7bbba551af7#sub)を使って「強調マークアップ」の除去(置換)をしています。4つ目の引数に`count`があるので`flags`(コンパイルフラグ)を渡すときには名前を明示する必要があります。`count`に気づかずに必死でコンパイルフラグを渡した気になっていて、うまく行かずに30分ほど無駄にしました・・・

```python:
replaced = re.sub(r'''
                   (\'{2,5})   # 2〜5個の'（マークアップの開始）
                   (.*?)       # 任意の1文字以上（対象の文字列）
                   (\1)        # 1番目のキャプチャと同じ（マークアップの終了）
                   ''', r'\2', string, flags=re.MULTILINE+re.VERBOSE)
```

`sub`関数は文字置換をします。引数の順に1. 正規表現パターン、2. 置換後の文字列、3. 置換対象文字列です。

```python
>>> re.sub(r'置換対象', '置換済', '置換対象 対象外 置換対象')
'置換済 対象外 置換済'
```

ちなみに「確立形態4」の値が変更前と後で以下のようになります。

```
41 確立形態4
変更前	 現在の国号「'''グレートブリテン及び北アイルランド連合王国'''」に変更
変更後	 現在の国号「グレートブリテン及び北アイルランド連合王国」に変更
----
```

### 出力結果(実行結果)
プログラム実行すると以下の結果が出力されます。まだ残っているマークアップは27、28本目で除去します。

```bash:出力結果
OrderedDict([('略名', 'イギリス'),
             ('日本語国名', 'グレートブリテン及び北アイルランド連合王国'),
             ('公式国名',
              '{{lang|en|United Kingdom of Great Britain and Northern '
              'Ireland}}<ref>英語以外での正式国名:<br/>\n'
              '*{{lang|gd|An Rìoghachd Aonaichte na Breatainn Mhòr agus Eirinn '
              'mu Thuath}}（[[スコットランド・ゲール語]]）<br/>\n'
              '*{{lang|cy|Teyrnas Gyfunol Prydain Fawr a Gogledd '
              'Iwerddon}}（[[ウェールズ語]]）<br/>\n'
              '*{{lang|ga|Ríocht Aontaithe na Breataine Móire agus Tuaisceart '
              'na hÉireann}}（[[アイルランド語]]）<br/>\n'
              '*{{lang|kw|An Rywvaneth Unys a Vreten Veur hag Iwerdhon '
              'Glédh}}（[[コーンウォール語]]）<br/>\n'
              '*{{lang|sco|Unitit Kinrick o Great Breetain an Northren '
              'Ireland}}（[[スコットランド語]]）<br/>\n'
              '**{{lang|sco|Claught Kängrick o Docht Brätain an Norlin '
              'Airlann}}、{{lang|sco|Unitet Kängdom o Great Brittain an Norlin '
              'Airlann}}（アルスター・スコットランド語）</ref>'),
             ('国旗画像', 'Flag of the United Kingdom.svg'),
             ('国章画像',
              '[[ファイル:Royal Coat of Arms of the United '
              'Kingdom.svg|85px|イギリスの国章]]'),
             ('国章リンク', '（[[イギリスの国章|国章]]）'),
             ('標語', '{{lang|fr|Dieu et mon droit}}<br/>（[[フランス語]]:神と私の権利）'),
             ('国歌', '[[女王陛下万歳|神よ女王陛下を守り給え]]'),
             ('位置画像', 'Location_UK_EU_Europe_001.svg'),
             ('公用語', '[[英語]]（事実上）'),
             ('首都', '[[ロンドン]]'),
             ('最大都市', 'ロンドン'),
             ('元首等肩書', '[[イギリスの君主|女王]]'),
             ('元首等氏名', '[[エリザベス2世]]'),
             ('首相等肩書', '[[イギリスの首相|首相]]'),
             ('首相等氏名', '[[デーヴィッド・キャメロン]]'),
             ('面積順位', '76'),
             ('面積大きさ', '1 E11'),
             ('面積値', '244,820'),
             ('水面積率', '1.3%'),
             ('人口統計年', '2011'),
             ('人口順位', '22'),
             ('人口大きさ', '1 E7'),
             ('人口値',
              '63,181,775<ref>[http://esa.un.org/unpd/wpp/Excel-Data/population.htm '
              'United Nations Department of Economic and Social '
              'Affairs>Population Division>Data>Population>Total '
              'Population]</ref>'),
             ('人口密度値', '246'),
             ('GDP統計年元', '2012'),
             ('GDP値元',
              '1兆5478億<ref '
              'name="imf-statistics-gdp">[http://www.imf.org/external/pubs/ft/weo/2012/02/weodata/weorept.aspx?pr.x=70&pr.y=13&sy=2010&ey=2012&scsm=1&ssd=1&sort=country&ds=.&br=1&c=112&s=NGDP%2CNGDPD%2CPPPGDP%2CPPPPC&grp=0&a= '
              'IMF>Data and Statistics>World Economic Outlook Databases>By '
              'Countrise>United Kingdom]</ref>'),
             ('GDP統計年MER', '2012'),
             ('GDP順位MER', '5'),
             ('GDP値MER', '2兆4337億<ref name="imf-statistics-gdp" />'),
             ('GDP統計年', '2012'),
             ('GDP順位', '6'),
             ('GDP値', '2兆3162億<ref name="imf-statistics-gdp" />'),
             ('GDP/人', '36,727<ref name="imf-statistics-gdp" />'),
             ('建国形態', '建国'),
             ('確立形態1',
              '[[イングランド王国]]／[[スコットランド王国]]<br />（両国とも[[連合法 '
              '(1707年)|1707年連合法]]まで）'),
             ('確立年月日1', '[[927年]]／[[843年]]'),
             ('確立形態2', '[[グレートブリテン王国]]建国<br />（[[連合法 (1707年)|1707年連合法]]）'),
             ('確立年月日2', '[[1707年]]'),
             ('確立形態3',
              '[[グレートブリテン及びアイルランド連合王国]]建国<br />（[[連合法 (1800年)|1800年連合法]]）'),
             ('確立年月日3', '[[1801年]]'),
             ('確立形態4', '現在の国号「グレートブリテン及び北アイルランド連合王国」に変更'),
             ('確立年月日4', '[[1927年]]'),
             ('通貨', '[[スターリング・ポンド|UKポンド]] (&pound;)'),
             ('通貨コード', 'GBP'),
             ('時間帯', '±0'),
             ('夏時間', '+1'),
             ('ISO 3166-1', 'GB / GBR'),
             ('ccTLD', '[[.uk]] / [[.gb]]<ref>使用は.ukに比べ圧倒的少数。</ref>'),
             ('国際電話番号', '44'),
             ('注記', '<references />')])
```