HN 日本語サマリー

← 一覧へ戻る
プログラミング

Pandoc Luaフィルター

Pandoc Lua Filters (pandoc.org)

131 pointsby ankitg1211 コメント

要約

Pandoc 2.0以降では、外部依存なしにLuaでフィルターを作成できるようになりました。これにより、JSON形式のAST(抽象構文木)を介したデータ変換のオーバーヘッドが回避され、パフォーマンスが向上します。Luaフィルターは、要素名をキーとし、その要素を処理する関数を値とするテーブルとして定義され、ドキュメントツリーの走査方法(typewiseまたはtopdown)を選択できます。

全文翻訳

Pandoc Luaフィルターの紹介 Pandocは長い間フィルターをサポートしており、これにより解析フェーズと書き込みフェーズの間でpandocの抽象構文木(AST)を操作できます。従来のpandocフィルターは、pandoc ASTのJSON表現を受け取り、変更されたASTのJSON表現を生成します。これらは任意のプログラミング言語で記述でき、--filterオプションを使用してpandocから呼び出すことができます。従来のフィルターは非常に柔軟ですが、いくつかの欠点があります。第一に、標準出力にJSONを書き込み、標準入力から読み取る(フィルターの両側で2回)際のオーバーヘッドがあります。第二に、フィルターが機能するかどうかは、ユーザーの環境の詳細に依存します。フィルターは、特定のプログラミング言語のインタープリターや、JSON形式でpandoc ASTを操作するためのライブラリが必要になる場合があります。特定のバージョンのpandoc実行可能ファイルを持つ誰でも使用できるフィルターを単純に提供することはできません。 バージョン2.0以降、pandocでは外部依存なしにLuaでフィルターを作成できるようになりました。Luaインタープリター(バージョン5.4)とpandocフィルターを作成するためのLuaライブラリがpandoc実行可能ファイルに組み込まれています。Pandocのデータ型は直接Luaにマーシャリングされ、パイプを介した標準出力へのJSONの書き込みと標準入力からの読み取りのオーバーヘッドを回避します。 以下は、strong強調をsmall capsに変換するLuaフィルターの例です。 return { Strong = function (elem) return pandoc.SmallCaps(elem.content) end, } または同等に function Strong(elem) return pandoc.SmallCaps(elem.content) end これは、ASTをウォークし、Strong要素を見つけたら、同じコンテンツを持つSmallCaps要素に置き換えることを意味します。実行するには、それをファイル(例: smallcaps.lua)に保存し、--lua-filter=smallcaps.luaでpandocを呼び出します。 以下は、pandocマニュアル(MANUAL.txt)をHTMLに変換する際の、コンパイル済みHaskell(smallcaps)とインタープリットPython(smallcaps.py)で書かれた同じJSONフィルターのバージョンの簡単なパフォーマンス比較です。 コマンド 時間 pandoc 1.01秒 pandoc --filter ./smallcaps 1.36秒 pandoc --filter ./smallcaps.py 1.40秒 pandoc --lua-filter ./smallcaps.lua 1.03秒 ご覧のとおり、LuaフィルターはJSONとの間でマーシャリングする際の相当なオーバーヘッドを回避しています。 Luaフィルターの構造 Luaフィルターは、要素名をキーとし、それらの要素に作用する関数を値とするテーブルです。フィルターは個別のファイルに入れられ、--lua-filterコマンドライン引数を介して渡されることが期待されます。例えば、フィルターがcurrent-date.luaというファイルで定義されている場合、次のように適用されます。 pandoc --lua-filter=current-date.lua -f markdown MANUAL.txt --lua-filterオプションは複数回指定できます。Pandocは、コマンドラインに表示される順序ですべてのフィルター(--filterで指定されたJSONフィルターおよび--lua-filterで指定されたLuaフィルターを含む)を適用します。 Pandocは、各Luaファイルがフィルターを返すことを期待しています。フィルタースクリプトによって値が返されない場合、pandocはpandoc要素の名前(例: Str, Para, Meta, または Pandoc)に対応する名前を持つすべてのトップレベル関数を収集することによって、単一のフィルターを生成しようとします。(だから、上記の2つの例は同等なのです。) Luaファイルからフィルターのリストを返すことも可能で、それらは順番に呼び出されます。ウォークメソッドが利用可能になる前は、これが1つのLuaファイルから複数のフィルターを実行する唯一の方法でした。しかし、ウォークメソッドを使用することが推奨されるため、フィルターのリストを返すことは現在推奨されておらず、この機能は将来削除される可能性があります。 各フィルターについて、ドキュメントが走査され、各要素がフィルターにかけられます。フィルターにエントリ(つまり、同じ名前の関数)が含まれている要素は、Lua要素フィルタリング関数に渡されます。言い換えれば、フィルターエントリはドキュメント内の対応する各要素に対して呼び出され、その要素を入力として受け取ります。 フィルター関数の戻り値は、次のいずれかである必要があります。 nil: これは、オブジェクトが変更されないことを意味します。 pandocオブジェクト: これは入力と同じタイプである必要があり、元のオブジェクトを置き換えます。 pandocオブジェクトのリスト: これらは元のオブジェクトを置き換えます。リストは元のオブジェクトの隣接要素とマージされます(元のオブジェクトが属するリストにスプライスされます)。空のリストを返すことはオブジェクトを削除します。 関数の出力は、入力と同じタイプの要素をもたらす必要があります。これは、インライン要素に作用するフィルター関数は、nil、インライン、またはインラインのリストのいずれかを返す必要があり、ブロック要素をフィルタリングする関数は、nil、ブロック、またはブロックのリストのいずれかを返す必要があることを意味します。この条件が違反された場合、Pandocはエラーをスローします。 要素のタイプに一致する関数がない場合、フィルタリングシステムはより一般的なフォールバック関数を探します。InlineとBlockの2つのフォールバック関数がサポートされています。それぞれが対応するタイプの要素に一致します。一致する関数がない要素は変更されません。pandoc要素のリストについては、モジュールドキュメントを参照してください。 要素シーケンス上のフィルター 一部のフィルタリングタスクでは、ドキュメント内の要素の順序を知る必要があります。その場合、一度に1つの要素を検査するだけでは不十分です。ブロックまたはインラインのリストに対するフィルターを定義するために使用できる2つの特別な関数名があります。 Inlines (inlines) フィルターに存在する場合、この関数は、Para(段落)ブロックのコンテンツやImageの説明など、すべてのインライン要素のリストに対して呼び出されます。関数に渡されるinlines引数は、各呼び出しに対してインライン要素のリストになります。 Blocks (blocks) フィルターに存在する場合、この関数は、MetaBlocksメタ要素ブロックのコンテンツ、リストの各アイテム、およびPandocドキュメントのメインコンテンツなど、すべてのブロック要素のリストに対して呼び出されます。関数に渡されるblocks引数は、各呼び出しに対してブロック要素のリストになります。 これらのフィルター関数は特別で、戻り値はnil(リストは変更されない)であるか、正しいタイプのリスト(つまり、入力引数と同じタイプ)である必要があります。このコンテキストでは単一の要素は通常バグを示唆するため、単一の要素を戻り値とすることは許可されていません。例として「通常の引用符の前のスペースを削除する」を参照してください。この機能はpandoc 2.9.2で追加されました。 トラバーサル順序 フィルターのトラバーサル順序は、traverseキーを'topdown'または'typewise'に設定することで選択できます。デフォルトは'typewise'です。 例: local filter = { traverse = 'topdown', -- ... filter functions ... } return filter このサポートはpandoc 2.17で追加されました。それ以前のバージョンではtraverse設定は無視されます。 Typewiseトラバーサル フィルターセット内の要素フィルター関数は、存在しないものをスキップして、固定順序で呼び出されます。Inline要素の関数、Inlinesフィルター関数、Block要素の関数、Blocksフィルター関数、Metaフィルター関数、そして最後にPandocフィルター関数です。手動でフィルターを実行することで、異なる順序を強制することも可能です。例えば、MetaのフィルターをStrのフィルターより前に実行したい場合、次のように記述できます。 function Pandoc(doc) doc = doc:walk { Meta = Meta } -- (1) return doc:walk { Str = Str } -- (2) end Topdownトラバーサル ドキュメントツリーをルートからリーフに向かって深さ優先で、かつ一度にすべてを走査する方が自然な場合があります。例えば、ブロックリスト [Plain [Str "a"], Para [Str "b"]] は、次のフィルター関数をこの順序で試します。Blocks, Plain, Inlines, Str, Para, Inlines, Str。 Topdownトラバーサルは、フィルター関数から第二の値としてfalseを返すことで短縮できます。この場合、返された要素の子要素は処理されません。例えば、脚注の内容の処理を除外したい場合は、次のように記述できます。 traverse = 'topdown' function Note (n) return n, false end グローバル変数 Pandocは、追加データをLuaフィルターに設定することによって渡します。