LaTeX で条件分岐や反復処理：ifthen パッケージのキホン

## まえがき

もちろん、LaTeXは（TeX言語と違って）プログラム言語ではないので、条件分岐や繰り返し処理のような“実行制御”なんてアリエナイ、というタテマエがあることは皆さんご存知でしょう。とはいっても実際には「LaTeXにもif文があったらいいのにな……」と思った事があるLaTeXユーザはいるのではないでしょうか。

実は“標準LaTeX”にif文は**あります。** LaTeXの開発元（LaTeX3 Project Team）による配布物の中には、「標準パッケージ（standard packages）」[^std-pkg]と呼ばれる一連のパッケージ群があり、その中の**ifthenパッケージ**がまさに「LaTeXに実行制御の機能を追加する」ためのものなのです。つまり「LaTeXに実行制御はない」というタテマエなんて実は無かったのでした[^std-usr]。

本記事では、ifthenパッケージとその周辺についてのキホンについて解説します。

### 注意

ifthenパッケージは“普通のLaTeXユーザ”向けのものであるので、解説は全てLaTeXの文法の範囲で行います。しかし必要に応じて、TeX言語者向けの補足説明を行うことがあります。その場合は注意のために :sushi: を付けることにします。例えば以下の通りです。

※:sushi: TeX言語はアレ！

## パッケージ読込

プレアンブルに次のように書きます。パッケージオプションはありません。

```latex
\usepackage{ifthen}
```

:::note info
この記事で紹介する例は全て、プレアンブルでifthenパッケージを読みこんでいる前提とします。
:::

## 条件分岐 ― \\ifthenelse

```latex:書式
\ifthenelse{«条件»}{«真»}{«偽»}
```

`«条件»`が成立すれば`«真»`を、しなければ`«偽»`を実行します。

※:sushi: 完全展開可能ではありません。

```latex:例
%%%%(プレアンブル)
\newcounter{verbosity}
\setcounter{verbosity}{2}
%%%%(文書本体)
\ifthenelse{\value{verbosity} > 1}{%
  % カウンタ verbosity の値が2以上ならこちらを実行(出力)
  {\TeX}はアレでアレでアレでアレでアレ、さらにアレ！%
}{%else
  % それ以外はこちらを実行(出力)
  {\TeX}はアレ！%
}
```

## 繰り返し実行 ― \\whiledo

```latex:書式
\whiledo{«条件»}{«何か»}
```

`«条件»`が成立する間、`«何か»`を繰り返し実行します。

※:sushi: もちろん完全展開可能ではありません。

```latex:例
%%%%(プレアンブル)
\newcounter{count}
%%%%(文書本体)
{\TeX}は%
% "とっても"を5回繰り返す
\setcounter{count}{5}%
\whiledo{\value{count}>0}{%
  とっても\addtocounter{count}{-1}}%
アレ！
```

## 条件部の書き方

`\ifthenelse`や`\whiledo`における`«条件»`の部分は、以下の要素を組み合わせて記述します。

  * 命題： 真偽値に評価される式のことです。詳細は後述。

    ```latex:例
    12 > 3           %==>真
    \equal{FOO}{foo} %==>偽
    \boolean{true}   %==>真
    ```

  * `\NOT`： 単項演算子です。

    ```latex:例
    \NOT 42 > 42     %==>真
    ```

  * `\AND`、`\OR`： 二項演算子です。左結合で、`\AND`と`\OR`の間に優先順位はありません。

    ```latex:例
    1<2 \OR 3<4 \AND 5>6 %==> "(真 or 真) and 偽"なので偽
    ```

  * `\(...\)`： 演算子の優先順位を変えます。

    ```latex:例
    1<2 \OR \(3<4 \AND 5>6\) %==> "真 or (真 and 偽)"なので真
    \NOT \(1<3 \AND 3<5\)    %==> "not (真 and 真)"なので偽
    ```

※ `\NOT` `\AND` `\OR`は（恐らく過去版との互換性のため）小文字で`\not` `\and` `\or`と書くことも許されていますが、これらの命令名は既存のものと衝突しているので使うべきでありません[^or-prim]。

### 命題の書き方

  * `«整数» < «整数»`／`«整数» = «整数»`／`«整数» > «整数»`： 整数どうしを比較します。

    ```latex:例
    %% カウンタ'tocdepth'の値が3より大ならば
    \value{tocdepth} > 3
    %% ≦,≧は直接書けないので"3以下"は次のように書く
    \NOT \value{tocdepth} > 3
    ```

  * `\lengthtest{«長さ» < «長さ»}`／`\lengthtest{«長さ» = «長さ»}`／`\lengthtest{«長さ» > «長さ»}`： 長さどうしを比較します。

    ```latex:例
    %% 長さ変数 \parindent が正ならば
    \lengthtest{\parindent > 0pt}
    ```

  * `\equal{«文字列»}{«文字列»}`： 2つの文字列が等しいかを判定します。
    文字列の中に（保護付でない）(La)TeXマクロがある場合は展開されます。
    
    ```latex:例
    %% 現在の節番号が'B'ならば
    \equal{\thesection}{B}
    ```
    
  * `\isundefined{\命令}`： その命令（制御綴）が未定義であるかを判定します（定義されていれば偽）。

    ```latex:例
    %% \chapter 命令が存在しないならば
    \isundefined{\chapter}
    ```

  * `\isodd{«整数»}`： 整数が奇数であるかを判定します。

    ```latex:例
    %% カウンタ'figure'の値が奇数ならば(なんだそりゃ)
    \isodd{\value{figure}}
    ```

:::note warn
恐らく「奇数か偶数か」の判定が一番有効なのはページ番号についてでしょう[^odd]。しかしLaTeXの仕様上の制限のため、実際に`\isodd{\value{page}}`をその用途で使えるのはヘッダ・フッタの出力の場合に限られることに注意してください。

詳細については[「LaTeXで現在のページ番号を取得・出力する話」](http://qiita.com/zr_tex8r/items/d9970aa8f4558de33dbe)を参照してください。
:::

  * `\boolean{«変数名»}`： 真偽値変数を参照します。詳細は後述。

## 値の書き方

整数や文字列などの“値”をどう書くか、また“変数”をどうやって使うか、については基本的に「LaTeXの文法に従う」わけですが、あまり馴染みのない人も多いと思われますので、必要最低限の説明をしておきます。

### 整数値

命題の記述の中の`«整数»`の箇所には以下のものを書くことができます。

  * `42`や`-3`等の、数字列で表される即値。
  * `\value{«カウンタ名»}`： そのカウンタの現在の値。
  * 上掲のものに展開されるLaTeXマクロ。
  * :sushi: その他、TeX言語で整数と解釈されるトークン列全て。

カウンタを扱うための命令には以下のものがあります。

  * `\newcounter{«カウンタ名»}`： 新たにカウンタを定義します。
  * `\setcounter{«カウンタ名»}{«整数»}`： カウンタの値を変更します。
  * `\addtocounter{«カウンタ名»}{«整数»}`： 現在のカウンタの値に引数の値を加算します。
  * calcパッケージを読み込むと、`\setcounter`や`\addtocounter`の引数の`«整数»`に`6*9`等の式を書くことが可能になります[^calc]。

※カウンタ値の操作のスコープは**グローバル**です。つまり`{...}`によるグループ化の影響をうけません。

```latex:例
%%%%(プレアンブル)
\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`等の「単位付実数」で表される即値[^imm-dim]。
  * `\parindent`などの長さ変数である命令： その長さ変数の値。  
    ※変数の値が伸縮付長さ（rubber length）である場合はその自然長を用いて比較が行われます。
  * 上掲のものに展開されるLaTeXマクロ。
  * :sushi: その他、TeX言語で寸法値（dimen）と解釈されるトークン列全て。

長さ変数を扱うための命令には以下のものがあります。

  * `\newlength{\命令}`： 新たに長さ変数を定義します。
  * `\setlength{\命令}{«長さ»}`： 変数の値を変更します。
  * `\addtolength{\命令}{«長さ»}`： 現在の変数の値に引数の値を加算します。
  * calcパッケージを読み込むと、`\setlength`や`\addtolength`の引数の`«長さ»`に`210mm-1in`等の式を書くことが可能になります。

※長さ変数の操作のスコープは**ローカル**です。つまり`{...}`によるグループ化の影響をうけます。

```latex:例
%%%%(プレアンブル)
\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マクロ

※:sushi: 保護付で完全展開したトークン列について完全一致の判定している（という動作と等価な）ようです。

`\newcommand`系列[^xparse]のマクロ定義命令で定義された無引数[^macroarg]のLaTeXマクロは展開されるため、LaTeXマクロを文字列型の変数のように扱うことができます。

:::note info
詳しくは[「完全攻略！ LaTeX のマクロ定義」](http://qiita.com/zr_tex8r/items/5067307890d36c0e4882)の記事を参照してください。
:::

```latex:例
%%%%(プレアンブル)
\newcommand*{\lang}{kansai}% "文字列変数"的マクロ
%%%%(文書本体)
\ifthenelse{\equal{\lang}{kansai}}{%
  {\TeX}はアレやで！%
}{%else
  {\TeX}はアレですよ！%
}
```

### 真偽値

ifthenパッケージの機能として、真偽値型の“変数”を定義してそれを`\ifthenelse`や`\whiledo`の命題として使うことができます。

  * `\newboolean{«変数名»}`： 新たに真偽値型の変数を定義します。
  * `\setboolean{«変数名»}{«値»}`： 変数の値を変更します。`«値»`は`true`か`false`です。
  * `\ifthenelse`や`\whiledo`の条件部で`\boolean{«変数名»}`と書くとその変数の値をもつ命題として扱われます。

※:sushi: 実は`\newboolean`は`\newif`に対するラッパーです。つまり、「`XXX`という真偽値変数」の実体は「`\ifXXX`というスイッチ」だということです。

```latex:例
%%%%(プレアンブル)
\newboolean{Precise}%正確か?
\newboolean{Useful}%有用か?
\setboolean{Precise}{false}
\setboolean{Useful}{false}
%%%%(文書本体)
\ifthenelse{\boolean{Precise} \AND \boolean{Useful}}{%
  %何もしない
}{%else
  出力すべきものは何もありませんでした。
  \end{document}% めでたしめでたし
}

\maketitle
\begin{abstract}
近年、…… %以下いろいろと書いてある
```

ユーザが定義する変数の他に、LaTeXカーネルや各種パッケージにおいて定義される真偽値変数もあります。

:::note warn
これらの変数については`\setboolean`で値を変更してはいけません。
:::

| 変数名 | 意味 | パッケージ |
|:--|:--|:--|
| `true` | 常に真（定数） | ― |
| `false` | 常に偽（定数） | ― |
| `mmode` | 数式モードであるか | ― |
| `tdir` | 縦書きモードであるか | ―[^tdir] |
| ~~`draft`~~ | ~~下書きモード（`draft`オプション付）であるか~~ | ※[^draft] |
| `pdftex` | **TeXエンジン**がpdfTeXであるか<br>（**LaTeXエンジン**がpdfLaTeX**または**欧文LaTeXか） | iftex[^iftex] |
| `pdf` | PDFモードであるか[^pdf-mode] | iftex[^iftex]／ifpdf |
| `xetex` | TeXエンジンがXeTeXであるか | iftex[^iftex]／ifxetex |
| `luatex` | TeXエンジンがLuaTeX（派生含む）であるか | iftex[^iftex]／ifluatex |
| `uptex`[^upper] | **TeXエンジン**がupTeX（派生含む）であるか | ifptex[^ifptex]／ifuptex |
| `nativeuptex`[^upper] | **TeXエンジン**が“内部Unicode”動作のupTeX（派生含む）であるか<br>（**LaTeXエンジン**がpLaTeX**でなく**upLaTeXであるか） | ifptex[^ifptex]／ifuptex |
| `ptex`[^upper] | **TeXエンジン**がpTeX（派生含む、特に**upTeXも含む**）であるか<br>（**LaTeXエンジン**がpLaTeX**または**upLaTeXであるか） | ifptex[^ifptex] |

:::note warn
TeX Live 2023以降ではpLaTeXが（従来のe-pTeXエンジンに代わり）e-upTeXエンジンの上で動作するようになります。従来通り“内部SJIS/EUC”（“内部Unicode”でなく）で動作するためpLaTeXとしての動作は全く変わらないのですが、**TeXエンジンの判定とLaTeXエンジンの判定がずれる**[^pdftex]ことに注意が必要です。**TeX Live 2017以降**では[^pre2017]以下が成り立ちます。

* 「pLaTeX**または**upLaTeXであるか」に相当するのは`ptex`です。
* 「pLaTeX**でなく**upLaTeXであるか」に相当するのは`nativeuptex`です。
:::

※:sushi: この他でも、もしあるパッケージが `\ifXXX` というスイッチ（if-トークン）を定義しているならそれは `XXX` という真偽値変数として使えます。要するに次の2つが等価になるわけです。

```latex
\ifXXX «真» \else «偽» \fi % TeXのif文
\ifthenelse{\boolean{XXX}}{«真»}{«偽»}% LaTeXのif文
```

## 実用例

せっかく便利なパッケージの使い方を学んだのですから、色々やってみましょう！

### その1： エンジンの種類により分岐

何かやんごとなき事情があって「複数のエンジンでコンパイルできる日本語文書」という**変態**なものを作りたい場合に、エンジンの種類により条件分岐を行い、それぞれ適切な処理を行う、という例です。

```latex
% 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}
```

![example1.png](https://qiita-image-store.s3.amazonaws.com/0/14550/139c49c9-78cc-3ecc-99e9-cea868697486.png)

### その2： テキストの幅に応じて分岐

マクロの引数に与えたテキストが長いか短いかで処理を変える、という例です。

```latex
% 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}
```

![example2.png](https://qiita-image-store.s3.amazonaws.com/0/14550/9ecd50ed-867e-2967-5120-fded5b6d7cd2.png)

### その3： LaTeXでFizzBuzz

とっても実用的な例。

```latex
% 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}
```

![example3.png](https://qiita-image-store.s3.amazonaws.com/0/14550/9e249337-1969-8e8b-597a-781e44a6e5a0.png)

## まとめ

LaTeXにif文はあります！ ちょっとした実行制御ならばTeX言語は必要ありません！ ifthenパッケージを活用しましょう！

[^std-pkg]: ifthen の他には fontenc、inputenc、latexsym 等が「標準パッケージ」に含まれます。
[^std-usr]: ちなみに、ifthen も含めて「標準パッケージ」は全て“普通のLaTeXユーザ”（文書作成者）が用いることを想定したものです。このことは usrguide で標準パッケージが紹介されていることから解ります。
[^or-prim]: :sushi: 特に `\or` は危険で、`\ifcase...\fi` の中に `\or` が紛れ込んでしまうと誤動作の原因になってしまいます。
[^odd]: そもそも `\isodd` なんて変な機能があるのはTeX言語に `\ifodd` というプリミティブが存在するからであり、TeXに `\ifodd` がある理由はページ番号の偶奇判定が必要だからです。
[^imm-dim]: この場合、`plus ... minus ...`のような伸縮を付けることはできません。
[^calc]: `\ifthenelse`の命題の記述の中で式が使えるようにはならないことに注意。
[^xparse]: `\NewDocumentCommand`系の“新方式”のLaTeXマクロ定義命令で定義されたLaTeXマクロは展開されません（※:sushi:実態がprotectedなマクロだからです）。その代わりに“新方式”のマクロ定義では「展開可能なLaTeXマクロ」を定義するための専用の命令が用意されていて、`\NewExpandableDocumentCommand`のように命令名に`Expandable`がついています。
[^macroarg]: 必須引数をもつLaTeXマクロも展開されますが、オプション引数をもつLaTeXマクロは「展開してはいけない」（つまり`\equal`の引数に入れてはいけない）という扱いになります。なお、`\NewExpandableDocumentCommand`系の「“新方式”の展開可能なLaTeXマクロ」はオプション引数をもつものも含めて展開されます。
[^draft]: かつてjsclassesバンドルの文書クラスで利用できましたが、ifdraftパッケージの機能と衝突するため、2016/07/13の版でこの機能は**廃止**されました。下書きモードであるかを判定したいときは、ifdraftパッケージを読み込んで、`\ifdraft{«真»}{«偽»}`としてください。
[^tdir]: (u)pLaTeXエンジンでのみ利用可能です。
[^iftex]: TeX Live 2020以降であればiftexパッケージが`pdf`・`xetex`・`luatex`を（※:sushi:つまり`\ifpdf`・`\ifxetex`・`\ifluatex`を）全て提供します。このiftexは旧来のiftexとifpdf・ifxetex・ifluatexの各パッケージの機能を統合したものです。
[^pdf-mode]: 「PDFモードである」というのは、エンジンがpdfTeXまたはLuaTeXであってかつPDFを書き出す状態である、ということ。
[^ifptex]: TeX Live 2017以降ではifptexパッケージが収録されていて、ifuptexパッケージは事実上ifptexの単なる別名になっています。
[^upper]: `pTeX`・`upTeX`・`nativeupTeX`も使用できます。TeX Live 2017より前の古いifupTeXパッケージでは`upTeX`・`nativeupTeX`**のみ**が使用可能でした。
[^pdftex]: 欧文LaTeXにはかなり昔から同様のすれが存在していて、欧文LaTeX（`latex`コマンド）は「pdfTeXエンジンのDVI出力モード動作」になっています。このため、`pdftex`はDVI出力の欧文LaTeXでも真と判定されます。
[^pre2017]: もしTeX Live 2017より前の古い環境に対応したい場合は以下のようにします：①ifptexパッケージは存在しないかもしれないのでifuptexパッケージを使います。②`nativeuptex`は未サポートかもしれないので`nativeupTeX`を使います。③`ptex`は未サポートかもしれません（残念ながら代替はありません）。