LoginSignup
46
40

More than 1 year has passed since last update.

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

Last updated at Posted at 2016-02-07

まえがき

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

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

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

注意

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

:sushi: TeX言語はアレ!

パッケージ読込

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

\usepackage{ifthen}

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

条件分岐 ― \ifthenelse

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

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

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

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

繰り返し実行 ― \whiledo

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

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

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

%%%%(プレアンブル)
\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マクロ。
  • :sushi: その他、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-1in1.5em等の「単位付実数」で表される即値6
  • \parindentなどの長さ変数である命令: その長さ変数の値。
    ※変数の値が伸縮付長さ(rubber length)である場合はその自然長を用いて比較が行われます。
  • 上掲のものに展開されるLaTeXマクロ。
  • :sushi: その他、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マクロ

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

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

詳しくは「完全攻略! LaTeX のマクロ定義」の記事を参照してください。

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

真偽値

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

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

:sushi: 実は\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
uptex13 TeXエンジンがupTeX(派生含む)であるか ifptex14/ifuptex
nativeuptex13 TeXエンジンが“内部Unicode”動作のupTeX(派生含む)であるか
LaTeXエンジンがpLaTeXでなくupLaTeXであるか)
ifptex14/ifuptex
ptex13 TeXエンジンがpTeX(派生含む、特にupTeXも含む)であるか
LaTeXエンジンがpLaTeXまたはupLaTeXであるか)
ifptex14

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

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

:sushi: この他でも、もしあるパッケージが \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}

example1.png

その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}

example2.png

その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}

example3.png

まとめ

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

  1. ifthen の他には fontenc、inputenc、latexsym 等が「標準パッケージ」に含まれます。

  2. ちなみに、ifthen も含めて「標準パッケージ」は全て“普通のLaTeXユーザ”(文書作成者)が用いることを想定したものです。このことは usrguide で標準パッケージが紹介されていることから解ります。

  3. :sushi: 特に \or は危険で、\ifcase...\fi の中に \or が紛れ込んでしまうと誤動作の原因になってしまいます。

  4. そもそも \isodd なんて変な機能があるのはTeX言語に \ifodd というプリミティブが存在するからであり、TeXに \ifodd がある理由はページ番号の偶奇判定が必要だからです。

  5. \ifthenelseの命題の記述の中で式が使えるようにはならないことに注意。

  6. この場合、plus ... minus ...のような伸縮を付けることはできません。

  7. \NewDocumentCommand系の“新方式”のLaTeXマクロ定義命令で定義されたLaTeXマクロは展開されません(※:sushi:実態がprotectedなマクロだからです)。その代わりに“新方式”のマクロ定義では「展開可能なLaTeXマクロ」を定義するための専用の命令が用意されていて、\NewExpandableDocumentCommandのように命令名にExpandableがついています。

  8. 必須引数をもつLaTeXマクロも展開されますが、オプション引数をもつLaTeXマクロは「展開してはいけない」(つまり\equalの引数に入れてはいけない)という扱いになります。なお、\NewExpandableDocumentCommand系の「“新方式”の展開可能なLaTeXマクロ」はオプション引数をもつものも含めて展開されます。

  9. (u)pLaTeXエンジンでのみ利用可能です。

  10. かつてjsclassesバンドルの文書クラスで利用できましたが、ifdraftパッケージの機能と衝突するため、2016/07/13の版でこの機能は廃止されました。下書きモードであるかを判定したいときは、ifdraftパッケージを読み込んで、\ifdraft{«真»}{«偽»}としてください。

  11. TeX Live 2020以降であればiftexパッケージがpdfxetexluatexを(※:sushi:つまり\ifpdf\ifxetex\ifluatexを)全て提供します。このiftexは旧来のiftexとifpdf・ifxetex・ifluatexの各パッケージの機能を統合したものです。 2 3 4

  12. 「PDFモードである」というのは、エンジンがpdfTeXまたはLuaTeXであってかつPDFを書き出す状態である、ということ。

  13. pTeXupTeXnativeupTeXも使用できます。TeX Live 2017より前の古いifupTeXパッケージではupTeXnativeupTeXのみが使用可能でした。 2 3

  14. TeX Live 2017以降ではifptexパッケージが収録されていて、ifuptexパッケージは事実上ifptexの単なる別名になっています。 2 3

  15. 欧文LaTeXにはかなり昔から同様のすれが存在していて、欧文LaTeX(latexコマンド)は「pdfTeXエンジンのDVI出力モード動作」になっています。このため、pdftexはDVI出力の欧文LaTeXでも真と判定されます。

  16. もしTeX Live 2017より前の古い環境に対応したい場合は以下のようにします:①ifptexパッケージは存在しないかもしれないのでifuptexパッケージを使います。②nativeuptexは未サポートかもしれないのでnativeupTeXを使います。③ptexは未サポートかもしれません(残念ながら代替はありません)。

46
40
0

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
46
40