![]() |
En toi Pythmeni tes TeXnopoleos
[電脳世界の奥底にて] |
現在のサイトは、10月末を以て終了となります。
http://zrbabbler.sp.land.to/lualatexlua2.html
怯まず Lua で LaTeX してみた
~続・LuaTeX で日本語しない件について~
しつこく Lua で LaTeX してみる
例によって、 ixbase0 パッケージ(参照)の使用を前提とする。
事例 6: その場で LaTeX パッケージをダウンロードして読み込む
\fetchfromwww[<ファイル名>]{<対象URL>}
で
指定の URL から内容(別に LaTeX パッケージでなくてもよい)を取得し、
指定のファイル名(省略時は URL の末尾部分)で保存する。
従って、直後に \usepackage
/\input
を
実行してそれを読み込むことができる。
常に最新版のパッケージを利用することができるのでとっても便利。
(サーバに余計な負荷をかけることになるので、
よい子のみなさんはマネしないように。)
% 文字コードは UTF-8 \documentclass[a4paper]{article} \usepackage{ixbase0} %%------ ダウンローダ \begin{execluacodeblock} local http = require("socket.http") -- URL url の内容を取得してファイル file に書き出す -- (file が nil なら url のパス末尾の名前を採る) -- 戻り値は正常に取得できたかのブール値 function fetch_from_www(url, file) file = file or ({url:find("/([-_.%w]+)$")})[3] if not file then return false end -- 指定URLにGET要求, ここでは200以外のステータスはエラー扱い local res, code = http.request(url) if not res or code ~= 200 then return false end -- 取得した内容をファイルに書き出す local fh = io.open(file, "wb") if not fh then return false end fh:write(res); fh:close() return true end \end{execluacodeblock} % %% \fetchfromwww[<出力ファイル名>]{<対象URL>} \newcommand*\fetchfromwww[2][]{ \renewcommand*\ffwTargetUrl{#2}% \directlua{ local file, url = "\luaescapestring{#1}", "\luaescapestring{#2}" % file が空なら nil に置き換える local res = fetch_from_www(url, (file \?\~= "" and file) or nil) if not res then tex.print("\??\\ffwShowWarning") end }% }% 失敗した時の警告処理も入れておく \newcommand*\ffwTargetUrl{} \newcommand*\ffwShowWarning{% \typeout{Warning: Fetch from WWW failed for some reason.}% \typeout{(URL=\ffwTargetUrl)}} %%------ % 以下では marginnote パッケージを CTAN からダウンロードして読み込んでいる \fetchfromwww{http://www.ring.gr.jp/pub/text/CTAN/macros/latex/contrib/marginnote/marginnote.sty} \usepackage{marginnote} \begin{document} This method is really worth noting. \marginnote{But the example is marginal.} \end{document}

marginnote パッケージは 2 パス処理を行うので、 完全な出力を得るには lualatex を 2 回実行する必要がある。
.dtx
形式で配布されて
いることがほとんどである。
その場合はサーバには .sty
ファイルは置かれていない
のでこの方法は上手くいかない。
事例 7: LaTeX 用の文献データベースを RDBS 上に構築する
要するに、BibTeX で行っている文献データベース管理を MySQL
等の RDBS でやろうという話。
文献データベースファイル(*.bib
)
の部分を RDBS が受け持ち、bibtex コマンドの実行の代わりに
Lua コードが(組版中に)文献リスト(*.bbl
)
を出力するという形式をとる。
ただし、飽くまで実験であるので実装は極力簡単にしていて、
例えば、データベースには、リストに出力する LaTeX テキスト
をそのまま登録するようになっている
(つまり、何らかの「文献クラス」に従って整形するのではない)。
従ってこのままの実装では実用性は非常に乏しい。
ここでは、RDBS として、SQLite3 を用い、また SQLite と Lua の接続に LuaSQL ライブラリを用いる。 SQLite は他のアプリケーションに組み込んで使用することを想定して 作られている軽量なデータベースエンジンである (Lua と類似した開発目的をもっている)。 このため、他の一般的な RDBS のように「サーバ」を介するのではなく、 SQLite のエンジンを組み込んだソフトウェアが直接データベース (ファイルとして保存されている)にアクセスするという方式を採っている。
W32TeX で以下に挙げる例を実行するために必要な準備は次の通り。 W32TeX をインストールしたディレクトリを C:\usr\local とする。
- LuaForge の LuaSQL のページから LuaSQL の Win32 バイナリ版 (私が用いたのは luasql-2.1.1-sqlite3-win32-lua51.zip) をダウンロードし、これを展開して sqlite3.dll を得る。
- この sqlite3.dll を luasql_sqlite3.dll に改名して、 C:\usr\local\bin\lib\lua に置く。 (一般的な Lua でのインストール方法と少し異なる。)
- 実は、この sqlite3.dll には SQLite3 のエンジンが「組み込まれて」 いるので、SQLite3 のデータベースを(SQL で)作成するソフトウェアを既に 持っている人はこれだけで十分である。 そうでない場合は、 SQLite のダウンロードページから 単体アプリケーションの Win32 版バイナリ (私が用いたのは sqlite-3_6_23_1.zip)をダウンロードしてインストールする (中の .exe ファイルを実行パスの通ったディレクトリに置くだけ)。
SQLite で文献データベースを管理するためのライブラリ ixdbbib およびその使用を含む LaTeX 文書 (ファイル名 test.tex)を以下に示す。
% このファイルの文字コードは UTF-8 \documentclass[a4paper]{article} \usepackage{ixbase0} %% ixdbbib ライブラリの Lua コード \begin{execluacodeblock} require "luasql_sqlite3" -- (※1) -- SQL の文字列リテラル(RDBS に適応させる必要あり) local function quote(s) return "'"..s:gsub("'", "''").."'" end -- リスト cites に id を追加する local cites = {}; local used = {} local function citation(id) if not used[id] then used[id] = true; table.insert(cites, id) end end -- 文献データを取得するSQL文 local sql_select = [[ SELECT id, content FROM bib_data WHERE id IN (?) ORDER BY skey ]] -- 文献データベース db_name と cites から -- 文献リスト(thebibliography) を生成し、ファイルに出力 local function bibdata(db_name) assert(lfs.isfile(db_name), "bibliography database '"..db_name.."' not found") -- DB へ接続 local env = assert(luasql.sqlite3()) local con = assert(env:connect(db_name)) local t = {}; local res = {nil, nil} -- (※2) for _, c in ipairs(cites) do table.insert(t, quote(c)) end local sql = sql_select:gsub("?", table.concat(t, ",")) local cur = assert(con:execute(sql)) -- データ読み出し local cnks = { "\\begin{thebibliography}{99}\n" } while cur:fetch(res, "n") do table.insert(cnks, "\\bibitem{"..res[1].."}"..res[2].."\n") end table.insert(cnks, "\\end{thebibliography}\n") cur:close(); con:close() -- ファイルに出力 local fh = assert(io.open(tex.jobname..".bbl", "w")) fh:write(table.concat(cnks)) fh:close() end -- エクスポート ixdbbib = { citation = citation; bibdata = bibdata; } \end{execluacodeblock} %% LaTeX コード \renewcommand\citation[1]{\directlua{ ixdbbib.citation("\luaescapestring{#1}")}} \renewcommand\bibdata[1]{\directlua{ ixdbbib.bibdata("\luaescapestring{#1}")}} \begin{document} % LaTeX 文書での文献参照方法は従来と同じ % (ただし \nocite は未対応) The primary reference is of course the imaginary bible of Lua{\TeX}~\cite{ltbook}. There are however rare and unfortunate cases where you need to consult the fictitious book of the source code~\cite{ltprogram}. %\bibliographystyle{} % 文献スタイルは無意味 \bibliography{sample.db} % データベース名を指定 \end{document}
上の例で読み込むデータベース sample.db を作成するための SQL を以下に掲載する。
sqlite3 -batch sample.db < sample.sql
DROP TABLE IF EXISTS bib_data; CREATE TABLE bib_data ( id VARCHAR(32) PRIMARY KEY, skey TEXT NOT NULL, content TEXT NOT NULL ); INSERT INTO bib_data VALUES ('ltbook', 'luatexteam2013', 'Lua{\TeX} team. \emph{The Lua{\TeX}book}, Luaddison-Wesley, 2013' ); INSERT INTO bib_data VALUES ('ltprogram', 'luatexteam2012', 'Lua{\TeX} team. \emph{Lua{\TeX}: The Program}, Luaddison-Wesley, 2012' );
test.tex と sample.db を同じディレクトリに置いて、そこで
lualatex test
を 3 回実行すれば組版が完了する。
結果は以下の通り。

なお、LuaSQL ライブラリは SQLite3 以外のエンジン、
例えば、Oracle、PostgreSQL、MySQL にも対応している。
他のエンジンを用いる場合は、モジュール名と env:connect
の引数を変えると同時に、quote
関数を適応させる
必要がある。
(MySQL 5.0.67 でも動作を確認した。)
参考: BibTeX を用いる場合は、
latex test
→bibtex test
→latex test
→latex test
のように latex
を(最低)3 回実行する必要があった。
ここで、bibtex
は 1 回目の latex
で出力された test.aux を基にして、文献リストを記したファイル
test.bbl を作成する役割を持っている。
上掲の ixdbbib を用いた文書の場合、
2 回目の lualatex
で test.aux が読み込まれた時に、
SQLite3 のデータベースを読んで test.bbl を作成している
(そしてその lualatex
の実行の中の \bibliography
の箇所で先程の test.bbl が読み込まれる)。
3 回目が必要な理由は、BibTeX 利用の場合と同じで、
相互参照の解決のためである。
参考:
LuaTeX ではファイルの探索を Kpathsearch を介して行っている関係で、
C ライブラリの検索方法が通常の Lua と異なる
(package.cpath
は使われず、
代わりに Kpathsearch の CLUAINPUTS
変数の値が使われる)。
特に、階層化されたモジュール名をディレクトリ階層に対応付ける
(例えば foo.bar
→ $LUA_CPATH/foo/bar.dll
)
ことができない。
そのため、LuaSQL の SQLite3 用モジュールを本来の名前
luasql.sqlite3
で扱うことができず、
変則的な扱いをせざるを得なくなっている。
通常の Lua では、package.cpath
に従って
luasql/sqlite3.dll
を配置し、
test.tex の中の (※1)
の行は
require "luasql.sqlite3"
( _
でなく .
)となる。
(※2)
の行の res
の初期化は
冗長(というより不気味)である。
本来は res = {}
で済むところであるが、
これだと LuaTeX の不具合のために強制終了してしまう。
(本家の Lua 5.1 だと大丈夫だった。)
事例 8: テンプレート的 LaTeX
ここでいうテンプレート処理とは、文書の記述の一部を 「テンプレート言語」の記述に置き換え、 その中にある「変数」を外部にあるデータと結びつけることで、 文書の内容を動的に変化させる技術のことである。
テンプレート
こんにちは、<? if username then ?><?= username ?><? else ?>名無し<? end ?>さん! |
+
|
データ
username = 鷗外 |
↓
|
||
結合した結果の文書
こんにちは、鷗外さん! |
||
ただし、ここで注目するのは、「外部のデータと結合すること」 ではなく、「文書の中にプログラミング言語を埋め込む」 ことである。 よって、外部のデータを参照せずに単独で実行できる テンプレート(もはや「テンプレート(雛形)」でない…) を活用することを考える。 特に、if 文や while 文などの制御構造の中に、コードではなく 「単なるテキスト」が混ぜられるのが面白い。
<? for n = 2, 9 do ?>* <?=n?> は<? if n == 2 or n == 3 then ?>素数<? elseif n % 2 == 0 then ?>合成数<? else ?>もしかしたら素数<? end ?>である。 <? end ?> |
→
|
* 2 は素数である。 * 3 は素数である。 * 4 は合成数である。 * 5 はもしかしたら素数である。 * 6 は合成数である。 * 7 はもしかしたら素数である。 * 8 は合成数である。 * 9 はもしかしたら素数である。 |
ということで、本節では、テンプレートの様式を利用して LaTeX のテキストと Lua のコードをもっと柔軟に混在させる ことを目的とする。 テンプレートエンジンの実装は既存のものを使ってもよかったが、 なるべく単純なものが欲しかったので自分で作ってみた。
- ixsst モジュール(ixsst.lua;2KB) [2010/07/24]
以下のように、テンプレートの作成と出力の機能のみをもつ、 非常に単純なモジュールである。
ixsst.compile(s)
: テンプレート文字列s
を 「テンプレートオブジェクト」に変換する。 テンプレートの文法は以下の通りである。<? ‹コード› ?>
: Lua のコードの断片を埋め込む。<?= ‹式› ?>
: Lua の式を評価して文字列に変換(tostring()
) した結果を出力する。<? ... ?>
の代わりに?
を任意の個数に増やして<??? ... ???>
のように書くことも可能で、この場合「<?
」等を コードに含めることができる。<?= ... ?>
の方も同様である。- それ以外の部分は、改行も含めて全てそのまま出力される。
templ_obj:exec(env)
: テンプレートオブジェクトtempl_obj
の 表すテンプレートに Lua の環境(要するにテーブル)env
を結合して出力した結果の文字列を返す。
今までに挙げたテンプレートの例におけるテンプレート言語も
このモジュールで定めたものである。
プレーンテキストで用いる場合は
<? ... ?>
の外にある改行は全て出力されるので
少し不自然な囲み方をする必要に迫られるが、
LaTeX の中で用いる場合は %
で改行を無効化できるので
この点はほとんど問題にならないだろう。
{ username = "鷗外" }
のようなテーブルを作って、
それを exec()
の引数に渡せばよい。
ただし、この節での目的においては、外部のデータではなく、
いつもの Lua の機能(関数)が見える必要があるので、
exec()
の引数にはグローバル環境 _G
を渡すことになる。
それでは、ixsst モジュールを LaTeX で用いた例を紹介する。 ここでは、1~30 までの整数の平方根と立方根の表を 「小数点の位置で揃えて」出力している。 勿論、2 つの関数値は Lua で計算している。
% このファイルの文字コードは UTF-8 \documentclass[a4paper]{article} \usepackage{ixbase0} \begin{execluacodeblock} -- モジュール読み込み require "ixsst" --- templatex(str): テンプレートを解釈して結果を LaTeX の入力とする -- @param str テンプレート文字列 function templatex(str) ixbase.print(ixsst.compile(str):exec(_G)) end \end{execluacodeblock} \begin{document} % %↓ テンプレート開始の位置にこの文字列を書く \begin{execluacodeblock}templatex[==[ <? -- この中は Lua コード -- 小数点位置で揃えるため、実数表記を 1&.41421356 -- のように2カラム(カラム指定は r@{}l)に分けている。 function dsplit(n) return string.format("%.8f", n):gsub("%.", "&.") end ?> \begin{center}\small\begin{tabular}{r|r@{}l|r@{}l} \hline \multicolumn1{c|}{$x$}& \multicolumn2{c|}{$\sqrt{x}$}& \multicolumn2{c}{$\sqrt[3]{x}$} \\\hline <? for n = 1, 30 do -- Lua の for ループ local s, c = n ^ (1/2), n ^ (1/3) ?>% ← 改行無効化 % Lua の式の値を LaTeX で出力させるため ?= を使う <?= n ?> & <?= dsplit(s) ?> & <?= dsplit(c) ?> \\ <? end ?>% \hline \end{tabular}\end{center} ]==]\end{execluacodeblock} %↑ テンプレート終了の位置にこの文字列を書く \end{document}
組版結果は以下の通り。

上の例では、「テンプレート」の部分を
\begin{execluacodeblock}templatex[==[ (テンプレート) ]==]\end{execluacodeblock}
で囲っている
(これは要するに \directlua{templatex("(テンプレート)")}
を文字列中の Lua や LaTeX の特殊文字が通るように記述したものである)。
もし、この機能を広範的に使うのであれば、
もう少し整った LaTeX パッケージにして、
例えば以下の形式で記述できる方がいいかも知れない。
\documentclass[a4paper]{article} \usepackage{ixbase0} \usepackage{templatex} % コードをパッケージにまとめる \begin{document} % テンプレートを単純に 1 つの環境で表せるようにする \begin{templatex} (テンプレート) \end{templatex} \end{document}
このようなパッケージ templatex を作ることは可能であるが、 TeX 言語の知識を必要とし (verbatim 的な処理を要するので LaTeX マクロでは無理)、 この文書の趣旨に合わないので ここでは前に挙げた形に留めることにした。