【python】wiktionaryの`{{}}`マークアップの整形

# 概要
wiktionaryの記事テキストのうち`{{}}`で囲まれたマークアップ（テンプレート）を整形（実際の記事で書かれた文字列へ変換）しました。

[【python】wiktionaryの見出しのうち日本語、名詞に関するものを抽出する](https://qiita.com/shimajiroxyz/items/0461de8be1ee0ca7dbab)の続きです。このページで作ったファイルから「日本語」の「名詞」に関する情報を抽出するため、該当するテンプレートの調査をしました。

なおテンプレートはユーザが自由に作ることが可能で非常に多くの種類があるため、対象をしぼっています。今回は、前述のページで抽出した日本語の名詞の語義に関する部分のテンプレートを対象としました。

# 背景
[【python】wiktionaryから語義情報を抽出する](https://qiita.com/shimajiroxyz/items/f75b4f7717643d3fbcf0)にて、wiktionaryの語義情報のうち、日本語の名詞に関するものを抽出しました。次のステップとして、マークアップを除去する必要があります。

今回の抽出範囲におけるマークアップとして、主に`{{}}`で囲まれたものと`[[]]`で囲まれたものがあります。このうち本記事では、`{{}}`で囲まれたものの整形を対象とします。

# 方針
wiktionaryやwikipediaで`{{}}`で囲む書き方をテンプレートタグと呼びます。

参考：[Help:テンプレート](https://ja.wikipedia.org/wiki/Help:%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88)

テンプレートタグは以下のように書かれます。

```
{{テンプレート名|引数1|引数2|.....}}
{{テンプレート名|テンプレート変数1=引数1|テンプレート変数2=引数2|.....}}
```

また、実際の記事テキストを目視したところ、両者を混合した以下のような書き方も許容されるようです。

```
{{テンプレート名|引数1|引数2|テンプレート変数3=引数3|テンプレート変数4=引数4|.....}}
```

どの引数が記事テキストの表層（実際に表示される文字）として使われるかはテンプレート名に依存しますので、テンプレート名で処理を場合分けすることにします。テンプレート名が同じでも付属する引数の数が違ったり表層に使われる部分が異なるケースがあるので、それらの差異もうまく吸収できるように書きます。またテンプレート変数が使われる場合と使われない場合の両方に対応できるようにします。

なお、引数が別の文字列に変換されて表層となるケースが多々あります（"jp" -> "日本語"など)が、分岐が増えすぎるものについては、引数をそのまま使うことにします。
また、`{{}}`で囲まれていても、{{PAGENAME}}などテンプレートタグではないものが含まれますが、今回はまとめて処理をします。

# 環境

```
% sw_vers
ProductName:    macOS
ProductVersion: 11.6.1
BuildVersion:   20G224
% python -V
Python 3.9.10
```

# コード

## データの読み込み
[【python】wiktionaryから語義情報を抽出する](https://qiita.com/shimajiroxyz/items/f75b4f7717643d3fbcf0)にて作成した、wiktionaryの語義情報のtsvを読み込みます。
データは単語（word）、言語見出し（head2）、品詞見出し（head3）、語義ID（meaning_id）、語義（meaning）の列から構成されます。

```Python
# ファイルの読み込み
import os
import pandas as pd
# dictionary_meaning_jp.tsvをdfとして読み込む
df = pd.read_csv('dictionary_meaning_jp_noun.tsv', sep='\t')
print(df.head())
```

```
  word     head2                head3  meaning_id  \
0    青    {{ja}}             {{noun}}           0   
1   保護       日本語                   名詞           0   
2   保護       日本語                   名詞           1   
3   今日  {{L|ja}}  {{noun}}・{{adverb}}           0   
4   今日  {{L|ja}}  {{noun}}・{{adverb}}           1   

                                             meaning  
0  #（複合語で）（あお）色の一つ、[[グリーン]]。[[くさき|草木]]の[[は|葉]]のような色。  
1                 #ある物が[[破壊]]されたりしないように[[まもる|守る]]こと。  
2                      #社会的弱者や生活力の低い者に対し[[支援]]を行うこと。  
3  #（[[きょう]]:[[熟字訓]]、古:[[こんにち]]）今過ごしているその[[日]]、この...  
4                              #（こんにち）[[現代]]、[[現在]]。 
```

## テストケースの作成

まずテンプレートタグがあるのか抽出し、眺めてみます。
整形用の関数作成時の分岐処理の参考にするため、いくつかの情報を合わせて出力するようにしています。

* template_name: テンプレート名
* arg_len: 引数の数。この値によって分岐処理の場合分けが必要になる場合があるため。
* eq_keys: テンプレート変数（=の左側の文字列）。この値によって分岐処理の場合分けが必要になる場合があるため必要。
* count: 各テンプレートタグの出現数。分岐処理があまりに多くなりすぎた場合、低頻度タグの分岐処理を諦める判断材料とするため必要。
* content: テンプレートタグの全体（テストデータの入力値）
* word: そのテンプレートタグを含む記事の単語例。テンプレートタグが記事テキストでどのように表記されているかを確認するために必要。
* meaning: テンプレートタグが使用されている文章の全体。単語記事を検索しなくても文章を見ればどのように記事で表示されているか推測できる場合があり、時間の節約になる。

```Python
import re
from collections import Counter
re_bra = re.compile(r"\{\{([^{[]+?)\}\}") # "{"や"["のネストを含めない
contents = []
example = {}
for w,m in zip(df.word,df.meaning):
  res = re_bra.findall(m)
  for r in res:
    # 各テンプレート名、引数の前後のスペースの有無を無視する
    r = [v.strip() for v in r.split("|")]
    # 先頭を大文字小文字の違いを無視する
    if r[0][0].isalpha():
      r[0] = r[0][0].lower() + r[0][1:]
    # =が含まれている引数に対して、=の左側の文字列をキーとして取得する
    eq_keys = [v.split("=")[0].strip() for v in r[1:] if "=" in v]

    # テンプレート名（r[0]）、引数の数、eq_keysのペアでユニークなキーを作成
    k = (r[0],len(r)-1,",".join(eq_keys)) 
    contents.append(k)
    # kのキーに対応するテンプレートタグ、単語、文章全体をexampleに追加
    if k not in example:
      example[k] = ("|".join(r),w,m)

# 各kのキーの要素数を取得
c = Counter(contents)
# kの種類数を表示
print(len(c))
# 表形式で出力
print("template_name","arg_len","eq_keys","count","content","word","meaning",sep="\t")
for (k1,k2,k3),v in sorted(c.items(),key=lambda x:x[0]):
  c,w,m = example[(k1,k2,k3)]
  print(k1,k2,k3,v,c,w,m,sep="\t")
```

```
124
template_name	arg_len	eq_keys	count	content	word	meaning
=	0		1	=	マコレ	# {{wikipedia-s|熱帯アフリカ}}西部および西中央部に[[分布|分布する]]<ref>[[w:en:Rafaël Govaerts|Govaerts, R.]], Harvey, Y., Jessup, L., Pennington, T.D. and Vink, W. (2021). World Checklist of Sapotaceae. Facilitated by the Royal Botanic Gardens, Kew. Published on the Internet; https://wcsp.science.kew.org/namedetail.do?name_id=205758 Retrieved 6 June 2021</ref>[[w:アカテツ科|アカテツ科]]の大[[高木]]。[[学名]]: ''[[species:Tieghemella heckelii|Tieghemella heckelii]]''（[[シノニム]]: ''[[species:Mimusops heckelii|Mimusops heckelii]]''）<ref name="NSY">{{Cite book|和書|editor=熱帯植物研究会 編|chapter=マコレ ''T. heckelii'' P{{scaps|ierre}}（''Dumoria heckelii'' A. C{{scaps|hev}}. {{=}} ''Mimusops heckelii'' H{{scaps|utch}} et D{{scaps|alz}}）|title=熱帯植物要覧|edition=第4版|publisher=養賢堂|year=1996|page=393}} ISBN 4-924395-03-X</ref>。赤褐色の[[木材]]が得られる<ref name="NSY" />。
abbr	0		4	abbr	ソ	#{{abbr}}：[[ソビエト]]の略。[[ソ連]]。
accessdate	1		2	accessdate|2020-12-04	ディル	# {{wikipedia-s|中東}}から[[w:北アフリカ|北アフリカ]]にかけてを[[原産]]とする<ref>POWO (2019). "Plants of the World Online. Facilitated by the Royal Botanic Gardens, Kew. Published on the Internet; http://www.plantsoftheworldonline.org/taxon/urn:lsid:ipni.org:names:837530-1 {{Accessdate|2020-12-04}}</ref>{{wikipedia-s|セリ科}}の[[草本]]。[[学名]]: ''[[species:Anethum graveolens|Anethum graveolens]]''。[[香辛料]]とされる。
...
```

それっぽい出力が得られました。
なお、printで結果を出力していますが、このあと、出力をコピペでエクセル等の表計算ソフトに貼り付けて、content列に対応する正解データを手作業で入力します。出力がダラダラ出るのが嫌な場合は、tsvとして保存するなどでも構いません。

また注意点として、上記コードでは、`{{}}`や`[[]]`がネストされたテンプレートタグを抽出しないように`r"\{\{([^{[]+?)\}\}"`という正規表現を用いています。テンプレートタグは`{{}}`や`[[]]`をネストすることができるのですが、ネストされた場合、テンプレート引数のセパレータ"|"がネストされたタグで使用されている可能性があり、その場合"|"によるスプリットが正しく機能しなくなります。それを防ぐためです。

よって、あるテンプレート名が引数にネストを含む状態でのみ使用されていた場合、上記の抽出では見落とす可能性があります。ネストを含むという特殊な状況でしか使われない時点で、頻度が少なく、無視しても影響は少ない（手動対応可能な数）ことが期待できますが、一応、どのくらいあるのか抽出してみます。

まずネストされたカッコから一番外側のものを外した中身を返す関数を定義します。
[ネストされた括弧と正規表現](https://pc.atsuhiro-me.net/entry/2019/10/07/210842)を少し変更して作りました。

```Python
#参考：https://pc.atsuhiro-me.net/entry/2019/10/07/210842
# 一番外側のカッコを外す。中身の開始位置をあわせて返す
def get_bracket_content(text, bra=["(",")"]):
  result = []
  depth = 0
  f = 0
  start, end = bra[0], bra[1]
  i = 0
  
  while i < len(text):
    if text[i:].startswith(start):
      depth += 1
      if depth == 1:
        f = i+len(start)
      i += len(start)
    elif text[i:].startswith(end):
      depth -= 1
      if depth == 0:
        result.append((f,text[f:i]))
      i += len(end)
    else:
      i += 1
  return result

print(get_bracket_content(r"{{foo|bar}}", ["{","}"]))
print(get_bracket_content(r"{{foo|bar}}", [r"{{",r"}}"]))
print(get_bracket_content(r"{{foo|{{bar}}}}", [r"{{",r"}}"]))
print(get_bracket_content(r"{{foo}}|{{a{{bar}}}}", [r"{{",r"}}"]))
```

```
[(1, '{foo|bar}')]
[(2, 'foo|bar')]
[(2, 'foo|{{bar}}')]
[(2, 'foo'), (10, 'a{{bar}}')]
```

次に引数がネストされたテンプレートタグも含めて抽出し、テンプレート名、引数の数、テンプレート変数使用の有無のペアからなるキーが、先に抽出したものに含まれないもののみを出力します。
引数の数を計算する際に、ネストされた引数の内部で使われるセパレータはカウントに入れないように、事前に変換する処理を入れる必要があることに注意してください。

```Python
import re
from collections import Counter

# ネストを含まないテンプレートタグの抽出
re_bra = re.compile(r"\{\{([^{[]+?)\}\}") # "{"や"["のネストを含めない
no_nested_contents = set()
for m in df.meaning:
  res = re_bra.findall(m)
  for r in res:
    # 各テンプレート名、引数の前後のスペースの有無を無視する
    r = [v.strip() for v in r.split("|")]
    # 先頭を大文字小文字の違いを無視する
    if r[0][0].isalpha():
      r[0] = r[0][0].lower() + r[0][1:]
    # =が含まれている引数に対して、=の左側の文字列をキーとして取得する
    eq_keys = [v.split("=")[0].strip() for v in r[1:] if "=" in v]

    # テンプレート名（r[0]）、引数の数、eq_keysのペアでユニークなキーを作成
    k = (r[0],len(r)-1,",".join(eq_keys)) 
    no_nested_contents.add(k)

print("no_nested_contents",len(no_nested_contents))

contents = []
example = {}
for w,m in zip(df.word,df.meaning):
  res = [t for i,t in get_bracket_content(m,[r"{{",r"}}"])]
  # {{のlevel2のネストの中身のseparatorだけを変換
  for i in range(len(res)):
    r = res[i]
    # level2のネストを取得
    level2tag = get_bracket_content(r,[r"{{",r"}}"])
    # sepratorを変換
    for s,t2 in level2tag:
      r = r[:s] + t2.replace("|","_") + r[s+len(t2):]
    res[i] = r
  
  # [[のlevel2のネストの中身のseparatorだけを変換
  for i in range(len(res)):
    r = res[i]
    # level2のネストを取得
    level2tag = get_bracket_content(r,[r"[[",r"]]"])
    # sepratorを変換
    for s,t2 in level2tag:
      r = r[:s] + t2.replace("|","_") + r[s+len(t2):]
    res[i] = r
  
  for r in res:
    # 各テンプレート名、引数の前後のスペースの有無を無視する
    r = [v.strip() for v in r.split("|")]
    # 先頭を大文字小文字の違いを無視する
    if r[0][0].isalpha():
      r[0] = r[0][0].lower() + r[0][1:]
    # =が含まれている引数に対して、=の左側の文字列をキーとして取得する
    eq_keys = [v.split("=")[0].strip() for v in r[1:] if "=" in v]

    # テンプレート名（r[0]）、引数の数、eq_keysのペアでユニークなキーを作成
    k = (r[0],len(r)-1,",".join(eq_keys))
    # キーがno_nested_contentsに含まれている場合は、処理をスキップ 
    if k in no_nested_contents: continue
    contents.append(k)
    # kのキーに対応するテンプレートタグ、単語、文章全体をexampleに追加
    if k not in example:
      example[k] = ("|".join(r),w,m)

# 各kのキーの要素数を取得
c = Counter(contents)
# kの種類数を表示
print(len(c))
# 表形式で出力
print("template_name","arg_len","eq_keys","count","content","word","meaning",sep="\t")
for (k1,k2,k3),v in sorted(c.items(),key=lambda x:x[0]):
  c,w,m = example[(k1,k2,k3)]
  print(k1,k2,k3,v,c,w,m,sep="\t")
```

```
no_nested_contents 124
9
template_name	arg_len	eq_keys	count	content	word	meaning
cite book	7	editor,title,url,publisher,year,page	2	cite book|和書|editor=[[w:渡辺清彦 (植物学者)_渡辺清彦]] 編|title=南方圏有用植物圖説  第貮編食用植物|url=https://archive.org/details/nanpoykenyuyyoy00wataa/page/673/mode/2up|publisher=[[w:シンガポール植物園_昭南植物園]]|year=1945|page=673	リョウリダケ	# [[バングラデシュ]]から[[台湾]]・{{wikipedia-s|マレー群島区系}}にかけて[[分布|分布する]]<ref>Clayton, W.D., Govaerts, R., Harman, K.T., Williamson, H. & Vorontsova, M. (2020). World Checklist of Poaceae. Facilitated by the Royal Botanic Gardens, Kew. Published on the Internet; https://wcsp.science.kew.org/namedetail.do?name_id=407421 Retrieved 2 November 2020</ref>[[w:イネ科|イネ科]][[w:マチク属|マチク属]]の[[たけ|竹]]の一種。[[学名]]: ''[[species:Dendrocalamus asper|Dendrocalamus asper]]''。[[たけのこ|タケノコ]]の[[葉鞘]]は褐紫色で[[食用]]となる<ref>{{Cite book|和書|editor=[[w:渡辺清彦 (植物学者)|渡辺清彦]] 編|title=南方圏有用植物圖説  第貮編食用植物|url=https://archive.org/details/nanpoykenyuyyoy00wataa/page/673/mode/2up|publisher=[[w:シンガポール植物園|昭南植物園]]|year=1945|page=673}}</ref><ref>{{Cite book|和書|first=E. J . H.|last=コーナー|last2=渡辺|first2=清彦|authorlink1=:en:E. J. H. Corner|authorlink2=渡辺清彦 (植物学者)|title=図説熱帯植物集成|publisher=廣川書店|year=1969|page=965}}</ref>。
cite book	8	chapter,editor,title,edition,publisher,year,page	1	cite book|和書|chapter=アマメシバ ''Sauropus androgynus'' M{{scaps_err}}.|editor=熱帯植物研究会 編|title=熱帯植物要覧|edition=第4版|publisher=養賢堂|year=1996|page=228	アマメシバ	# [[熱帯]]および[[亜熱帯]][[アジア]]に[[分布|分布する]][[w:コミカンソウ科|コミカンソウ科]]（旧{{wikipedia-s|トウダイグサ科}}）の小木の一種。[[学名]]: ''[[species:Breynia androgyna|Breynia androgyna]]''（[[シノニム]]: ''[[species:Sauropus androgynus|Sauropus androgynus]]''）<ref>Govaerts, R. (2020). World Checklist of Phyllanthaceae. Facilitated by the Royal Botanic Gardens, Kew. Published on the Internet; https://wcsp.science.kew.org/namedetail.do?name_id=498532 Retrieved 27 December 2020</ref>。[[は|葉]]は[[にる|煮]]て[[食用]]とすることが[[可能|可能で]]、白-桃色の[[果実]]も[[砂糖漬け]]とされる<ref name="NSY">{{Cite book|和書|chapter=アマメシバ ''Sauropus androgynus'' M{{scaps|err}}.|editor=熱帯植物研究会 編|title=熱帯植物要覧|edition=第4版|publisher=養賢堂|year=1996|page=228}} ISBN 4-924395-03-X</ref>。
cite book	8	editor,chapter,title,edition,publisher,year,pages	2	cite book|和書|editor=熱帯植物研究会|chapter=ミロバランノキ ''T. chebula'' R{{scaps_etz}}.|title=熱帯植物要覧|edition=第4版|publisher=養賢堂|year=1996|pages=366-7	ミロバランノキ	# [[インド]]から{{wikipedia-s|インドシナ}}にかけてを原産とする[[w:シクンシ科|シクンシ科]][[w:モモタマナ属|モモタマナ属]]の[[落葉]][[高木]]の一種。[[学名]]: ''[[species:Terminalia chebula|Terminalia chebula]]''。[[果実]]は[[黄色]][[染料]]や[[くろ|黒]][[インク]]の原料、[[薬用]]となる<ref name="NSY">{{Cite book|和書|editor=熱帯植物研究会|chapter=ミロバランノキ ''T. chebula'' R{{scaps|etz}}.|title=熱帯植物要覧|edition=第4版|publisher=養賢堂|year=1996|pages=366-7}} ISBN 4-924395-03-X</ref>。
cite book	11	last,first,authorlink,chapter,title,url,publisher,year,page,doi	1	cite book|和書|last=金平|first=亮三|authorlink=金平亮三|chapter=一六二 うみそや Buchanania arborescens B{{scaps_l}}.|title=台湾樹木誌|url=https://dl.ndl.go.jp/info:ndljp/pid/956866|publisher=台湾総督府殖産局|year=1917|page=166|doi=10.11501/956866	ウミソヤ	# [[台湾]]南部や[[熱帯]][[アジア]]から[[オーストラリア]]北部にかけてを原産とする<ref>POWO (2019). "Plants of the World Online. Facilitated by the Royal Botanic Gardens, Kew. Published on the Internet; http://www.plantsoftheworldonline.org/taxon/urn:lsid:ipni.org:names:69275-1 Retrieved 17 November 2020."</ref>[[w:ウルシ科|ウルシ科]]の[[常緑]]小[[高木]]の一種。[[学名]]: ''[[species:Buchanania arborescens|Buchanania arborescens]]''<ref name="rk">{{Cite book|和書|last=金平|first=亮三|authorlink=金平亮三|chapter=一六二 うみそや Buchanania arborescens B{{scaps|l}}.|title=台湾樹木誌|url=https://dl.ndl.go.jp/info:ndljp/pid/956866|publisher=台湾総督府殖産局|year=1917|page=166|doi=10.11501/956866}}</ref>。
zHfont	1		1	zHfont|[[埊]]	則天文字	#:（例）「[[圀]]」（[[国]]）、「{{ZHfont|[[埊]]}}」（[[地]]）
ふりがな	4	yomi2,yomilink	1	ふりがな|耳垢|[[みみあか]]|yomi2=じこう|yomilink=n	ねこみみ	# やわらかく[[しめる|湿って]]いる{{ふりがな|耳垢|[[みみあか]]|yomi2=じこう|yomilink=n}}。
同音後	1		5	同音後|[[洗滌]]	洗浄	#（{{同音後|[[洗滌]]}}。「洗滌（せんでき）」が慣用的に「せんじょう」と読まれるようになり、その意味でも用いられるようになった語。）洗って[[きれい]]にすること。
要出典範囲	1	{{context_slang_あかとう_lang	2	要出典範囲|{{context_slang_あかとう_lang=ja}}[[警ら]]車（[[パトロールカー]]）の[[回転灯]]。[[パトランプ]]。	赤灯	#{{要出典範囲|{{context|slang|あかとう|lang=ja}}[[警ら]]車（[[パトロールカー]]）の[[回転灯]]。[[パトランプ]]。}}
要出典範囲	2	date	1	要出典範囲|[[前輪]]・[[後輪]]ともに単輪で動くことから|date=2017年2月	単車	#[[オートバイ]]、[[バイク]]の[[通称]]。{{要出典範囲|[[前輪]]・[[後輪]]ともに単輪で動くことから|date=2017年2月}}。
```

全部で9個ありました。(要出典範囲, 1, {{context_slang_あかとう_lang)はネストされたテンプレートタグで使われている等号に誤って反応しています。これを除いた残り8個をテストケースに加えることにします。

あとは実際の単語記事とテンプレートタグを見比べつつ、各contentに対応する正解データを手動で書いていきます。
正解の作り方は必ずしも一つではないので、想定するアプリケーションに合わせてください。

例えば`context|figuratively|lang=ja`というテンプレートタグの出力は第1引数`figuratively`の日本語表現である`(比喩)`ですが、テンプレート引数langの値によっては別言語の表記になるはずです。また、第1引数にはcomputing, units of measureなど無数の別の単語が入る可能性があります。これらの全てのパターンを網羅した辞書を作ることは難しいので、今回はcontextについては、第一引数の値をほぼそのまま返すという処理にすることにします。

また、テンプレートタグの事例を1つ見ただけだと正解がわからない場合があります。例えば、`wikipedia-s`というテンプレートタグのテスト入力として当初以下を取得していましたが、これだと、第1引数、テンプレート変数w、テンプレート変数labelのいずれにも同じ値が入っていたため、どの引数をどういう優先度で表層系に用いればよいかわかりません。

```
'wikipedia-s|乾溜|w=乾留'
wikipedia-s|だぼはぜ|label=ダボハゼ|w=ダボハゼ
```

そこでwikipedia-sのテンプレートタグのうちテンプレート引数が使われているもの（等号が含まれる引数を1つ以上もつもの）を全て出力してみます。

```Python
import re
re_bra = re.compile(r"\{\{([^{[]+?)\}\}")
contents = []
example = {}
for w,m in zip(df.word,df.meaning):
  res = re_bra.findall(m)
  for r in res:
    if (r.startswith("wikipedia") and len(r.split("|"))>2 ) == False : continue
    r = [v.strip() for v in r.split("|")]   
    if r[0][0].isalpha():
      r[0] = r[0][0].lower() + r[0][1:]
    eq_keys = [v.split("=")[0].strip() for v in r[1:] if "=" in v]

    k = (r[0],len(r),",".join(eq_keys)) 
    contents.append(["|".join(r),w])

for c in contents:
  print("\t".join(c))
```

```
wikipedia-s|乾溜|w=乾留	コークス
wikipedia-s|トゥルカナ湖|label=トゥルカナ湖畔	アウストラロピテクス・アナメンシス
wikipedia-s|だぼはぜ|label=ダボハゼ|w=ダボハゼ	だぼ
wikipedia-s|礪波|w=礪波郡	だぼ
wikipedia-s|かち|label=徒士|w=徒士	出奔
wikipedia-s|唐胡麻|label=トウゴマ|w=トウゴマ	蓖麻子油
```

4行目の`wikipedia-s|礪波|w=礪波郡`と「だぼ」の記事を見比べると、テンプレート引数wの値である「礪波郡」が優先されて記事に表示されていることがわかります。(wikipedia-s, 3, w)のテスト入力は`wikipedia-s|礪波|w=礪波郡`で置き換えることにします。
テンプレート引数wとテンプレート引数labelが両方使われている場合、どちらが優先されるかは不明のまま（wとlabelが同じ値の事例しかないため）ですが、これは、今回の対象範囲では、labelとwのどちらを優先させても結果が変わらないということでもあるので、今回は暫定的にlabelを優先させる処理で書くことにします。

上記のような作業を行いつつ、最終的に以下のテストケース（入力と正解のペア）を得ました。

```Python
testcases = r"""
=	
abbr	略語
accessdate|2020-12-04	
ain	アイヌ語
alternative form of|ja|バッファー	バッファーの別形
ant	対義語
ar	アラビア語
bor|ja|zh|桃源||	zh 桃源からの借用語
cite book|和書|chapter=ちゅうか【中華】|chapterurl=https://kotobank.jp/word/%E4%B8%AD%E8%8F%AF-567212|title=精選版 日本国語大辞典（コトバンク版）|publisher=小学館|accessdate=2021-04-29|via=コトバンク	
cite book|editor=M. Paul Lewis, Gary F. Simons, Charles D. Fennig|year=2015|title=Ethnologue: Languages of Africa and Europe|edition=18th|location=Dallas|publisher=SIL|pages=60, 234, 273	
cite book|和書|editor=熱帯植物研究会 編|chapter=ベン ''P. cochinchinensis''|title=熱帯植物要覧|edition=第4版|publisher=養賢堂|year=1996|page=190	
cite book|和書|last=大野|first=徹|authorlink=大野徹|title=ビルマ(ミャンマー)語辞典|publisher=大学書林|year=2000|page=245	
cite book|和書|last=牧野|first=富太郎|authorlink=牧野富太郎|title=牧野日本植物圖鑑|url=http://www.hokuryukan-ns.co.jp/makino/index.php?no1=P445|publisher=北隆館|year=1940|page=445	
cite book|和書|last=牧野|first=富太郎|authorlink=牧野富太郎|title=牧野日本植物圖鑑|url=http://www.hokuryukan-ns.co.jp/makino/index.php?no1=P471|publisher=北隆館|year=1940|pages=471	
cite book|和書|first=E. J . H.|last=コーナー|last2=渡辺|first2=清彦|authorlink1=:en:E. J. H. Corner|authorlink2=渡辺清彦 (植物学者)|title=図説熱帯植物集成|publisher=廣川書店|year=1969|page=965	
cite journal|和書|last=池田|first=武文|title=キャビテーションとエンボリズム―渇きのシグナル―|year=1998|journal=森林科学|volume=24|page=35|doi=10.11519/jjsk.24.0_35	
cite journal|last=Maurin|first=Olivier|last2=Gere|first2=Jephris|last3=van Der Bank|first3=Michelle|last4=Boatwright|first4=James Stephen|year=2017|title=The inclusion of ''Anogeissus'', ''Buchenavia'' and ''Pteleopsis'' in ''Terminalia'' (Combretaceae: Terminaliinae)|journal=Botanical Journal of the Linnean Society|volume=184|issue=3|page=321<!--|pages=312&ndash;325-->|doi=10.1093/botlinnean/box029	
cite web|url=http://www.ethnologue.com/language/kek|title=''Ethnologue''|accessdate=2015-01-10	
cite web|url=http://www.ktr.mlit.go.jp/road/sinsei/road_sinsei00000018.html|title=特殊車両通行関連用語|publisher=国土交通省関東地方整備局|accessdate=2015-07-19	
comp	複合語
context|figuratively|lang=ja	(figuratively)
context|computing|units of measure|lang=ja	(computing, units of measure)
context|Buddhism|lang=ja|skey=しき	(Buddhism)
context|Buddhism|lang=ja|sort=ひ び	(Buddhism)
context|volleyball|tennis|table tennis|lang=ja	(volleyball, tennis, table tennis)
context|化学物質|biochemistry|lang=ja|sort=かとう	(化学物質, biochemistry)
context|tennis|table tennis|volleyball|badminton|lang=ja	(tennis, table tennis, volleyball, badminton)
context|tennis|volleyball|basketball|football|等|lang=ja	(tennis, volleyball, basketball, football, 等)
drv	派生語
en	英語
etyl|lat|jpn	lat
etym	語源
etym|eng	語源
iPA|iakiau	iakiau
ipa|a	a
ja	日本語
kotobank|船殻	船殻
l|en|large	large
label|ja|astrology	(astrology)
label|ja|astronomy|obsolete	(astronomy, obsolete)
lang|en|a cat	(a cat)
lb|ja|将棋	(将棋)
lb|ja|dated|誤訳	(dated, 誤訳)
lb|ja|古風|derogatory|men's speech	(古風, derogatory, men's speech)
lzh	漢文
m|en|mass communication	mass communication
ojp	古代日本語
pAGENAME	PAGENAME
pi	パーリ語
pron|	発音
q|歴史的用法・まれ	(歴史的用法・まれ)
r:Sobol1994	
ruby|拠|よ	拠
sa	サンスクリット語
scaps|oxb	_oxb 
small|Thunb.	_Thunb. 
supra|2	^2 
syn	類義語
t|en|Dinosauria	Dinosauria
t+|en|steel	steel
t+|en|reinforce|alt=Reinforced	Reinforced
t-|en|ambiguity aversion	ambiguity aversion
unicode|/pa/	/pa/
usage	用法
w|松前藩	松前藩
wagokanji of|こゆき	こゆきの漢字表記
wikipedia-s|西スラヴ語群	西スラヴ語群
wikipedia-s|トゥルカナ湖|label=トゥルカナ湖畔	トゥルカナ湖畔
wikipedia-s|礪波|w=礪波郡	礪波
wikipedia-s|だぼはぜ|label=ダボハゼ|w=ダボハゼ	ダボハゼ
wp|音節文字	
wp|ペット|愛玩動物	
xlink|ルソー	ルソー
xlink|日本史|日本の歴史	日本史
おくりがな|全|て|すべて	全て
おくりがな|見|え|みえ|みえる	見え
おくりがな2|燃|も|える|もえる	燃える
おくりがな2|遊|あそ|び|あそぶ|	遊び
おくりがな3|見|み||立|た|てた|みたてる	見立てた
ふりがな|酒|さけ	酒
ふりがな|後|うし|ろ	後
ふりがな|御命日講|ごめいにちこう|kanalink=n	御命日講
ふりがな|御|ご|yomi2=お	御
ふりがな|耳根|にこん|yomilink=no	耳根
ふりがな|側|がわ|特殊=かわ	側
ふりがな|安息日|あんそくじつ|yomi2=あんそくにち|yomi3=あんそくび|yomilink=n	安息日
サ変|恋	恋する
ジル|感	感じる
ジル|生|じ	生じ
スル|画	画する
スル|熟|し	熟し
ズル|吟	吟ずる
タグ|ja|言語名	(言語名)
タグ|jpn|cat=ヨーロッパの国名	(ヨーロッパの国名)
タグ|jpn|日本語文法|古典日本語文法	(日本語文法, 古典日本語文法)
タグ|jpn|cat=植物|cat2=食品	(植物, 食品)
タグ|ja|cat=麻雀|sort=ひんふ ぴんふ	(麻雀)
タグ|jpn|情報技術|cat2=プログラミング	(情報技術, プログラミング)
タグ|jpn|情報技術|jsort=エムピースリー	(情報技術)
タグ|ja|寿司|label=no	
タグ|jpn|数学|sort=こう	(数学)
タグ|ja|略語|交通|自動車	(略語, 交通, 自動車)
タグ|jpn|生化学|cat2=ビタミン|cat3=栄養学	(生化学, ビタミン, 栄養学)
タグ|ja|統計学|cat2=数学|label=no	
タグ|jpn|会計|経済|jsort=ピーエル	(会計, 経済)
タグ|ja|心理学|言語学|label=認知心理学, 認知言語学	(認知心理学, 認知言語学)
タグ|jpn|植物|cat2=野菜|cat3=季語 春|label=no	
タグ|jpn|バラモン教|ヒンズー教|仏教|label=no	
タグ|jpn|物理学|化学|材料科学|sort=そう|label=物性物理	(物性物理)
全国書誌番号|55008080	
季語|夏	夏の季語
学名|Felis silvestris catus	学名はFelis silvestris catus
学名は|Canis lupus familiaris	学名はCanis lupus familiaris
書誌・大日本国語辞典|4|537|221	
書誌・大辞典（平凡社）|7|492|252	
書誌・大辞林|1	
書誌・大辞林|1|head=けじめ	
書誌・日本語大辞典初版|634	
要出典	
要出典|2016年11月	
要出典範囲|（古）	（古）
要出典範囲|形成力を有する物。|日本語としての「プラスチック」がこれらの意味で使われている用例または出典を求む	形成力を有する物。
送り仮名2|死|し|ぬ|しぬ	死ぬ
送り活2|諫|いさ|め|いさめる	諫め
cite book|和書|editor=[[w:渡辺清彦 (植物学者)_渡辺清彦]] 編|title=南方圏有用植物圖説  第貮編食用植物|url=https://archive.org/details/nanpoykenyuyyoy00wataa/page/673/mode/2up|publisher=[[w:シンガポール植物園_昭南植物園]]|year=1945|page=673	
cite book|和書|chapter=アマメシバ ''Sauropus androgynus'' M{{scaps_err}}.|editor=熱帯植物研究会 編|title=熱帯植物要覧|edition=第4版|publisher=養賢堂|year=1996|page=228	
cite book|和書|editor=熱帯植物研究会|chapter=ミロバランノキ ''T. chebula'' R{{scaps_etz}}.|title=熱帯植物要覧|edition=第4版|publisher=養賢堂|year=1996|pages=366-7	
cite book|和書|last=金平|first=亮三|authorlink=金平亮三|chapter=一六二 うみそや Buchanania arborescens B{{scaps_l}}.|title=台湾樹木誌|url=https://dl.ndl.go.jp/info:ndljp/pid/956866|publisher=台湾総督府殖産局|year=1917|page=166|doi=10.11501/956866	
zHfont|[[埊]]	[[埊]]
ふりがな|耳垢|[[みみあか]]|yomi2=じこう|yomilink=n	耳垢
同音後|[[洗滌]]	「[[洗滌]]」の「同音の漢字による書きかえ」
要出典範囲|[[前輪]]・[[後輪]]ともに単輪で動くことから|date=2017年2月	[[前輪]]・[[後輪]]ともに単輪で動くことから
""".splitlines()[1:]
testcases = [v.split("\t") for v in testcases]
for c in testcases: assert len(c) == 2
#print(testcases)
```

## 整形関数の作成

前節で作ったテストケースを見ながら法則を考えて、関数を定義します。

```Python
def removeMarkUp(text): # {{}}の中身が入力
  result = text.split("|")
  
  template, args = result[0].strip(), [v.strip() for v in result[1:]]
  
  constant_return_value = {
    "=": "",
    "abbr": "略語",
    "accessdate": "",
    "ain": "アイヌ語",
    "ant": "対義語",
    "ar": "アラビア語",
    "cite book": "",
    "cite journal": "",
    "cite web": "",
    "comp": "複合語",
    "drv": "派生語",
    "en": "英語",
    "etym": "語源",
    "ja": "日本語",
    "lzh": "漢文",
    "ojp": "古代日本語",
    "pAGENAME": "PAGENAME",
    "pi": "パーリ語",
    "pron": "発音",
    "r:Sobol1994": "",
    "sa": "サンスクリット語",
    "syn": "類義語",
    "usage": "用法",
    "wp": "",
    "全国書誌番号": "",
    "書誌・大日本国語辞典": "",
    "書誌・大辞典（平凡社）": "",
    "書誌・大辞林": "",
    "書誌・日本語大辞典初版": "",
    "要出典": ""
  }
  # templateでは先頭の大文字小文字を区別しないので小文字に揃える
  if template[0].isalpha():
    template = template[0].lower() + template[1:]
  
  if template in constant_return_value:
    return constant_return_value[template]
  elif template == "alternative form of":
    #assert len(args) == 2
    #ssert "=" not in "|".join(args)
    return "{}の別形".format(args[1])
  elif template == "bor":
    return "{}からの借用語".format(" ".join(args[1:]).strip())
  elif template == "context":
    res = []
    for arg in args:
      if "=" in arg: continue
      res.append(arg)
    return "({})".format(", ".join(res))
  elif template == "etyl":
    return args[0]
  elif template in ["iPA", "ipa"]:
    return args[0]
  elif template == "kotobank":
    #assert len(args) == 1
    #assert "=" not in "|".join(args)
    return args[0]
  elif template == "l":
    return args[1]
  elif template in ["label","lb"]:
    return "({})".format(", ".join(args[1:]))
  elif template == "lang":
    return "({})".format(args[1])
  elif template == "m":
    return args[1]
  elif template == "q":
    return "({})".format(args[0])
  elif template in ["ruby","ふりがな"]:
    return args[0]
  elif template in ["scaps", "small"]:
    #assert len(args) == 1
    #assert "=" not in "|".join(args)
    return "_{} ".format(args[0])
  elif template == "supra":
    #assert len(args) == 1
    #assert "=" not in "|".join(args)
    return "^{} ".format(args[0])
  elif template in ["t","t-"]:
    #args = [arg for arg in args if "=" not in arg]
    return args[1]
  elif template == "t+":
    for arg in args:
      if arg.startswith("alt="):
        return arg.split("=")[1]
    return args[1]
  elif template == "unicode":
    return args[0]
  elif template == "w":
    return args[0]
  elif template == "wagokanji of":
    #assert len(args) == 1
    #assert "=" not in args[0]
    return args[0]+"の漢字表記"
  elif template == "wikipedia-s":
    res = ""
    for arg in args:
      if arg.startswith("w="): continue
      elif arg.startswith("label="): res = arg.split("=")[1]; break
      elif "=" not in arg: res = arg
    return res
  elif template == "xlink":
    #assert "=" not in "".join(args)
    return args[0]
  elif template == "zHfont":
    #assert len(args) == 1
    #assert "=" not in "|".join(args)
    return args[0]
  elif template == "おくりがな":
    return args[0]+args[1]
  elif template in ["おくりがな2", "おくりがな3", "送り仮名2", "送り活2"]:
    #assert len(args)%3 == 1
    #assert "=" not in "|".join(args)
    args = args[:len(args)//3*3]
    return "".join([arg for i,arg in enumerate(args) if i%3!=1])
  elif template in ["サ変", "スル"]:
    #assert len(args)<=2
    #assert "=" not in "|".join(args)
    res = args[0]
    if len(args) == 1: res += "する"
    else: res += args[1]
    return res
  elif template == "ジル":
    #assert len(args) <= 2
    #assert "=" not in "|".join(args)
    res = args[0]
    if len(args) == 1: res += "じる"
    else: res += args[1]
    return res
  elif template == "ズル":
    #assert len(args) <= 2
    #assert "=" not in "|".join(args)
    res = args[0]
    if len(args) == 1: res += "ずる"
    else: res += args[1]
    return res
  elif template == "タグ":
    #assert "=" not in "|".join(args[:2])
    res = []
    for arg in args[1:]:
      if arg.startswith("label="):
        lab = arg.split("label=")[1]
        if lab == "no": return ""
        else: return f"({lab})"
      if arg.startswith("cat") and "=" in arg:
        res.append(arg.split("=")[1])
      elif "=" not in arg:
        res.append(arg)
    return "({})".format(", ".join(res))
  elif template == "季語":
    #assert len(args) == 1
    #assert "=" not in "|".join(args)
    return "{}の季語".format(args[0])
  elif template in ["学名","学名は"]:
    #assert len(args) == 1
    #assert "=" not in "|".join(args)
    return "学名は"+args[0]
  elif template == "要出典範囲":
    args = [arg for arg in args if "=" not in arg]
    #assert len(args) == 1
    return args[0]
  elif template == "同音後":
    #assert len(args) == 1
    #assert "=" not in "|".join(args)
    return "「{}」の「同音の漢字による書きかえ」".format(args[0])
  
text = r"ふりがな|耳垢|[[みみあか]]|yomi2=じこう|yomilink=n"
print(removeMarkUp(text))
```

```
耳垢
```

テストします。

```Python
import unittest
class Test(unittest.TestCase):
  def test(self):
    for v in testcases:
      #print(v)
      self.assertEqual(removeMarkUp(v[0]), v[1], v[0])

# jupyter notebookで実行する場合
unittest.main(argv=['first-arg-is-ignored'], exit=False) 
# scriptで実行する場合
#unittest.main()
```

```
.
----------------------------------------------------------------------
Ran 1 test in 0.013s

OK
<unittest.main.TestProgram at 0x11a1c6a90>
```

# おわりに
以上で、`{{}}`のテンプレートタグを記事中の表記に変換する事ができるようになりました。
変換対象のテンプレートタグ（テンプレート名）の抽出、変換ロジックの考察とテストケース作成、ネストされたテンプレートタグの処理など、一つ一つはできなくはないですが、全部丁寧にやろうとすると、面倒な作業が多かったです。
なおここまでやっても見落としているテンプレート名がある可能性は否定できないですが、それについては、まず整形の処理を最後まで作りきってしまってから`{{`が残っているケースを探して手動で訂正するとよいかと思います。

また、`[[`のマークアップ（役割的にはリンク）の整形も作業として残っていますが、今回とほぼ同様の手順でできるかと思います。気が向いたら記事にします。