TeX Alchemist Online

TeX のこと,フォントのこと,Mac のこと

帳票生成ツールとしての LaTeX の活用

f:id:doraTeX:20190119114207p:plain

この記事は TeX & LaTeX Advent Calendar 2020 の5日目の記事です。4日目は wtsnjp さんでした。 6日目は kn1cht さんです。


【追記】 本記事を受けて,ZRさんが key-value型の引数指定をもつユーザ命令を(LaTeXレベルで)定義するための bxkvcmd パッケージ を作成してくださいました!これを使えば,本稿の内容をより容易に実現できます。


【目次】

皆さんは LaTeX を何に使われていますか?

  • レポート作成
  • 論文執筆
  • 書籍執筆/編集
  • 教材/テスト作成

 ⋮

といった用途に使われている方が多いと思います。

他に「一般企業の定型業務で LaTeX を活用する方法」の一例として,様々な帳票処理への活用を考えてみましょう。

差し込み印刷

  • 年賀状
  • 案内文書
  • 領収書・見積書・納品書・請求書・督促状
  • アンケート
  • 試験の受験票

 ⋮

といったものを作成するとき,「定型フォーマットの文面に対して,相手ごとにわずかに異なる内容(氏名・住所・顧客ID・品目・金額・受験会場など)を差し込んだ文書を大量生成したい」という状況がよくあります。いわゆる「差し込み印刷」と呼ばれるものです。それを LaTeX でどうやって実現すればよいか,という疑問をよく耳にしますので,いくつかのアイデアを示しておきましょう。

最も単純なアイデア ~マクロの引数に可変要素を並べる~

TeX ソースの概形

最も単純なアイデアは,個人ごとに異なる可変要素をマクロの引数にするという方法です。概形としては,

\documentclass[dvipdfmx,autodetect-engine]{jsarticle}
\pagestyle{empty}% 全体のページ番号出力を抑制

\newcommand\帳票出力A[引数の数]{%
  %<ここに一人分の出力内容を書く>
  %<個人ごとに異なる部分は #1, #2, ... といった引数を代入>
  %<たとえば「拝啓 #1 #2 様」のように>
  \newpage% ⬅ 一人分が終わるごとに紙を改める
}

\begin{document}

\帳票出力A{保毛田}{保毛彦}{ほげた}{ほげひこ}{東京都千代田区}{赤色}
\帳票出力A{保毛村}{保毛太郎}{ほげむら}{ほげたろう}{北海道札幌市}{青色}
%……<以下これを人数分並べる>……

\end{document}

といった方法になります。

Excel による TeX ソース生成

なお,元データからこの形の TeX ソースを生成するには,たとえば元データを Excel で管理しているのであれば,関数を使って加工・結合すると楽でしょう。

たとえば,元データシートが次のようになっていたとします。

f:id:doraTeX:20201204024912p:plain

出力シートの方で,& を使った文字列結合により,Excel で TeX ソースを生成させます。

f:id:doraTeX:20201204131128p:plain

="\帳票出力A{" & 元データ!A2 & "}{" & 元データ!B2 & "}{" & 元データ!C2 & "}{" & 元データ!D2 & "}{" & 元データ!E2 & "}{" & 元データ!F2 & "}"

としておけば,1行目からオートフィルで2行目以降を埋められます。

(なお,本稿の主旨とはずれますが,一般に Excel においては,「ネ申Excel」化を避けて再利用性・機械可読性を高めるため,生データを入力するシート,データを加工するシート,整形して最終出力を得るシートは分けるようにしましょう。)

引数の数が9個を超えるとき ~ xkeyval パッケージの利用~

単純なケースなら上記の方法でカバーできますが,複雑な帳票になってくると,相手ごとに変更したい可変要素の数が9個を超えてしまいます。TeX においては「マクロの引数は最大9個まで」という仕様となっており,これが壁となります。そういうときは,xkeyval パッケージkey-value リストの機能を使うとよいでしょう。

xkeyval パッケージについては,wtsnjp さんの次の記事にまとめられています。

qiita.com

TeX ソースの概形

xkeyval パッケージはとても高機能ですが,今回の用途では,最も単純な使い方しかしません。全体の概形は以下のようになります。

\documentclass[dvipdfmx,autodetect-engine]{jsarticle}
\usepackage{xkeyval}
\pagestyle{empty}

\makeatletter

%% パラメータとなるキー命令名を一括定義
\define@cmdkeys{帳票パラメータ}[]{姓,名,せい,めい,住所,好きなマフラーの色}

\newcommand\帳票出力B[1]{%
  \begingroup
  \setkeys{帳票パラメータ}{#1}% 与えられたパラメータを各命令に格納
  %<ここに一人分の出力内容を書く>
  %<個人ごとに異なる部分は \姓, \名, ... といった命令を代入>
  %<たとえば「拝啓 \姓 \名 様」のように>
  \endgroup
  \newpage% ⬅ 一人分が終わるごとに紙を改める
}

\makeatother

\begin{document}

\帳票出力B{姓=保毛田,名=保毛彦,せい=ほげた,めい=ほげひこ,住所=東京都千代田区,好きなマフラーの色=赤色}
\帳票出力B{姓=保毛村,名=保毛太郎,せい=ほげむら,めい=ほげたろう,住所=北海道札幌市,好きなマフラーの色=青色}
%……<以下これを人数分並べる>……

\end{document}

しくみ

要になるのは

\define@cmdkeys{帳票パラメータ}[]{姓,名,せい,めい,住所,好きなマフラーの色}

の部分です。ここで,キーとなる項目を一括登録しておきます。(なお,[] の部分は定義される命令名の接頭辞をなくすためです。これがないと,帳票の中からパラメータ値を参照するときに \cmdKV@帳票パラメータ@姓 のような長ったらしい名前で参照する必要が出てきてしまいます。)

次に,\帳票出力B の定義において,引数の数は1つとし,その定義の冒頭に

\setkeys{帳票パラメータ}{#1}%

を仕込んでおきます。そして,\帳票出力B を呼び出す側では

\帳票出力B{姓=保毛田,名=保毛彦,せい=ほげた,めい=ほげひこ,住所=東京都千代田区,好きなマフラーの色=赤色}

という key-value 形式で呼び出します。すると,この key-value の列が #1 として \帳票出力 に渡され,それが展開される過程で

\setkeys{帳票パラメータ}{姓=保毛田,名=保毛彦,せい=ほげた,めい=ほげひこ,住所=東京都千代田区,好きなマフラーの色=赤色}

が実行されることになります。\define@cmdkeys での指定に基づき,これは

\def\姓{保毛田}
\def\名{保毛彦}
\def\せ{ほげた}
\def\め{ほげひこ}
……

と同等の動きをします。すなわち,それ以降で \姓 と打つことにより,「key-value リストに与えられた個人の姓」を参照することができます。(なお,\帳票出力B の定義部で個人ごとの内容を \begingroup ~ \endgroup で囲んでいるのは,1つ前の個人の定義内容が次の人に波及する事故を防ぐためです。)

すると,帳票出力定義部では,

拝啓 \姓 \名 様

日頃よりお世話になっております。お客様の好きなマフラーの色は\好きなマフラーの色 と伺っております。

のように打ち込めばよいことになります。

xkeyval パッケージによる key-value 形式を用いた場合,

  • 「引数9個」の壁を越えられる。
  • 順番を気にする必要がない。
  • パラメータの内容が #1, #2 など番号ではなく \姓\名 といった分かりやすい命令名で参照できるので可読性・メンテナンス性が高い。

といったメリットがあります。ただし,パラメータの内容に半角コンマ , が含まれるときは

好きなマフラーの色={赤色,青色}

のように,ブレースで囲んでおく必要があります。いっそのこと,

\帳票出力B{姓={保毛田},名={保毛彦},せい={ほげた},めい={ほげひこ},住所={東京都千代田区},好きなマフラーの色={赤色,青色}}

のように,パラメータの内容は一律にブレースで囲むようにする,という安全な運用もありでしょう。

Excel による TeX ソース生成

この形の TeX ソースを Excel から生成するには,次のようにするとよいでしょう。

まず,元データが以下のようになっていたとします。

f:id:doraTeX:20201204041217p:plain

これをまず,加工シートにおいて,各パラメータごとに key={value} の形に加工します。(ここでは「パラメータの内容は一律にブレースで囲む」運用でゆくことにします。)

f:id:doraTeX:20201204041342p:plain

A1セルの内容をドラッグでオートフィルすれば,他のセルも一斉に key={value} 形式に加工されます。

次に,出力用シートにおいて,個人ごとに1行の \帳票出力 命令を組み上げます。ここは最新の Office 365 や Excel 2019 で追加された TEXTJOIN 関数を使うと便利です。これは,「複数セルの内容を特定の区切り文字を使って結合してくれる関数」です。

f:id:doraTeX:20201204125039p:plain

A1セルには

="\帳票出力B{" & TEXTJOIN(",",FALSE,加工!A1:加工!F1) & "}"

という関数を仕込んであります。TEXTJOIN(",",FALSE,加工!A1:加工!F1) の部分で,「加工シートのA1からF1のセルの内容を "," 区切りで結合した文字列を得る」という処理をしています。(FALSE は「空のセルを無視しない」という指示です。)このA1セルを下にドラッグしてオートフィルすれば,全データに対する key-value 形式の \帳票出力B のソースが一気に得られます。

別解:見かけ上10個以上の引数をとる命令を定義する

key-value 形式ではなく「マクロ引数に可変要素を並べる」方式のままで,引数10個以上に対応させることはできないか,という質問を受けることがあります。「元々引数は9個以内に収まる前提で帳票出力命令を設計していたが,後から要件定義が変わって,可変要素が10個以上になってしまった,今から大幅な設計変更は大変なので,できるだけこれまでの形を活かせないか」といった場合ですね。 つまり,

\帳票出力A{保毛田}{保毛彦}{ほげた}{ほげひこ}{123-4567}{東京都千代田区}{03(1234)5678}{赤色}{ピッチャー}{右利き}{左投げ}{右打ち}{...}...

と,後から可変要素の種類が次々に増えてきてしまい,マクロ引数が破綻した場合です。key-value 形式に作り直すのが筋が良いとは思いますが,既存ソースや Excel ファイルの構造を極力変えたくないとき,応急処置的にこの形に対応することを考えます。

たとえば引数を12個取りたいとしましょう。最初の9個の引数は #1#9 でとれるので,残り10~12個目の引数を \十個目, \十一個目, \十二個目 でとれればそれでよい,としましょう。その場合,とりあえず9個の引数を取ってマクロに格納しておき,そこからあふれた引数は次に読んでまとめてマクロに格納,最後に帳票出力の本体に入る,という小手先の手法で,一応対応可能ではあります。

\documentclass[dvipdfmx,autodetect-engine]{jsarticle}
\pagestyle{empty}
\makeatletter

%% まずは9個の引数を記憶して次へ回す
\newcommand\帳票出力C[9]{%
  \def\一個目{#1}%
  \def\二個目{#2}%
  \def\三個目{#3}%
  \def\四個目{#4}%
  \def\五個目{#5}%
  \def\六個目{#6}%
  \def\七個目{#7}%
  \def\八個目{#8}%
  \def\九個目{#9}%
  %%% はじめの9個の引数の内容を記憶した上で次の引数群を受け取りにかかる
  \帳票出力C@二グループ目
}

%% 次に10~12番目の引数を受け取って帳票出力の本体を開始させる
\newcommand\帳票出力C@二グループ目[3]{%
  \def\十個目{#1}%
  \def\十一個目{#2}%
  \def\十二個目{#3}%
  %%% 残り3個の引数の内容を記憶した上で帳票出力の本体に入る
  \帳票出力C@本体{\一個目}{\二個目}{\三個目}{\四個目}{\五個目}{\六個目}{\七個目}{\八個目}{\九個目}%
}

%% 個人別帳票の出力の本体部
\newcommand\帳票出力C@本体[9]{%
  %<ここに一人分の出力内容を書く>
  % 1~9番目の引数には #1 ~ #9,\一個目 ~ \九個目 のどちらでもアクセスできるが,残り3つは  \十個目 ~ \十二個目 のマクロを通してしかアクセスできない。
  #1 #2様のポジションは#9,利き手は「\十個目」,投げ方は「\十一個目」ですね。
  \newpage% ⬅ 一人分が終わるごとに紙を改める
}

\makeatother

\begin{document}

%%% 使用者側からはあたかも12個の引数を処理できているように見える
\帳票出力C{保毛田}{保毛彦}{ほげた}{ほげひこ}{123-4567}{東京都千代田区}{03(1234)5678}{赤色}{ピッチャー}{右利き}{左投げ}{右打ち}
%……<以下これを人数分並べる>……

\end{document}

※ 厳密に言えば,元の \帳票出力A と比べて,\帳票出力C#1#9 の内容がマクロ展開で1段階分包まれているという点で等価ではありませんが,最終的に文字列に展開される帳票であれば,その差が問題になるケースは少ないでしょう。どうしてもその差が問題になるケースであれば,expl3 の \cs_generate_variant:Nn\帳票出力C@本体 の引数指定を ooooooooo 指定に差し替えたバリアントを作成する手が考えられます(これを \expandafter chain で直書きするととんでもないことになりそう)。

その他のアイデア

このように,「構造を持ったデータの列」を扱いたい場合,

  • expl3 で property list の sequence を使う
  • LuaTeX で Lua のテーブルを使う

といった,より本格的にデータ構造をきちんと反映させる設計も考えられるでしょう。ただ,そこまでゆくと「プログラミング」感が強くなり,「ちょっとした差し込み印刷をしたい😊」という幸せな LaTeX ユーザからは遠くなってしまいそうです。やはり「Excel を用いて key-value 形式のソースを生成して貼る」というあたりが,単調な事務処理の延長としては最も現実的で幸せなラインではないでしょうか。

既存PDFを下敷きにして上から文字を貼り込む

帳票を一から LaTeX で作成するのではなく,「下敷きとなる既存PDFファイルに上から次々に文字を貼り込みたい」というケースもあります。

たとえば,下敷きとして既に次のようなPDFファイルがあったとします。

f:id:doraTeX:20201205012132p:plain

この「受験番号」「氏名」の欄に個別化された値を流し込みたいとしましょう。

考え方

このとき,考え方としては以下のようになります。

  1. 生成する LaTeX 文書の用紙サイズを,下敷きとなるPDFの用紙サイズを同じに設定する。
  2. 下敷きとなるPDFを,ページ中央にピン留めする感じで貼り込む。
  3. その上から LaTeX で文字を適切な位置に貼り込む。
  4. 2., 3. を改ページしながら人数分繰り返す。

となります。

サンプル

既存PDFを下敷きに,個別化された受験票を生成する具体例を示しましょう。

\documentclass[dvipdfmx,autodetect-engine,a5j,papersize]{jsarticle}
\usepackage{xkeyval}
\usepackage{tikz}
\usetikzlibrary{calc}
\usepackage{bxpgfcurpage}% https://gist.github.com/zr-tex8r/ee0998a76f90338c935ec8915c44d3d8

\pagestyle{empty}

\makeatletter

% 下敷き画像をボックスに保存
\newsavebox\templatebox
\setbox\templatebox=\hbox{\includegraphics[pagebox=mediabox]{template.pdf}}

\define@cmdkeys{受験票パラメータ}[]{受験番号,姓,名}

\newcommand\受験票出力[1]{%
  \begingroup
  \setkeys{受験票パラメータ}{#1}% 与えられたパラメータを各命令に格納
  \begin{tikzpicture}[remember picture,overlay]
    \node at (current page.center) {\usebox\templatebox};% 下敷きPDFをページ中央に貼る
    \node[font=\Large\ttfamily,anchor=west] (受験番号欄) at ($(current page.north west) + (4.4cm,-3.9cm)$) {\受験番号}; %%% 受験番号を貼り込む
    \node[font=\Large,anchor=west] at ($(受験番号欄.north west) + (0cm,-1.75cm)$) {\姓\hspace{1zw}\名}; %%% 氏名を貼り込む
  \end{tikzpicture}
  \endgroup
  \newpage% ⬅ 一人分が終わるごとに紙を改める
}

\makeatother

\begin{document}

\受験票出力{受験番号={0001},姓={保毛田},名={保毛彦}}
\受験票出力{受験番号={0002},姓={保毛村},名={保毛太郎}}
\受験票出力{受験番号={0003},姓={保毛山},名={保毛子}}
\受験票出力{受験番号={0004},姓={保毛杉},名={保毛美}}

\end{document}

解説

全体の構成は,前半で述べた \帳票出力B と同じ,xkeyval パッケージの key-value リストを用いた個別化です。

\documentclass[dvipdfmx,autodetect-engine,a5j,papersize]{jsarticle}

今回,下敷きとなるPDFがA5サイズだったので,出力用紙サイズもA5に設定しています。

\usepackage{tikz}
\usetikzlibrary{calc}

後で下敷き画像の配置,受験番号・氏名の貼り込みに TikZ を使います。

\usepackage{bxpgfcurpage}

今回のサンプルではこの行はなくても問題ありませんが,TikZ の (current page) を使いたいときは ZR さんによる bxpgfcurpage パッケージをロードしておくのが安全です。jsclasses の \mag がかかっていたり,pTeX系エンジンでトンボが出力されていたりしても,(current page) が期待通りに機能するようになります。

(なお,bxpgfcurpage パッケージをロードする前提ならば,bxpgfcurpage パッケージが出力用紙サイズ設定もやってくれるので,jsarticle の papersize オプションはなくても大丈夫です。)

\newsavebox\templatebox
\setbox\templatebox=\hbox{\includegraphics[pagebox=mediabox]{template.pdf}}

各ページごとに毎回同じ画像の \includegraphics を発行すると,ページごとに画像ロードがかかり重くなるので,一度だけ下敷き画像をロードして記憶しておき,それを使い回すことにします。採用するバウンディングボックスはPDFの MediaBox を用いています。適宜 CropBox, TrimBox, BleedBox, ArtBox を使用します。

\define@cmdkeys{受験票パラメータ}[]{受験番号,姓,名}
……
\setkeys{受験票パラメータ}{#1}

前半で述べた \帳票出力B と同じ,xkeyval パッケージの key-value リストを用いた個別化の手法です。以下,\受験番号\姓\名 で各パラメータにアクセスできます。

\begin{tikzpicture}[remember picture,overlay]

これにより TikZ の (current page) が使えるようになります。

\node at (current page.center) {\usebox\templatebox};

台紙となるページ中央に下敷き画像の中央を合わせて貼り込みます(注:正しい位置に来るためにコンパイルが2回必要です)。

f:id:doraTeX:20201205110450p:plain

\node[font=\Large\ttfamily,anchor=west] (受験番号欄) at ($(current page.north west) + (4.4cm,-3.9cm)$) {\受験番号};

ページ左上 (current page.north west) からの相対変位を指定して,受験番号を上から貼り込みます。相対変位の数値は目分量で最適な値を探します。($ ... $) というのは TikZ の calc ライブラリの機能です。

この手法は,帳票出力に限らず,LaTeX で紙面上の絶対位置を指定して文字や図を配置したいというときに広く役立ちます。

f:id:doraTeX:20201205110920p:plain

\受験票出力{受験番号={0001},姓={保毛田},名={保毛彦}}
……

key={value} 形式で個別のパラメータを渡し,受験票本体を1枚ずつ出力します。

完成品

今回作成した個別化受験票生成のサンプルを,Overleaf に置いておきました。

www.overleaf.com

というわけで,一般企業での定型事務作業にも LaTeX は色々役立たせることができるのではないでしょうか。退屈なことは LaTeX にやらせてしまいましょう!