LaTeX
TeX

xparse パッケージでスゴイ LaTeX マクロを作ろう!

More than 1 year has passed since last update.

これは「TeX & LaTeX Advent Caleandar 2014」の1日目の記事です。
(2日目は k16shikano さん です。)

昨年のアドベントカレンダーでは、「完全攻略! LaTeXのマクロ定義」と題して、「LaTeX のマクロ(ユーザ定義命令)」を作る命令(\newcommand 等)の使い方を紹介しました。そこで述べられているように、LaTeX 標準での命令定義機能には割と大きな制約があります。

  • 複数の引数をとる命令の場合、最初の1つしかオプション引数([...] で表される、省略可能な引数)にできない。
  • ユーザ定義命令の引数が「段落をまたぐ」ことの可否を選べるが、全部の引数について一律に許可する(\newcommand)か、あるいは一律に禁止する(\newcommand*)かの選択しかなく、個別の引数ごとに選ぶことができない。
  • LaTeX の慣習では、ある命令の“変種”を命令の制御綴の後に * を置いたもので表す。(例えば \hspace 命令に対する \hspace*。)しかし、\newcommand 等ではこういう命令を定義することができない。
  • LaTeX の慣習では、必須の引数は {...}、オプション引数は [...] で表すのが原則である。しかし例外的に「変な括弧」が用いられること(例えば \framebox(1,1){...} や Beamer の \only<2>{...} 等)がある。LaTeX 標準の命令定義機能はこれに対応できない。

これらの制限を全て取り払って「極めて自由な書式をもつユーザ命令を定義可能にする」機能を提供するのが、“LaTeX3 チーム”が開発した xparse パッケージです。本記事では、この xparse パッケージの基本的な利用方法を解説します。

※なお、“LaTeX3 チーム”とは文字通り“LaTeX3”(次世代の LaTeX)を開発するチームのことであって、xparse パッケージも LaTeX3 と多少の繋がりがあるのですが、ここではそれには触れません。少なくとも今ある xparse パッケージはごく普通の「LaTeX2e のパッケージ」です。

:sushi: TeX 言語(LaTeX でなくて)のプログラマ向けの補足的な情報を、:sushi: を付けて示すことにします。

基本編

パッケージの読込

普通に \usepackage 命令で読み込みます。パッケージオプションはありません。

\usepackage{xparse}

以下で示す例では、xparse パッケージ(と \color 命令のために color パッケージ)が読み込まれていることを前提にします。

新たに命令を定義する

すなわち、標準の \newcommand に相当するものです。xparse では、\NewDocumentCommand という命令を使います。

\NewDocumentCommand\命令名{引数仕様}{定義本体}

これを従来の \newcommand と比較してみましょう。

\newcommand[*]{\命令名}[引数の個数]{定義本体}

\NewDocumentCommand では非常に複雑な「引数の書式」、例えば「引数は5個あって、そのうち2・4番目はオプションで、最初の3つは段落をまたがない」のようなものが許容されます。そのため、単なる「引数の個数」だけでは済まなくなり、代わりに「引数仕様」(argument specification)という文字列を定義時に指定する必要があります。この「引数仕様」は少し複雑でとっつきにくいものですが、xparse を使う上で不可欠なものなので、取りあえずは必要なものから確実に理解していくことにしましょう。

まずは、一番簡単な、引数の無い場合です。赤い大きな文字で「アレ」と出力する命令 \myAre を定義してみましょう。この場合、「引数仕様」には空の文字列を指定します。なお、引数仕様は必須引数なので決して省略できないことに注意して下さい。

% \myAre: 赤い大きな文字で"アレ"と出力する.
\NewDocumentCommand\myAre{}{{\color{red}\Large アレ}}
% (参考: \newcommand では以下の通り.)
%\newcommand{\myAre}{{\color{red}\Large アレ}}
% 使ってみる
{\TeX}{\myAre}です。

xparse-1.png

次に、必須の引数のみをもつ命令を定義しましょう。例として、2つの必須引数をもつ命令 \myAlert を定義してみます。\newcommand の場合は引数の個数を表す [2] を付けるのでした。これに対して、\NewDocumentCommand の場合は、引数仕様として、文字 m(<mandatory)を2つ並べた文字列 m m を指定します(空白は任意)。

% \myAlert{色名}{テキスト} : 指定の色を用いて大きな文字で出力する.
\NewDocumentCommand\myAlert{m m}{{\color{#1}\Large #2}}
% (参考: \newcommand では以下の通り.)
%\newcommand*{\myAlert}[2]{{\color{#1}\Large #2}}
% 使ってみる
{\TeX}\myAlert{red}{アレ}です。

xparse-1.png

既存の命令の定義を変更する、など

\NewDocumentCommand 命令は \newcommand に相当するものなので、当該の名前の命令が既に定義済の場合はエラーが発生します。\NewDocumentCommand は「LaTeX3 形式のエラー通知機能」を使用するので次のようなド派手なエラーメッセージを表示しますが、要するに「定義済だからエラー」ということです。

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
! LaTeX error: "xparse/command-already-defined"
!
! Command '\myAlert' already defined!
!
! See the LaTeX3 documentation for further information.
!
! For immediate help type H <return>.
!...............................................

<*> ...Command\myAlert{m m}{{\color{#1}\Large #2}}

既に定義済の命令の定義を変更するには、\RenewDocumentCommand 命令を利用します。標準の \renewcommand に相当するものです。

\RenewDocumentCommand\命令名{引数仕様}{定義本体}

実際に \RenewDocumentCommand 命令を使用すると、やはり次のようなド派手な警告メッセージが出ますが、意図通りであるなら気にする必要はありません。(xparse は、命令が再定義された時に必ず警告メッセージを出します。また、命令が新たに定義された場合はログに情報(Info)メッセージを書き出します。)

*************************************************
* LaTeX warning: "xparse/redefine-command"
*
* Redefining document command \myAlert with arg. spec. 'm m' on line 7.
*************************************************

標準の \providecommand に相当するもの、つまり「当該の命令が未定義の場合のみ定義する」を実行するには \ProvideDocumentCommand を利用します。

\ProvideDocumentCommand\命令名{引数仕様}{定義本体}

ここまでは標準 LaTeX と同じですが、実は xparse にはもう1つのパターンがあります。\DeclareDocumentCommand 命令は「当該の命令が定義済か否かに関わらず無条件に指定の定義を実行する」という動作をします。

\DeclareDocumentCommand\命令名{引数仕様}{定義本体}

「LaTeX のマクロ定義」の記事を読んだ人ならば、標準 LaTeX がこのパターンをサポートしていない理由は既に解っているでしょう。記事の中で、「TeX のプリミティブ \def は無条件に定義を書き換えてしまうので危険」という話をしましたが、この \DeclareDocumentCommand は結局同じ危険性を持ち合わせているわけです。ただし、\def とは異なりこちらは xparse の命令なので、先に述べたように再定義が起こったときは例のド派手な警告が出現するので、\def よりは安全でしょう。とはいってもやはりこの命令の使用については慎重であるべきだと私は思います。

オプション引数をもつ命令

\newcommand 等の標準の命令では、「オプション引数にできるのは先頭の引数のみ」という大きな制約がありました。xparse では、必須引数とオプション引数を任意に混在させた書式の命令が定義できます。

オプション引数をもつ命令を定義するには、引数仕様として m ではなく大文字の O(<optional)を指定します。つまり、「2つの引数をもち、そのうち1つ目がオプションである」という場合は Om を順に並べればよいわけです。ただし、実際には O は「引数を省略した時の既定の値」を“引数”にとって O{値} のように書く必要があります。すなわち、「2つの引数をもち、そのうち1つ目がオプションで既定値が red である」ような命令 \myAlertA を定義する場合は以下の文を実行します。

% \myAlertA[色名]{テキスト} : 指定の色(既定値はred)を用いて大きな文字で出力する.
\NewDocumentCommand\myAlertA{O{red} m}{{\color{#1}\Large #2}}
% (参考: \newcommand の場合.)
%\newcommand*{\myAlertA}[2][red]{{\color{#1}\Large #2}}

引数仕様の中の1文字が1つの引数に対応するので、これまでの説明から、「2つ目の引数だけをオプションにする」「両方の引数をオプションにする」方法も容易に解るでしょう。それぞれ次のようになります。(これらは \newcommand では実現できません。)

% \myAlertB{色名}[テキスト] : 指定の色を用いて大きな文字でテキスト(既定値は"アレ")を出力する.
\NewDocumentCommand\myAlertB{m O{アレ}}{{\color{#1}\Large #2}}
% \myAlertC[色名][テキスト] : 指定の色(既定値はred)を用いて大きな文字でテキスト(既定値は"アレ")を出力する.
\NewDocumentCommand\myAlertC{O{red} O{アレ}}{{\color{#1}\Large #2}}

もう少し複雑な例として、「必須引数の前後にオプション引数がある」命令を定義してみます。

% \myAreA[テキスト]{色名}[フォント命令] :
%   "(テキスト)はアレ" と出力する. "アレ"の部分は
%   指定の色とフォントが適用される.
\NewDocumentCommand\myAreA{O{\TeX} m O{\Large}}
  {#1は{\color{#2}#3アレ}}

この引数の形式の場合、[テキスト][フォント命令] の両方について明示・省略が選べるようになります。

  • \myAreA{red}.\TeX は{\color{red}\Large アレ}.
  • \myAreA[TikZ]{red}.TikZは{\color{red}\Large アレ}.
  • \myAreA{red}[\huge].\TeX は{\color{red}\huge アレ}.
  • \myAreA[TikZ]{blue}[\gtfamily].TikZは{\color{blue}\gtfamily アレ}.

引数が段落をまたぐのを許可する

LaTeX 標準の機能で命令を定義する場合、引数が段落をまたぐことを許すか否かを選択することができました。つまり、\newcommand 等の“* なし”の命令を用いた場合は引数は段落をまたぐことができて(これを「長い引数(long argument)」と呼ぶ)、\newcommand* 等の“* つき”の命令を用いた場合は引数は段落をまたぐことが禁止されました(これを「短い引数(short argument)」と呼ぶ)。

xparse の機能を用いて定義された命令の場合、既定では引数は「短い引数」になります。これを「長い引数」に変えたい場合は、引数仕様の中の対応する文字の前に + を付けます。つまり、引数の各々に対して、それが「長い」か「短い」かを指定することができるわけです。

% \myFramedParBox{枠の色}[内部の色]{幅}{テキスト} :
% テキストを指定の幅で組んで, それを指定の枠と内部の色(既定値white)
% をもつ枠の中に入れる.
\NewDocumentCommand\myFramedParBox{m O{white} m +m}
  {\fcolorbox{#1}{#2}{\parbox{#3}{#4}}}

上の例では、色名と寸法値が入るはずの引数は「短い」ものとしつつ、最後のテキスト(\parbox の引数となるもの)の引数については、指定を +m として「長い」引数にしています。従って、次のように、改段落(空行)を含むテキストを引数にとることが可能です。

\myFramedParBox{red}[yellow]{10zw}{%
{\TeX}はアレです。

しかしTikZはもっともっとアレです。
}

xparse-3.png

補足

  • 今までに紹介した命令(\ナントカDocumentCommand)で定義した命令は必ず(LaTeXの意味で)「頑強(robust)」(さらに言うと、「保護付(protected)」と同様の状態)になります。「LaTeXのマクロ」の範囲で頑強・保護付になって不利になるという場面はほぼあり得ないと思うので、「頑強」の概念をまだ理解していない人は特にこれを気にする必要はありません。
    :sushi: 要するにe-TeXの \protected により保護されたマクロになります。

発展編

① 既定値無しのオプション引数

オプション引数の指定 O は必ず「既定値の指定」をもち、省略した時の動作は「既定値を指定したときと同じ」でした。これに対して、「引数が指定されたか否かで条件分岐したい」という場合が考えられます。
例えば、先の例の \myAlertA[色名]{テキスト} について、「色名が省略された場合は特定の“既定の色”にするのでなく、そもそも色変更を行わない」という動作に変えたいとします。この命令を \myAlertD とすると、この命令の動作は次のようになるべきで、どうしても条件分岐が必要となってきます。

  • \myAlertD[red]{アレ}{\color{red}\Large アレ}
  • \myAlertD{アレ}{\Large アレ}

これを実現するには、「既定値無しのオプション引数」を表す引数仕様文字である小文字の o(<optional)を利用します。

\NewDocumentCommand\myAlertD{o m}{%
% #1=色名(省略可), #2=テキスト
}

これで、\myAlertD{アレ} のように実際に色名を省略して実行した場合、引数 #1 には“値無し”を意味する特殊な「NoValueマーカー」が紐づけられます。従って、あとは値が「NoValueマーカー」であるかを判定して条件分岐する機構があればよさそうです。xparse ではそのような命令を用意しています。

\IfNoValueTF{引数}{値無し}{値有り}
\IfNoValueT{引数}{値無し}
\IfNoValueF{引数}{値有り}
\IfValueTF{引数}{値有り}{値無し}
\IfValueT{引数}{値有り}
\IfValueF{引数}{値無し}

これらは何れも、「引数 が『NoValueマーカー』に一致する(値無し)か否(値有り)かに応じて 値無し値有り のコードの一方を実行する(存在しない場合は何もしない)」という動作を行います。(:sushi: これらの命令は完全展開可能です。)

今の場合、#1 が“値有り”である時に \color{#1} を実行すればよいので、

% \myAlertE[色名]{テキスト} : 大きい文字で出力する.
%   色名が指定された場合, その色を用いる.
\NewDocumentCommand\myAlertD{o m}{%
% #1=色名(省略可), #2=テキスト
  {%
    \IfValueT{#1}{\color{#1}}%
    \Large #2%
  }%
}

実際に試してみましょう。

{\color{blue}{\TeX}\myAlertD[red]{アレ}\myAlertD{アレ}です。}

xparse-4.png

② “*-形”の条件分岐

例えば、以下のような“*-形”をもつ命令 \myAlertE を定義したいとします。

  • \myAlertE{アレ}{\Large アレ} (大きくするだけ)
  • \myAlertE*{アレ}{\color{red}\Large アレ} (*-形の時はさらに赤くする)

xparse の場合、「* があるか」というのを一種の引数とみなし、それを引数仕様文字 s(<star)で表します。\myAlertE の場合、先頭に「* の引数」があるので以下のような引数仕様になります。

\NewDocumentCommand\myAlertE{s m}{%
% #1=*の有無, #2=テキスト
}

この場合、#1 には「*があるか」の真偽値を示す特殊なフラグが紐づけられます。この“真偽値のフラグ”を以下の命令に渡して条件分岐ができます。

\IfBooleanTF{引数}{}{}
\IfBooleanT{引数}{}
\IfBooleanF{引数}{}

これらは何れも、「引数のフラグの値に応じて のコードの一方を実行する(存在しない場合は何もしない)」という動作を行います。(:sushi: 完全展開可能です。)

最終的に \myAlertE の実装は以下のようになります。

% \myAlertE[*]{テキスト} : 大きい文字で出力する.
%   *-形の場合, 赤色を用いる.
\NewDocumentCommand\myAlertE{s m}{%
% #1=*の有無, #2=テキスト
  {%
    \IfBooleanT{#1}{\color{red}}%
    \Large #2%
  }%
}

実際に試してみます。

{\TeX}\myAlertE{アレ}\myAlertE*{アレ}です。

xparse-5.png

ちなみに、引数仕様 s は先頭以外でも使用できます。なので次のような命令定義も可能です。

% \mySampleA[*]{テキスト1}[*]{テキスト2} :
%   "(テキスト1)は(テキスト2)" と出力. テキスト2は大きな字になる.
%   * がある場合, その後ろの引数のテキストが赤色になる.
\NewDocumentCommand\mySampleA{s m s m}{%
  {\IfBooleanT{#1}{\color{red}}#2}%
  {\IfBooleanT{#3}{\color{red}}\Large #4}%
}
% 使用例
\mySampleA*{\TeX}{アレ}、だが\mySampleA{TikZ}*{もっとアレ}

xparse-6.png

“*”以外の記号を使う

LaTeXにおいて、* 以外の記号による条件分布を行う命令が使われることがあります。例えば xkeyval パッケージには \setkeys という命令がありますが、これには *-形 \setkeys* の他に“+-形” \setkeys+ も持ちます。

xparseではこのような“変なスター”で分岐する命令を作ることもできます。引数仕様に S の代わりに t記号 (<token?)を指定するだけです。定義本体での引数の扱いは s と全く同じです。

% \myNum[*][+]{カウンタ名} : カウンタの値を算用数字で
%   出力する. * 付の場合は赤くなり, さらに + 付の場合は
%  フォントがアホになる.
\NewDocumentCommand\myNum{s t+ m}{%
  {%
    \IfBooleanT{#1}{\color{red}}%
    \IfBooleanT{#2}{\LARGE\usefont{OT1}{cmfr}{m}{it}}%
    \arabic{#3}%
  }%
}
% 使用例
\newcounter{foo}\setcounter{foo}{42}
\myNum{foo} \myNum*{foo} \myNum+{foo} \myNum*+{foo}

xparse-7.png

③ “変な括弧”を使う

次のように解釈される命令 \myPutText を定義したいとします。
(これは picture 環境内で用いて、「指定の座標に指定の位置をアンカーとしてテキストを出力する」ためのものです。)

  • \myPutText(座標)[位置]{テキスト}
    \put(座標){\makebox(0,0)[位置]{テキスト}}
    • 座標 は必須、位置 はオプションで既定値は c

通常の LaTeX の命令と異なり、この命令は ( ) で囲まれる必須引数を持っています。このような“変な括弧”で囲まれた必須引数を扱う引数仕様文字が r(<required)で、これは後に「括弧として使う文字のペア」を付けて使います。今の場合、引数仕様の中で r() を指定することになります。
※ この場合、もし \myPutText の直後に ( が無かったとすると、「( が無い」という趣旨の(ド派手な)エラーメッセージが出ます。

% \myPutText(座標)[位置]{テキスト} : 指定の座標にテキストを出力.
\NewDocumentCommand\myPutText{r() O{c} m}{%
  \put(#1){\makebox(0,0)[#2]{#3}}%
}
% 使用例
\begin{picture}(20,20)
  \myPutText(10,10)[br]{\color{red}\TeX}
  \myPutText(10,10)[tl]{\color{blue}\TeX}
  \myPutText(10,10){\color{magenta}\TeX}
\end{picture}

xparse-8.png

また同様に、“変な括弧”をもつオプション指定を扱うこともできます。

  • r() の代わりに d() を指定すると、変な括弧であることを除いて o と同じ動作になります。すなわち、( が無い場合は #1 は「NoValueマーカー」になります。(d<delimited)
  • r() の代わりに D(){既定値} を指定すると、変な括弧であることを除いて O{既定値} と同じ動作になります。すなわち、( が無い場合は #1 は指定の既定値になります。

引数仕様文字の一覧

文字 意味
m 通常の必須引数
o オプション引数、既定値無し
O{値} オプション引数、既定値有り
s * の有無を判定
t? 変なスター版のs*の代わりに?
r() 変な括弧版の必須引数({}の代わりに()
d() 変な括弧版のo[]の代わりに()
D(){値} 変な括弧版のO[]の代わりに()
+ (他の文字の前に付く) 長い引数

なお、引数仕様文字には、ここで紹介しなかったもっと変態なもの(verbatim 引数、区切り付引数、……)も存在するので、変態に興味がある人は xparse のマニュアルを参照してください。(ただし、xparse のマニュアルの説明には少しばかり expl3 の概念が入り込んでいることに注意。)

※参考:verbatimな引数(v)を使う例の紹介→「xparse で verbatim な引数を扱う話

まとめ

少々複雑な引数指定をもつユーザ定義命令が必要な場合も心配は要りません! TeX言語も要りません! xparseパッケージを活用しましょう!