Wiktionaryのスクリプトをローカルで動かす


Wiktionary では変化形を Lua のスクリプトで生成します。テンプレートの仕組みをざっと見て、スクリプトをローカルで動かします。

シリーズの記事です。

  1. Wiktionaryの効率的な処理方法を探る
  2. Wiktionaryの処理速度をF#とPythonで比較
  3. Wiktionaryの言語コードを取得
  4. Wiktionaryから特定の言語を抽出
  5. Wiktionaryで英語の不規則動詞を調査
  6. Wiktionaryのスクリプトをローカルで動かす ← この記事

この記事のスクリプトは以下のリポジトリに掲載しています。

テンプレート

MediaWiki にはテンプレートという機能があります。{{名前}} という文字列を テンプレート:名前 というページで指示した内容に置き換えます。指示にはタグなどを組み合わせます。

en-verb

前回の記事で見たように、英語の動詞変化形は {{en-verb}} というテンプレートで作られます。

{{en-verb}} の参照先は Template:en-verb です。

ソース
{{#invoke:en-headword|show|verbs}}<!--

-->{{#if:{{#invoke:ugly hacks|is_valid_page_name|{{{1|valid}}}}}||[[Category:Template with raw link/en-verb]][[Category:Template with raw link/en-verb/1]]}}<!--
-->{{#if:{{#invoke:ugly hacks|is_valid_page_name|{{{2|valid}}}}}||[[Category:Template with raw link/en-verb]][[Category:Template with raw link/en-verb/2]]}}<!--
-->{{#if:{{#invoke:ugly hacks|is_valid_page_name|{{{3|valid}}}}}||[[Category:Template with raw link/en-verb]][[Category:Template with raw link/en-verb/3]]}}<!--
-->{{#if:{{#invoke:ugly hacks|is_valid_page_name|{{{4|valid}}}}}||[[Category:Template with raw link/en-verb]][[Category:Template with raw link/en-verb/4]]}}<!--

--><noinclude>{{documentation}}</noinclude>

この内容をざっくり見ます。

invoke

{{#invoke:en-headword|show|verbs}}

#invoke は MediaWiki の Scribunto という拡張機能で、Lua で記述されたモジュールを呼び出します。

関数の呼び出しを表します。

  • モジュール: en-headword
  • 関数: show
  • 引数: verbs

モジュールは Module:モジュール名 というページに記述された Lua のスクリプトです。

(抜粋)
-- The main entry point.
-- This is the only function that can be invoked from a template.
function export.show(frame)

後でこのスクリプトを実際に動かします。👉スクリプトを動かす

コメント

<!--

-->

ソースの見やすさのため改行を入れる際、表示から除外するためコメントにしているようです。

カテゴリー

{{#if:{{#invoke:ugly hacks|is_valid_page_name|{{{1|valid}}}}}||[[Category:Template with raw link/en-verb]][[Category:Template with raw link/en-verb/1]]}}

ページへの引数をチェックします。引数では変化形が指定され、そのページが存在しなければ Template with raw link/en-verb などのカテゴリーに入れます。変化形も見出しに入れるためにチェックするのが目的のようです。

ページに対する引数は {{{1}}} のように取得します。引数が存在しなければチェックする必要はありませんが、ここでは {{{1|valid}}} と指定することで、引数がなければ valid という存在するページを使ってチェックを通しているようです。4番目の引数まで同じコードが続きます。

ある種のプログラミングですが、独特な手法のため初見ではすぐに意味が分かりませんでした。

日本語版ではこの手法で変化形も生成します。👉日本語版

documentation

<noinclude>{{documentation}}</noinclude>

ブラウザで Template:en-verb を開くと説明が表示されますが、それを埋め込んでいます。

指定は相対パスで、実体は Template:en-verb/documentation です。

<noinclude> が指定されているため、他のページから {{en-verb}} で埋め込むときには無視されます。

スクリプトを動かす

Module:en-headword は読むにはやや長いため、まずは動かしてみます。

依存関係

モジュールは mw.ustring などの MediaWiki のライブラリを使用します。

Wiktionary の別のモジュールも呼び出します。

準備

依存するファイルをダウンロードしたりダンプから取り出すスクリプトを用意しました。

ダンプからの取り出しは、以前の記事で作成したデータベースを使用します。(enwiktionary.db)

実行

Template:en-verb からの Module:en-headword の呼び出しをエミュレートするスクリプトを用意しました。コマンドライン引数を frame に入れてモジュールに渡します。

start = 1
if arg[1] == "-s" then start = 2 end
if not arg[start] then
    print("usage: lua " .. arg[0] .. " [-s] word [args...]")
    return
end
lualib = "mediawiki-extensions-Scribunto/includes/engines/LuaCommon/lualib/"
package.path = lualib .. "?.lua;" .. lualib .. "mw.?.lua;" .. package.path
frame = {
    args = {"verbs"},
    title = word,
    getParent = function()
        args = {}
        for k, v in pairs(arg) do
            if k > start then args[k - start] = v end
        end
        return {args = args}
    end,
    expandTemplate = function(frame, title)
        --print(title.title)
    end,
}
mw = {
    getCurrentFrame = function()
        return frame
    end,
    loadData = require,
    title = {
        getCurrentTitle = function()
            return { text = arg[start], subpageText = arg[start], }
        end,
    },
    text    = require("text"),
    ustring = require("ustring/ustring"),
}
result = require("Module:en-headword").show(frame)
if start > 1 then result = string.gsub(result, "<.->", "") end
print(result)

これを実行すれば、Wiktionary に埋め込まれるデータが取得できます。Lua への最初の引数には原形、それ以降は en-verb の引数を与えます。

  • 例: set {{en-verb|sets|setting|set}}
$ lua en-verb.lua set sets setting set
<strong class="Latn headword" lang="en">set</strong> (<i>third-person singular simple present</i> 
<b class="Latn form-of lang-en 3|s|pres-form-of      " lang="en">[[sets#English|sets]]</b>, 
<i>present participle</i> <b class="Latn form-of lang-en pres|ptcp-form-of      " lang="en">
[[setting#English|setting]]</b>, <i>simple past and past participle</i> 
<b class="Latn form-of lang-en past|and|past|ptcp-form-of      " lang="en">[[set#English|set]]</b>)

ごちゃごちゃしているので、タグを取り除くオプション -s を用意しました。

$ lua en-verb.lua -s set sets setting set
set (third-person singular simple present [[sets#English|sets]], present participle 
[[setting#English|setting]], simple past and past participle [[set#English|set]])

※ 変化形はリンクになっています。説明と区別するため取り除かずに残しました。

例を見ながら色々と試してみると良いでしょう。

最短一致

他の言語の正規表現では * に対する最短一致は *? が一般的ですが、Lua では - です。これによってタグを取り除きます。

if start > 1 then result = string.gsub(result, "<.->", "") end

参考

Lua の仕様はコンパクトで、1つのウェブページに網羅されています。

日本語版

日本語版では Lua のモジュールは使わずに、タグによる条件分岐で変化形を生成します。

<onlyinclude>{{head|en|verb|head={{{head|}}}}}
(<small>三単現: </small>{{#if:{{isValidPageName|{{{1|valid}}}}}|''[[<!--
  -->{{en-verb/getPres3rdSg|{{{1|}}}|{{{2|}}}|{{{3|}}}|{{{4|}}}}}<!--
-->]]''|{{{1|-}}}}},
<small>現在分詞: </small>{{#if:{{isValidPageName|{{{2|valid}}}}}|''[[<!--
  -->{{en-verb/getPresP|{{{1|}}}|{{{2|}}}|{{{3|}}}|{{{4|}}}}}<!--
-->]]''|{{{2|-}}}}}, 
<small>過去形: </small>{{#if:{{isValidPageName|{{{3|valid}}}}}|''[[<!--
  -->{{en-verb/getPast|{{{1|}}}|{{{2|}}}|{{{3|}}}|{{{4|}}}}}<!--
-->]]''|{{{3|-}}}}},
<small>過去分詞: </small>{{#if:{{isValidPageName|{{{4|{{{3|valid}}}}}}}}|''[[<!--
  -->{{en-verb/getPastP|{{{1|}}}|{{{2|}}}|{{{3|}}}|{{{4|}}}}}<!--
-->]]''|{{{4|{{{3|-}}}}}}}} )
<includeonly>[[Category:{{eng}}]][[Category:{{eng}} {{verb}}]]</includeonly></onlyinclude>

このうち三単現の {{en-verb/getPres3rdSg}} だけ見てみます。

<onlyinclude><!--
// 第2/3パラメータが "es"であるとき、"{1}[{2}]es"をかえす
-->{{#ifeq:{{#if:{{{3|}}}|{{{3}}}|{{{2}}}}}|es|{{{1}}}{{#if:{{{3|}}}|{{{2}}}}}es|<!--
  // その他の場合で、第2/3パラメータが "ied" であるとき、"{1}{2}es"をかえす
  -->{{#ifeq:{{{2}}}{{{3}}}|ied|{{{1}}}{{{2}}}es|<!--
      // その他の場合で、第2/3パラメータが"d", "ed", 又は "ing"であるとき、"{PAGENAME}s"を返す。
      -->{{#switch: {{#if:{{{3|}}}|{{{3}}}|{{{2}}}}}|d|ed|ing={{PAGENAME}}s|<!--
        // その他の場合で、3個以上のパラメータがあるとき、{1}を返す。
        -->{{#if:{{{3|}}}|{{{1}}}|<!--
          // その他は{PAGENAME}sを返す。
          -->{{PAGENAME}}s<!--
-->}}}}}}}}</onlyinclude>

処理内容はコメントに書いてある通りですが、慣れないと分かりにくいため、一部だけ説明します。

{{#if:{{{3|}}}|{{{3}}}|{{{2}}}}}

これは三項演算子で書けば exists($3) ? $3 : $2 のような意味です。第3パラメータがあればそれを、なければ第2パラメータを返す式です。null 合体演算子で書けば $3 ?? $2 に相当します。

{{{3|}}}{{{3}}} は引数が存在しないときの挙動が異なるため、使い分けます。{{{3|}}} は引数が存在しないときに else を選ばせるためのイディオムのようです。

記法が独特で取っつきにくいですが、コードは局所化されて規模が小さいので、少し慣れれば Lua のスクリプトよりも追いやすいかもしれません。

感想

今まで Wiktionary で変化形をどう記述しているのかさっぱり分かりませんでした。実際に調べてみると、テンプレートとモジュールを組み合わせた複雑な仕組みを持っていることが分かりました。言語の専門家とプログラマーが共同で作業しないと作り込めないため、個人にはなかなか敷居が高いように感じました。

OmegaWiki

OmegaWiki という多言語辞書プロジェクトは、Wiktionary への不満がきっかけで作られたようです。

The idea of OmegaWiki was born out of frustration with Wiktionary. Many Wiktionary projects worked together in using templates to indicate the non-language specific information. The labels of this information were indicated using templates. It proved useful; much information was copied from one project to another. It became problematic when updates happened. It had to be done manually in all participating projects. Given that the ISO 639-6 recognises in between 7,000 and 8,000 languages, it just does not scale.

DeepL 翻訳に手を加えた訳

OmegaWiki のアイデアは、Wiktionary への不満から生まれました。多くの Wiktionary のプロジェクトは、言語に固有ではない情報を示すためにテンプレートを使っていました。この情報のラベルはテンプレートを使って表示されました。それは役に立ったため、多くの情報がプロジェクトから別のプロジェクトへとコピーされました。しかし更新が行われると問題になります。コピーされたすべてのプロジェクトを手動で更新する必要があります。ISO 639-6 では 7,000 から 8,000 の言語を識別しますが、このやり方はスケールしません。

今回の調査から、何となくこの気持ちが分かる気がします。