68
59

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2014-12-01

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

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

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

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

※なお、“LaTeX3チーム”とは文字通り“LaTeX3”(次世代の LaTeX)を開発するチームのことであって、xparseパッケージもLaTeX3と多少の繋がりがあるのですが、ここでは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\命令名{引数仕様}{定義本体}

ここまでは標準と同じですが、実は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と同じ動作になります。すなわち、(がない場合は当該引数は「NoValueマーカー」になります。(d<delimited)
  • r()の代わりにD(){既定値}を指定すると、変な括弧であることを除いてO{既定値}と同じ動作になります。すなわち、(が無い場合は当該引数は既定値になります。

引数仕様文字の一覧

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

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

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

まとめ

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

68
59
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
68
59

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?