まえがき
もちろん、LaTeXは(TeX言語と違って)プログラム言語ではないので、条件分岐や繰り返し処理のような“実行制御”なんてアリエナイ、というタテマエがあることは皆さんご存知でしょう。とはいっても実際には「LaTeXにもif文があったらいいのにな……」と思った事があるLaTeXユーザはいるのではないでしょうか。
実は“標準LaTeX”にif文はあります。 LaTeXの開発元(LaTeX3 Project Team)による配布物の中には、「標準パッケージ(standard packages)」1と呼ばれる一連のパッケージ群があり、その中のifthenパッケージがまさに「LaTeXに実行制御の機能を追加する」ためのものなのです。つまり「LaTeXに実行制御はない」というタテマエなんて実は無かったのでした2。
本記事では、ifthenパッケージとその周辺についてのキホンについて解説します。
注意
ifthenパッケージは“普通のLaTeXユーザ”向けのものであるので、解説は全てLaTeXの文法の範囲で行います。しかし必要に応じて、TeX言語者向けの補足説明を行うことがあります。その場合は注意のために を付けることにします。例えば以下の通りです。
※ TeX言語はアレ!
パッケージ読込
プレアンブルに次のように書きます。パッケージオプションはありません。
\usepackage{ifthen}
この記事で紹介する例は全て、プレアンブルでifthenパッケージを読みこんでいる前提とします。
条件分岐 ― \ifthenelse
\ifthenelse{«条件»}{«真»}{«偽»}
«条件»
が成立すれば«真»
を、しなければ«偽»
を実行します。
※ 完全展開可能ではありません。
%%%%(プレアンブル)
\newcounter{verbosity}
\setcounter{verbosity}{2}
%%%%(文書本体)
\ifthenelse{\value{verbosity} > 1}{%
% カウンタ verbosity の値が2以上ならこちらを実行(出力)
{\TeX}はアレでアレでアレでアレでアレ、さらにアレ!%
}{%else
% それ以外はこちらを実行(出力)
{\TeX}はアレ!%
}
繰り返し実行 ― \whiledo
\whiledo{«条件»}{«何か»}
«条件»
が成立する間、«何か»
を繰り返し実行します。
※ もちろん完全展開可能ではありません。
%%%%(プレアンブル)
\newcounter{count}
%%%%(文書本体)
{\TeX}は%
% "とっても"を5回繰り返す
\setcounter{count}{5}%
\whiledo{\value{count}>0}{%
とっても\addtocounter{count}{-1}}%
アレ!
条件部の書き方
\ifthenelse
や\whiledo
における«条件»
の部分は、以下の要素を組み合わせて記述します。
-
命題: 真偽値に評価される式のことです。詳細は後述。
例12 > 3 %==>真 \equal{FOO}{foo} %==>偽 \boolean{true} %==>真
-
\NOT
: 単項演算子です。例\NOT 42 > 42 %==>真
-
\AND
、\OR
: 二項演算子です。左結合で、\AND
と\OR
の間に優先順位はありません。例1<2 \OR 3<4 \AND 5>6 %==> "(真 or 真) and 偽"なので偽
-
\(...\)
: 演算子の優先順位を変えます。例1<2 \OR \(3<4 \AND 5>6\) %==> "真 or (真 and 偽)"なので真 \NOT \(1<3 \AND 3<5\) %==> "not (真 and 真)"なので偽
※ \NOT
\AND
\OR
は(恐らく過去版との互換性のため)小文字で\not
\and
\or
と書くことも許されていますが、これらの命令名は既存のものと衝突しているので使うべきでありません3。
命題の書き方
-
«整数» < «整数»
/«整数» = «整数»
/«整数» > «整数»
: 整数どうしを比較します。例%% カウンタ'tocdepth'の値が3より大ならば \value{tocdepth} > 3 %% ≦,≧は直接書けないので"3以下"は次のように書く \NOT \value{tocdepth} > 3
-
\lengthtest{«長さ» < «長さ»}
/\lengthtest{«長さ» = «長さ»}
/\lengthtest{«長さ» > «長さ»}
: 長さどうしを比較します。例%% 長さ変数 \parindent が正ならば \lengthtest{\parindent > 0pt}
-
\equal{«文字列»}{«文字列»}
: 2つの文字列が等しいかを判定します。
文字列の中に(保護付でない)(La)TeXマクロがある場合は展開されます。例%% 現在の節番号が'B'ならば \equal{\thesection}{B}
-
\isundefined{\命令}
: その命令(制御綴)が未定義であるかを判定します(定義されていれば偽)。例%% \chapter 命令が存在しないならば \isundefined{\chapter}
-
\isodd{«整数»}
: 整数が奇数であるかを判定します。例%% カウンタ'figure'の値が奇数ならば(なんだそりゃ) \isodd{\value{figure}}
恐らく「奇数か偶数か」の判定が一番有効なのはページ番号についてでしょう4。しかしLaTeXの仕様上の制限のため、実際に\isodd{\value{page}}
をその用途で使えるのはヘッダ・フッタの出力の場合に限られることに注意してください。
詳細については「LaTeXで現在のページ番号を取得・出力する話」を参照してください。
-
\boolean{«変数名»}
: 真偽値変数を参照します。詳細は後述。
値の書き方
整数や文字列などの“値”をどう書くか、また“変数”をどうやって使うか、については基本的に「LaTeXの文法に従う」わけですが、あまり馴染みのない人も多いと思われますので、必要最低限の説明をしておきます。
整数値
命題の記述の中の«整数»
の箇所には以下のものを書くことができます。
-
42
や-3
等の、数字列で表される即値。 -
\value{«カウンタ名»}
: そのカウンタの現在の値。 - 上掲のものに展開されるLaTeXマクロ。
- その他、TeX言語で整数と解釈されるトークン列全て。
カウンタを扱うための命令には以下のものがあります。
-
\newcounter{«カウンタ名»}
: 新たにカウンタを定義します。 -
\setcounter{«カウンタ名»}{«整数»}
: カウンタの値を変更します。 -
\addtocounter{«カウンタ名»}{«整数»}
: 現在のカウンタの値に引数の値を加算します。 - calcパッケージを読み込むと、
\setcounter
や\addtocounter
の引数の«整数»
に6*9
等の式を書くことが可能になります5。
※カウンタ値の操作のスコープはグローバルです。つまり{...}
によるグループ化の影響をうけません。
%%%%(プレアンブル)
\usepackage{ifthen,calc}
\newcommand*{\SIX}{6}
\newcounter{Nine}\setcounter{Nine}{9}
\newcounter{Test}
%%%%(文書本体)
\setcounter{Test}{\SIX*\value{Nine}}%54が代入される
\newcommand*{\Answer}{42}
\ifthenelse{\value{Test}=\Answer}% 54=42 となり偽
{The universe goes well.}
{The universe goes wrong!!}%こっちが出力される
長さ値
命題の記述の中の«長さ»
の箇所には以下のものを書くことができます。
-
42pt
や-1in
や1.5em
等の「単位付実数」で表される即値6。 -
\parindent
などの長さ変数である命令: その長さ変数の値。
※変数の値が伸縮付長さ(rubber length)である場合はその自然長を用いて比較が行われます。 - 上掲のものに展開されるLaTeXマクロ。
- その他、TeX言語で寸法値(dimen)と解釈されるトークン列全て。
長さ変数を扱うための命令には以下のものがあります。
-
\newlength{\命令}
: 新たに長さ変数を定義します。 -
\setlength{\命令}{«長さ»}
: 変数の値を変更します。 -
\addtolength{\命令}{«長さ»}
: 現在の変数の値に引数の値を加算します。 - calcパッケージを読み込むと、
\setlength
や\addtolength
の引数の«長さ»
に210mm-1in
等の式を書くことが可能になります。
※長さ変数の操作のスコープはローカルです。つまり{...}
によるグループ化の影響をうけます。
%%%%(プレアンブル)
\usepackage{ifthen,calc}
\newlength{\lengthA}\setlength{\lengthA}{10pt + 5pt}
\newlength{\lengthB}\setlength{\lengthB}{10pt plus 5pt}
%%%%(文書本体)
\newcommand*{\OneInch}{1in}
% "1in(=72.27pt) > 12pt"となり真
\ifthenelse{\lengthtest{\OneInch > 12pt}}{Yes!}{No!}
% "15pt > 12pt"となり真
\ifthenelse{\lengthtest{\lengthA > 12pt}}{Yes!}{No!}
% "10pt(\lengthB の自然長) > 12pt"となり偽
\ifthenelse{\lengthtest{\lengthB > 12pt}}{Yes!}{No!}
% この書き方は不可
%\ifthenelse{\lengthtest{10pt plus 5pt > 12pt}}{Yes!}{No!}
文字列
\equal
の引数の文字列については、少なくとも以下のものを含めることができます。
- 「LaTeXの特殊文字」でない文字
- 均衡な
{ }
の組 -
\protect
で保護された命令 - 上掲のものの列に展開されるLaTeXマクロ
※ 保護付で完全展開したトークン列について完全一致の判定している(という動作と等価な)ようです。
\newcommand
系列7のマクロ定義命令で定義された無引数8のLaTeXマクロは展開されるため、LaTeXマクロを文字列型の変数のように扱うことができます。
詳しくは「完全攻略! LaTeX のマクロ定義」の記事を参照してください。
%%%%(プレアンブル)
\newcommand*{\lang}{kansai}% "文字列変数"的マクロ
%%%%(文書本体)
\ifthenelse{\equal{\lang}{kansai}}{%
{\TeX}はアレやで!%
}{%else
{\TeX}はアレですよ!%
}
真偽値
ifthenパッケージの機能として、真偽値型の“変数”を定義してそれを\ifthenelse
や\whiledo
の命題として使うことができます。
-
\newboolean{«変数名»}
: 新たに真偽値型の変数を定義します。 -
\setboolean{«変数名»}{«値»}
: 変数の値を変更します。«値»
はtrue
かfalse
です。 -
\ifthenelse
や\whiledo
の条件部で\boolean{«変数名»}
と書くとその変数の値をもつ命題として扱われます。
※ 実は\newboolean
は\newif
に対するラッパーです。つまり、「XXX
という真偽値変数」の実体は「\ifXXX
というスイッチ」だということです。
%%%%(プレアンブル)
\newboolean{Precise}%正確か?
\newboolean{Useful}%有用か?
\setboolean{Precise}{false}
\setboolean{Useful}{false}
%%%%(文書本体)
\ifthenelse{\boolean{Precise} \AND \boolean{Useful}}{%
%何もしない
}{%else
出力すべきものは何もありませんでした。
\end{document}% めでたしめでたし
}
\maketitle
\begin{abstract}
近年、…… %以下いろいろと書いてある
ユーザが定義する変数の他に、LaTeXカーネルや各種パッケージにおいて定義される真偽値変数もあります。
これらの変数については\setboolean
で値を変更してはいけません。
変数名 | 意味 | パッケージ |
---|---|---|
true |
常に真(定数) | ― |
false |
常に偽(定数) | ― |
mmode |
数式モードであるか | ― |
tdir |
縦書きモードであるか | ―9 |
draft |
draft オプション付)であるか |
※10 |
pdftex |
TeXエンジンがpdfTeXであるか (LaTeXエンジンがpdfLaTeXまたは欧文LaTeXか) |
iftex11 |
pdf |
PDFモードであるか12 | iftex11/ifpdf |
xetex |
TeXエンジンがXeTeXであるか | iftex11/ifxetex |
luatex |
TeXエンジンがLuaTeX(派生含む)であるか | iftex11/ifluatex |
uptex 13
|
TeXエンジンがupTeX(派生含む)であるか | ifptex14/ifuptex |
nativeuptex 13
|
TeXエンジンが“内部Unicode”動作のupTeX(派生含む)であるか (LaTeXエンジンがpLaTeXでなくupLaTeXであるか) |
ifptex14/ifuptex |
ptex 13
|
TeXエンジンがpTeX(派生含む、特にupTeXも含む)であるか (LaTeXエンジンがpLaTeXまたはupLaTeXであるか) |
ifptex14 |
※ この他でも、もしあるパッケージが \ifXXX
というスイッチ(if-トークン)を定義しているならそれは XXX
という真偽値変数として使えます。要するに次の2つが等価になるわけです。
\ifXXX «真» \else «偽» \fi % TeXのif文
\ifthenelse{\boolean{XXX}}{«真»}{«偽»}% LaTeXのif文
実用例
せっかく便利なパッケージの使い方を学んだのですから、色々やってみましょう!
その1: エンジンの種類により分岐
何かやんごとなき事情があって「複数のエンジンでコンパイルできる日本語文書」という変態なものを作りたい場合に、エンジンの種類により条件分岐を行い、それぞれ適切な処理を行う、という例です。
% pLaTeX/upLaTeX/LuaLaTeX/XeLaTeX文書
\documentclass[autodetect-engine,dvipdfmx-if-dvi,
a4paper,ja=standard,jafont=kozuka-pr6n]{bxjsarticle}
\usepackage{ifthen}
\usepackage{ifxetex,ifluatex,ifuptex}
% \CID 命令を使いたいので...
\ifthenelse{\boolean{xetex}}{
% XeLaTeXではzxotfを読み込む
\usepackage{zxotf}
}{\ifthenelse{\boolean{luatex}}{
% LuaLaTeXではluatexja-otfを読み込む
\usepackage{luatexja-otf}
}{\ifthenelse{\boolean{nativeupTeX}}{
% upLaTeXではuplatexオプション付でotf
\usepackage[uplatex]{otf}
}{%else (pLaTeXと仮定する)
% pLaTeXではオプション無しでotf
\usepackage{otf}
}}}
\begin{document}
% '葛'の特定の異体字を出力したい
\Large \CID{1481}城市から\CID{7652}飾区まで。
\end{document}
その2: テキストの幅に応じて分岐
マクロの引数に与えたテキストが長いか短いかで処理を変える、という例です。
% upLaTeX文書
\documentclass[uplatex,dvipdfmx,a4paper]{jsarticle}
\usepackage{ifthen}
\usepackage{okumacro}% \kintou を使いたい
\usepackage{graphicx}% \resizebox を使いたい
\newlength{\OrigWidth}
%% \InWidth{«横幅»}{«テキスト»}
% テキストを指定の横幅に"イイカンジに"出力する.
\newcommand*\InWidth[2]{%
\settowidth{\OrigWidth}{#2}% \settowidth 便利
\ifthenelse{\lengthtest{\OrigWidth > #1}}{%
% 指定幅より大きい場合は横方向に縮小する
\resizebox{#1}{\height}{#2}% 横幅だけ変える
}{%else
% 指定幅以下の場合は均等割りを行う
\kintou{#1}{#2}%
}%
}
\begin{document}
\begin{center}\gtfamily
\begin{tabular}{|c|}\hline
\InWidth{8zw}{レット} \\\hline
\InWidth{8zw}{ミーニング} \\\hline
\InWidth{8zw}{ノーエクスパンド} \\\hline
\InWidth{8zw}{エクスパンドアフター} \\\hline
\InWidth{8zw}{アフターアサインメント}\\\hline
\end{tabular}
\end{center}
\end{document}
その3: LaTeXでFizzBuzz
とっても実用的な例。
% LaTeX文書, エンジン不問
\documentclass[a4paper]{article}
\usepackage{ifthen,calc}
\newcounter{Count}
\newcounter{Rem}
%% \FizzBuzz{«整数n»}
% 1からnまでの範囲でFizzBuzzする.
\newcommand*\FizzBuzz[1]{%
\setcounter{Count}{0}%
\whiledo{\value{Count} < #1}{%
\addtocounter{Count}{1}%
\IfMultiple{15}{FizzBuzz}{%
\IfMultiple{3}{Fizz}{%
\IfMultiple{5}{Buzz}{%
\arabic{Count}% 算用数字で値を出力
}}}\ % 空白を出力
}%
}
%% \IfMultiple{«整数n»}{«真»}{«偽»}
% Countの値がnの倍数であるかを判定する.
\newcommand*{\IfMultiple}[3]{%
% 余りを計算する
\setcounter{Rem}{\value{Count}/#1*#1-\value{Count}}%
\ifthenelse{\value{Rem}=0}{#2}{#3}%
}
\begin{document}
% 1から100までFizzBuzzする
\noindent \FizzBuzz{100}
\end{document}
まとめ
LaTeXにif文はあります! ちょっとした実行制御ならばTeX言語は必要ありません! ifthenパッケージを活用しましょう!
-
ifthen の他には fontenc、inputenc、latexsym 等が「標準パッケージ」に含まれます。 ↩
-
ちなみに、ifthen も含めて「標準パッケージ」は全て“普通のLaTeXユーザ”(文書作成者)が用いることを想定したものです。このことは usrguide で標準パッケージが紹介されていることから解ります。 ↩
-
特に
\or
は危険で、\ifcase...\fi
の中に\or
が紛れ込んでしまうと誤動作の原因になってしまいます。 ↩ -
そもそも
\isodd
なんて変な機能があるのはTeX言語に\ifodd
というプリミティブが存在するからであり、TeXに\ifodd
がある理由はページ番号の偶奇判定が必要だからです。 ↩ -
\ifthenelse
の命題の記述の中で式が使えるようにはならないことに注意。 ↩ -
この場合、
plus ... minus ...
のような伸縮を付けることはできません。 ↩ -
\NewDocumentCommand
系の“新方式”のLaTeXマクロ定義命令で定義されたLaTeXマクロは展開されません(※実態がprotectedなマクロだからです)。その代わりに“新方式”のマクロ定義では「展開可能なLaTeXマクロ」を定義するための専用の命令が用意されていて、\NewExpandableDocumentCommand
のように命令名にExpandable
がついています。 ↩ -
必須引数をもつLaTeXマクロも展開されますが、オプション引数をもつLaTeXマクロは「展開してはいけない」(つまり
\equal
の引数に入れてはいけない)という扱いになります。なお、\NewExpandableDocumentCommand
系の「“新方式”の展開可能なLaTeXマクロ」はオプション引数をもつものも含めて展開されます。 ↩ -
(u)pLaTeXエンジンでのみ利用可能です。 ↩
-
かつてjsclassesバンドルの文書クラスで利用できましたが、ifdraftパッケージの機能と衝突するため、2016/07/13の版でこの機能は廃止されました。下書きモードであるかを判定したいときは、ifdraftパッケージを読み込んで、
\ifdraft{«真»}{«偽»}
としてください。 ↩ -
TeX Live 2020以降であればiftexパッケージが
pdf
・xetex
・luatex
を(※つまり\ifpdf
・\ifxetex
・\ifluatex
を)全て提供します。このiftexは旧来のiftexとifpdf・ifxetex・ifluatexの各パッケージの機能を統合したものです。 ↩ ↩2 ↩3 ↩4 -
「PDFモードである」というのは、エンジンがpdfTeXまたはLuaTeXであってかつPDFを書き出す状態である、ということ。 ↩
-
pTeX
・upTeX
・nativeupTeX
も使用できます。TeX Live 2017より前の古いifupTeXパッケージではupTeX
・nativeupTeX
のみが使用可能でした。 ↩ ↩2 ↩3 -
TeX Live 2017以降ではifptexパッケージが収録されていて、ifuptexパッケージは事実上ifptexの単なる別名になっています。 ↩ ↩2 ↩3
-
欧文LaTeXにはかなり昔から同様のすれが存在していて、欧文LaTeX(
latex
コマンド)は「pdfTeXエンジンのDVI出力モード動作」になっています。このため、pdftex
はDVI出力の欧文LaTeXでも真と判定されます。 ↩ -
もしTeX Live 2017より前の古い環境に対応したい場合は以下のようにします:①ifptexパッケージは存在しないかもしれないのでifuptexパッケージを使います。②
nativeuptex
は未サポートかもしれないのでnativeupTeX
を使います。③ptex
は未サポートかもしれません(残念ながら代替はありません)。 ↩