マクロツイーター

はてダから移行した記事の表示が崩れてますが、そのうちに直せればいいのに(えっ)

ラベルに紐づくカウンタ番号を取得する真っ当な方法

相互参照で使われる \ref\pageref の命令は、少なくとも LaTeX の仕様としては、参照先の番号やページ番号に「単純に展開される」わけではない。だから、\count255=\ref{foo} のようなコードの挙動は、厳密には未定義なのである。

それでは、\count255=\ref{foo} のようなことをしたい、すなわち、「ラベルに紐づいたカウンタ番号(やページ番号)のトークン列を直接取得したい」場合はどうすればよいか。本記事ではその方法について解説する。

それでもカウンタ番号を取得したい話

例えば次のような命令を実装したいとする。

  • \kansujiref{<ラベル>}: [頑強命令] ラベルに紐づいたカウンタ値を漢数字(一〇方式)で出力する。ただし、当該のカウンタ番号の表記は単純な算用数字の列(42 等)であると仮定する。(つまり、そのラベルに紐づいたカウンタが foo だとすると \thefoo の定義は \arabic{foo} であるとする。)ラベルが未定義の場合は \textbf{?} を代わりに出力する。

    ※横組と縦組が混在していて、カウンタ番号の表記そのものは算用数字であるが、縦組の部分でのカウンタ値は漢数字で出力したい、という状況を想定している。

想定される使用例と、その出力を挙げる。

% upLaTeX文書
\documentclass[uplatex,a5paper]{jsarticle}
\usepackage{plext,bxjalipsum}
\usepackage{mykansujiref}% ここで \kansujiref を定義
\begin{document}
\section{{\TeX}は激アレ}\label{sec:TeX}
なんとか。
\section{{\LaTeX}は微アレ}\label{sec:LaTeX}
かんとか。

\bigskip

\begin{minipage}<t>{10zw}% 部分的に縦組がある
\kansujiref{sec:LaTeX}節において、\jalipsumiroha\end{minipage}

\end{document}
(縦組の部分の出力)

以下にあげるのは“邪悪な”(つまり挙動未定義の)実装である。コレを書いてはイケナイ。

[mykansujiref.sty]
\@ifdefinable\kansujiref{
  \DeclareRobustCommand*\kansujiref[1]{% 頑強にする
    \expandafter\ifx\csname r@#1\endcsname\relax % ラベル未定義
      % 警告とかは略
      \textbf{}% 未定義時の出力
    \else
      \kansuji\ref{#1}\relax % 邪悪なコード
    \fi}
}

一応カウンタ番号を取得できる話

それでは“真っ当な”\kansujiref はどう書けばよいのか? マクロ \r@<ラベル> にカウンタ番号の情報が含まれているのでこれを直接用いればよい。\ref の処理を参考にすれば、実装は比較的容易である。

\@ifdefinable\kansujiref{
  \DeclareRobustCommand*\kansujiref[1]{%
    \expandafter\ifx\csname r@#1\endcsname\relax
      % 警告とかは略
      \textbf{}%
    \else
      \kansuji\expandafter\expandafter\expandafter
          \@firstoftwo\csname r@#1\endcsname\relax
    \fi}
}

先のものと異なりこの実装は“真っ当”であるが、しかし残念ながらあまり汎用的ではない。相互参照の機能を拡張するパッケージの中には、この内部マクロ \r@<ラベル> の規定を変更するもの(例えば hyperref や titleref など)がかなり多く存在する。汎用的なパッケージを作ろうとすると、これらのパッケージとの併用も考えなければならず、煩雑なことこの上ない。なんとかならないだろうか。

もっと汎用的にカウンタ番号を取得できる話

幸いにも、この「カウンタ番号を取得する」という機能については、それ専用の refcount というパッケージがある。refcount パッケージは以下の機能を提供する。

  • \setcounterref{<カウンタ>}{<ラベル>}:[LaTeX 命令] ラベルに紐づいたカウンタ値を(第 1 引数の)カウンタに代入する。
  • \addtocounterref{<カウンタ>}{<ラベル>}:[LaTeX 命令] ラベルに紐づいたカウンタ値を(第 1 引数の)カウンタの現在値に加算する。
    ※上記 2 つの命令では、ラベルに紐づいたカウンタ番号は算用数字でないといけない。ラベルに紐づいたカウンタと引数のカウンタは異なっていてもよい。
  • \getrefnumber{<ラベル>}:[完全展開可能] ラベルに紐づいたカウンタ番号のトークン列。
    ※「カウンタ値」でなくて「カウンタ番号」であることに注意。“xlii”等のローマ数字である可能性もある。
  • \setcounterpageref\addtocounterpageref\getpagerefnumber: 上記 3 つの命令の \pageref 版。
  • \setrefcountdefault{<トークン列>}:[通常] ラベルが未定義の場合に代わりに使われるトークン列(既定では 0)を変更する。
  • \refused{<ラベル>}:[通常] ラベルが参照されたことを LaTeX に通知する。同時に、ラベルが未定義の場合は警告を出す。
    ※完全展開可能の \getrefnumber では通知や警告ができないので、それと併用する。
  • \IfRefUndefinedExpandable{<ラベル>}{<真>}{<偽>}:[完全展開可能] ラベルが未定義であるかのテスト。

※[補足] LaTeX 標準の相互参照機能では、補助ファイルには「カウンタ番号」(表記)しか記録されないので、「カウンタ値」を得ることは原理的に無理である。refcount は LaTeX 標準の機能を利用するため、「カウンタ値」を得る機能は提供していない。ラベルに紐付く「カウンタ値」を取得したい場合は zref パッケージの利用が必要になる。

そして、一番大事なこととして、refcount は「相互参照を拡張するパッケージ」の有力なもの(hyperref、nameref、titleref)と併用可能である。

そういうわけで、\kansujiref のような命令を実装したい場合には、refcount の機能を使うといいであろう。これだと、hyperref や titleref とも共存できる。

[mykansujiref.sty]
\RequirePackage{refcount}

\@ifdefinable\kansujiref{
  \DeclareRobustCommand*\kansujiref[1]{%
    \refused{#1}% 通知とか未定義時警告とか
    \IfRefUndefinedExpandable{#1}{%
      \textbf{}% 未定義時の出力
    }{%else
      \kansuji\getrefnumber{#1}\relax
    }}
}