Python
正規表現
言語処理100本ノック

素人の言語処理100本ノック:28

More than 1 year has passed since last update.

言語処理100本ノック 2015の挑戦記録です。環境はUbuntu 16.04 LTS + Python 3.5.2 :: Anaconda 4.1.1 (64-bit)です。過去のノックの一覧はこちらからどうぞ。

第3章: 正規表現

Wikipediaの記事を以下のフォーマットで書き出したファイルjawiki-country.json.gzがある.
・1行に1記事の情報がJSON形式で格納される
・各行には記事名が"title"キーに,記事本文が"text"キーの辞書オブジェクトに格納され,そのオブジェクトがJSON形式で書き出される
・ファイル全体はgzipで圧縮される
以下の処理を行うプログラムを作成せよ.

28. MediaWikiマークアップの除去

27の処理に加えて,テンプレートの値からMediaWikiマークアップを可能な限り除去し,国の基本情報を整形せよ.

出来上がったコード:

main.py
# coding: utf-8
import gzip
import json
import re
fname = 'jawiki-country.json.gz'


def extract_UK():
    '''イギリスに関する記事本文を取得

    戻り値:
    イギリスの記事本文
    '''

    with gzip.open(fname, 'rt') as data_file:
        for line in data_file:
            data_json = json.loads(line)
            if data_json['title'] == 'イギリス':
                return data_json['text']

    raise ValueError('イギリスの記事が見つからない')


def remove_markup(target):
    '''マークアップの除去
    MediaWikiマークアップを可能な限り除去する

    引数:
    target -- 対象の文字列
    戻り値:
    マークアップを除去した文字列
    '''

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

    # 内部リンク、ファイルの除去
    pattern = re.compile(r'''
        \[\[        # '[['(マークアップの開始)
        (?:         # キャプチャ対象外のグループ開始
            [^|]*?  # '|'以外の文字が0文字以上、非貪欲
            \|      # '|'
        )*?         # グループ終了、このグループが0以上出現、非貪欲
        ([^|]*?)    # キャプチャ対象、'|'以外が0文字以上、非貪欲(表示対象の文字列)
        \]\]        # ']]'(マークアップの終了)
        ''', re.MULTILINE + re.VERBOSE)
    target = pattern.sub(r'\1', target)

    # Template:Langの除去        {{lang|言語タグ|文字列}}
    pattern = re.compile(r'''
        \{\{lang    # '{{lang'(マークアップの開始)
        (?:         # キャプチャ対象外のグループ開始
            [^|]*?  # '|'以外の文字が0文字以上、非貪欲
            \|      # '|'
        )*?         # グループ終了、このグループが0以上出現、非貪欲
        ([^|]*?)    # キャプチャ対象、'|'以外が0文字以上、非貪欲(表示対象の文字列)
        \}\}        # '}}'(マークアップの終了)
        ''', re.MULTILINE + re.VERBOSE)
    target = pattern.sub(r'\1', target)

    # 外部リンクの除去  [http://xxxx] 、[http://xxx xxx]
    pattern = re.compile(r'''
        \[http:\/\/ # '[http://'(マークアップの開始)
        (?:         # キャプチャ対象外のグループ開始
            [^\s]*? # 空白以外の文字が0文字以上、非貪欲
            \s      # 空白
        )?          # グループ終了、このグループが0か1出現
        ([^]]*?)    # キャプチャ対象、']'以外が0文字以上、非貪欲(表示対象の文字列)
        \]          # ']'(マークアップの終了)
        ''', re.MULTILINE + re.VERBOSE)
    target = pattern.sub(r'\1', target)

    # <br>、<ref>の除去
    pattern = re.compile(r'''
        <           # '<'(マークアップの開始)
        \/?         # '/'が0か1出現(終了タグの場合は/がある)
        [br|ref]    # 'br'か'ref'
        [^>]*?      # '>'以外が0文字以上、非貪欲
        >           # '>'(マークアップの終了)
        ''', re.MULTILINE + re.VERBOSE)
    target = pattern.sub('', target)

    return target


# 基礎情報テンプレートの抽出条件のコンパイル
pattern = re.compile(r'''
    ^\{\{基礎情報.*?$   # '{{基礎情報'で始まる行
    (.*?)       # キャプチャ対象、任意の0文字以上、非貪欲
    ^\}\}$      # '}}'の行
    ''', re.MULTILINE + re.VERBOSE + re.DOTALL)

# 基礎情報テンプレートの抽出
contents = pattern.findall(extract_UK())

# 抽出結果からのフィールド名と値の抽出条件コンパイル
pattern = re.compile(r'''
    ^\|         # '|'で始まる行
    (.+?)       # キャプチャ対象(フィールド名)、任意の1文字以上、非貪欲
    \s*         # 空白文字0文字以上
    =
    \s*         # 空白文字0文字以上
    (.+?)       # キャプチャ対象(値)、任意の1文字以上、非貪欲
    (?:         # キャプチャ対象外のグループ開始
        (?=\n\|)    # 改行+'|'の手前(肯定の先読み)
        | (?=\n$)   # または、改行+終端の手前(肯定の先読み)
    )           # グループ終了
    ''', re.MULTILINE + re.VERBOSE + re.DOTALL)

# フィールド名と値の抽出
fields = pattern.findall(contents[0])

# 辞書にセット
result = {}
keys_test = []      # 確認用の出現順フィールド名リスト
for field in fields:
    result[field[0]] = remove_markup(field[1])
    keys_test.append(field[0])

# 確認のため表示(確認しやすいようにkeys_testを使ってフィールド名の出現順にソート)
for item in sorted(result.items(),
        key=lambda field: keys_test.index(field[0])):
    print(item)

実行結果:

端末
('略名', 'イギリス')
('日本語国名', 'グレートブリテン及び北アイルランド連合王国')
('公式国名', 'United Kingdom of Great Britain and Northern Ireland英語以外での正式国名:\n*An Rìoghachd Aonaichte na Breatainn Mhòr agus Eirinn mu Thuath(スコットランド・ゲール語)\n*Teyrnas Gyfunol Prydain Fawr a Gogledd Iwerddon(ウェールズ語)\n*Ríocht Aontaithe na Breataine Móire agus Tuaisceart na hÉireann(アイルランド語)\n*An Rywvaneth Unys a Vreten Veur hag Iwerdhon Glédh(コーンウォール語)\n*Unitit Kinrick o Great Breetain an Northren Ireland(スコットランド語)\n**Claught Kängrick o Docht Brätain an Norlin Airlann、Unitet Kängdom o Great Brittain an Norlin Airlann(アルスター・スコットランド語)')
('国旗画像', 'Flag of the United Kingdom.svg')
('国章画像', 'イギリスの国章')
('国章リンク', '(国章)')
('標語', 'Dieu et mon droit(フランス語:神と私の権利)')
('国歌', '神よ女王陛下を守り給え')
('位置画像', 'Location_UK_EU_Europe_001.svg')
('公用語', '英語(事実上)')
('首都', 'ロンドン')
('最大都市', 'ロンドン')
('元首等肩書', '女王')
('元首等氏名', 'エリザベス2世')
('首相等肩書', '首相')
('首相等氏名', 'デーヴィッド・キャメロン')
('面積順位', '76')
('面積大きさ', '1 E11')
('面積値', '244,820')
('水面積率', '1.3%')
('人口統計年', '2011')
('人口順位', '22')
('人口大きさ', '1 E7')
('人口値', '63,181,775United Nations Department of Economic and Social Affairs>Population Division>Data>Population>Total Population')
('人口密度値', '246')
('GDP統計年元', '2012')
('GDP値元', '1兆5478億IMF>Data and Statistics>World Economic Outlook Databases>By Countrise>United Kingdom')
('GDP統計年MER', '2012')
('GDP順位MER', '5')
('GDP値MER', '2兆4337億')
('GDP統計年', '2012')
('GDP順位', '6')
('GDP値', '2兆3162億')
('GDP/人', '36,727')
('建国形態', '建国')
('確立形態1', 'イングランド王国/スコットランド王国(両国とも1707年連合法まで)')
('確立年月日1', '927年/843年')
('確立形態2', 'グレートブリテン王国建国(1707年連合法)')
('確立年月日2', '1707年')
('確立形態3', 'グレートブリテン及びアイルランド連合王国建国(1800年連合法)')
('確立年月日3', '1801年')
('確立形態4', '現在の国号「グレートブリテン及び北アイルランド連合王国」に変更')
('確立年月日4', '1927年')
('通貨', 'UKポンド (&pound;)')
('通貨コード', 'GBP')
('時間帯', '±0')
('夏時間', '+1')
('ISO 3166-1', 'GB / GBR')
('ccTLD', '.uk / .gb使用は.ukに比べ圧倒的少数。')
('国際電話番号', '44')
('注記', '')

ファイルの除去

前問remove_markup()を改修しています。
前問で内部リンクを除去する際、[[]]で括られた範囲に|が0または1個しか出てこないという条件にすることでファイルを巻き込まないようにしていましたが、今回は2個以上も対象にすることで、内部リンクと同時にファイルも除去しています。

Template:Langの除去

Template:Langを見ると{{lang|言語タグ|文字列}}という書式のようですので、この文字列を残すようにしました。

外部リンクの除去

マークアップ早見表外部リンクを見ると、[http://www.example.org 表示文字]の時は表示文字列を残す必要がありそうなので、そのようにしてみました。

その他のマークアップの除去

MediaWikiマークアップを可能な限り除去という問題ですが、ここまでの除去結果を見ると残っているのは<br><ref>だけのようなので、この2つを除くようにしています。

 
29本目のノックは以上です。誤りなどありましたら、ご指摘いただけますと幸いです。


実行結果には、100本ノックで用いるコーパス・データで配布されているデータの一部が含まれます。この第3章で用いているデータのライセンスはクリエイティブ・コモンズ 表示-継承 3.0 非移植日本語訳)です。