
質問を作成しました以前私のコードについてですが、大きすぎて不明瞭で、問題を適切に切り分けることができませんでした (その間にコードが変更されました)。ここでは、非常に単純なおもちゃの例に関する私の質問の新しいバージョンを示します。これにより、質問はより正確で短くなります。
次のように同じ引数で呼び出すことができる関数を作成してみます:
\myFunction{foo} some text \myFunction{foo}
しかし、この関数は 2 番目のケースでは異なる結果を返す必要があり、さらに、ラベルは最初のケースでのみ定義される必要があります (「\ref」がコマンドの最初の呼び出しのみを参照するようにするため)。
必要な結果:
"Foo is OK and labeled" some text "you have defined foo before, this is not labeled" !
いくつか試してみましたが、この結果は確実に得られませんでした。結果は環境や複数のコンパイルによって異なる傾向があります。
ラベル メカニズムを使用しようとしたのは、便利な警告がいくつか統合されており、\label{foo} が変数 r@foo を作成するように見えるため、次のように記述したためです。
\newcommand{\MyTesting}[1]
{
\ifcsname r@#1\endcsname
Already defined
\else
\label{#1}
\fi
}
この結果は...奇妙です。ラベルが aux ファイル (またはこれに似た他のファイル) に次のような単純な呼び出しを書き込むようです。
\MyTesting{test}
連続した編集を通して以下を提供します。
- ラベルが変更された可能性があります。相互参照を正しくするために再実行してください。
- 何もない
- ラベルが変更された可能性があります。相互参照を正しくするために再実行してください。
- 何もない
- 等...
したがって、結果は 1 つのコンパイルが 2 つに変更されるように見えますが、これは望ましい結果ではありません。
しかし、この時点ではまだ重要ではありません。次のようにテストしてみましょう。
\MyTesting{test} some text \MyTesting{test}
連続した編集により、次のようになりました。
- ラベルが変更された可能性があります。相互参照を正しくするために再実行してください。
- ラベル「テスト」が複数定義されています
- ラベルが変更された可能性があります。相互参照を正しくするために再実行してください。
- ラベル「テスト」が複数定義されています
- 等...
ここでは、ロジックがよくわかりません...ラベルが aux に保存されている場合でも、\MyTesting の先頭のテストによって複数の定義が防止されるはずです。
回答のボーナス基準: 関数の呼び出しは、2 回評価されると思われる「図」のキャプションなどの環境でも堅牢である必要があります。
この問題に関してどんな助けでも歓迎します ;)
MWE:
%%%% work with koma-script, should also work on standard classes %%%%
\documentclass{book}
\usepackage[english]{babel}
\usepackage{lmodern}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\usepackage{graphicx} % only for testing
\usepackage{floatrow} % for testing
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\makeatletter
\newcommand{\MyLabel}[1]
{
\ifcsname r@#1\endcsname
Already defined
\else
\label{#1}
\fi
}
\makeatother
%%%%%% begin %%%%%%%
\begin{document}
%%%%%% TEST %%%%%%
\chapter{TEST}
\section{Introduction}
Try to label a first time \MyLabel{Firsttest}
Try to label a second time with the same \MyLabel{Firsttest}
%%% for testing in a caption, you can uncomment this part of code %%%
%\begin{figure}[h]
%\centering
%\includegraphics[scale=0.2]{images/Tux.png}
%\caption{A caption}%
%\end{figure}
%%% for testing in a floatrow, you can uncomment this part of code %%%
%\begin{figure}[ht]
% \centering
% {
% \begin{floatrow}[1]
% \ffigbox[\FBwidth]{\caption{A caption}}{\includegraphics[scale=0.3]{images/Tux.png}}
% \end{floatrow}
% }
%\end{figure}
\end{document}
答え1
\r@label
ラベルがすでに定義されているかどうかをテストするテスト.aux
ファイルによると. 確かに、\label
writes はファイル\newlabel
への呼び出しを行います.aux
。この.aux
ファイルは次のように読み取られます。
これによって
\enddocument
、LaTeX は複数定義されたラベルについて警告できるようになります。ドキュメントの開始時に、ファイル
\newlabel
内に存在する呼び出しが、前回のコンパイル実行中に定義された各ラベルを.aux
定義できるようになります。\r@label
\label
したがって:
マクロが定義されている場合、それ
\r@test
は\label{test}
前回のコンパイル実行; 「すでに定義済み」と表示され、電話しない\label{test}
この実行では、これは全てtest
このコンパイル実行中に引数を指定してマクロを呼び出します。次回コンパイルするときには、ファイルにはラベル の呼び出し
.aux
がないので、マクロは常にが定義されていないことを検出し、このコンパイル実行では常に を呼び出します。そのため、このコンパイル実行中にマクロが引数とともに呼び出されるたびに、「ラベル 'test' が複数定義されています」という警告が出力されます。呼び出しはの呼び出しをファイルに書き込むため、次回のコンパイル実行では、手順 1 に戻ります。\newlabel
test
\r@test
\label{test}
test
\label{test}
\newlabel
test
.aux
あなたが望んでいるのは次のことだと私は思います。\ifx\protect\@typeset@protect
テストにより、図のキャプションに何も漏れていないことを確認できます。表のリストまたは図のリスト(テストはタイプセット中は真ですが、キャプションが.lot
または.lof
ファイルに 経由で書き込まれるときは真ではありません\addtocontents
。後者は を使用するため\protected@write
、一時的に\protect
\let
が と等しくなります\@unexpandable@protect
)。
編集: 動作の仕組み上、キャプション内のfloatrow
の処理はそれよりもはるかに複雑ですが、次の方法は問題なく動作するようです。ラベルを安定させるには、コンパイルを複数回実行する必要があることに注意してください。\MyLabel
floatrow
\documentclass{article}
\usepackage{etoolbox}
\usepackage{refcount}
\usepackage{graphicx} % only for testing
\usepackage{floatrow} % only for testing
\makeatletter
\newcommand*{\myInit}[1]{%
\renewcommand*{\do}[1]{\newcounter{mycount@##1}}%
\docsvlist{#1}%
\AtBeginDocument{%
\renewcommand*{\do}[1]{%
\ifcsundef{my@goodvalue@##1}{\def\@currentlabel{??}\label{##1}}{}}%
\docsvlist{#1}%
}%
}
\newcommand*{\my@MaybeDefine}[2]{%
\ifcsundef{my@goodvalue@#1}{\csgdef{my@goodvalue@#1}{#2}}{}%
}
\newcommand*{\my@WriteCtr}[2]{%
\write\@auxout{\string\my@MaybeDefine{#1}{#2}}%
}
\newcommand*{\MyLabel}[2]{%
\ifx\protect\@typeset@protect
\stepcounter{mycount@#1}%
\edef\my@internal@label{my@internal@label@#1@\number\value{mycount@#1}}%
\ifcsdef{my@goodvalue@#1}
{\ifnum\value{mycount@#1}=\csuse{my@goodvalue@#1}
\refstepcounter{#2}%
\label{#1}%
\else
\IfRefUndefinedBabel{#1}{}{% Ref #1 is defined
\IfRefUndefinedBabel{\my@internal@label}
{}
{%
\ifnum\getpagerefnumber{\my@internal@label}=\getpagerefnumber{#1}
the special label is defined earlier on the same page%
\else
\ifnum\getpagerefnumber
{\my@internal@label}>\getpagerefnumber{#1}
the special label was defined on an earlier page%
\fi
\fi
}%
}%
\fi
}
{\typeout{You need to rerun LaTeX for the special labels.}}%
\label{\my@internal@label}%
\begingroup
\edef\tmp{\endgroup\noexpand\my@WriteCtr{#1}{\number\value{mycount@#1}}}%
\tmp
\fi
}
\makeatother
\myInit{First-test, Second-test} % The special labels
\newcounter{example}
\setcounter{example}{0} % not really needed: this is done implicitly
\begin{document}
\listoffigures
\section{Introduction}
Try to label a first time\MyLabel{First-test}{example}.
Try to label a second time with the same: \MyLabel{First-test}{example}.
Label \verb|First-test| is on page~\pageref{First-test} and corresponds to
value~\ref{First-test} of the \verb|example| counter. Label \verb|Second-test|
is on page~\pageref{Second-test} and corresponds to value~\ref{Second-test} of
the \verb|example| counter.
\begin{figure}
\centering
\includegraphics[scale=0.2]{example-image-a}
\caption{A caption.}
\end{figure}
\begin{table}[p]
\centering
Some floating material that will appear late in the PDF output:
\MyLabel{Second-test}{example}.%
\label{a-table}%
\caption{A table environment}
\end{table}
\begin{figure}[ht]
\centering
\begin{floatrow}[1]
\ffigbox[\FBwidth]
{\caption{Another caption\MyLabel{Second-test}{example}}}
{\includegraphics[scale=0.3]{example-image-b}}
\end{floatrow}
\end{figure}
Calling \verb|\MyLabel{Second-test}{example}| a third time:
\MyLabel{Second-test}{example}.
\end{document}
使い方
注意してください、これは少し技術的な話です。私たちが抱えていた主な問題floatrow
は、キャプションテキストが何度も\protect
等しくタイプセットされることです\@typeset@protect
(私のテストでは1つのキャプションに5回も!)。実際、出荷を決定する前にいくつかの方法で測定しているようです。したがって、特定のキャプションごとに特別なラベル( で宣言され\myInit
、 で使用されるもの\MyLabel
)では、最初にどこに発送されるか(つまり、DVIまたはPDFファイルに送信されるか)を検出する必要があります。今回だけを使用します\label
。前の時間については、何も出力してはなりません (そうしないと、測定を妨げる可能性があります)。後の時間については、質問で要求されているように「定義済み」を出力する必要がありますが、\label
呼び出しは出力しません。
\MyLabel
さて、 はどのようにして特定のラベルが初めて出荷されたことを検出するのでしょうか。各ラベルについて、タイプセッティングモードで呼び出された回数(\protect
に等しい\@typeset@protect
)をカウントし、\write
対応するカウンタの値を.aux
ファイル( のvalue
)に格納します\my@MaybeDefine{special label}{value}
。これが主なトリックです。 A\write
はそれは何です(TeXbook参照)つまり箱の中に入っていくもの、そして箱が出荷された場合にのみ、ファイルに実際に書き込みが行われます。floatrow
. したがって、キャプション テキストなどを計測するために や他のパッケージによって使用されるダミー呼び出しは、次のように処理されます。送信も.aux
ファイルへの書き込みもありません。ファイルにvalue
最初に\my@MaybeDefine{special label}{value}
書き込まれたは、送信されたボックス内で最初の引数とともに使用されたのが.aux
初めてであることを示します。したがって、 の内部カウンターがこの最初の値に等しい場合、ソース ファイルが最後のコンパイル以降変更されていないと仮定すると、 を含む素材が初めて「実際に」タイプセットされていることを意味します。\MyLabel
special label
special label
special label
もう1つ:フロート(表、図など)のせいで、特別なラベルのコマンドよりも先にタイプセットされる(\protect
等しい場合でも\@typeset@protect
)\label
特別なラベル、しかし出力ファイルの後半に現れる。このような場合、特別なラベルマテリアルが早期フロート用にタイプセットされているときは、 は「適切な値」よりも低い値になりますが、マテリアルは よりも後に表示されるため、「定義済み」テキストが必要です\label
。このため、内部ラベルを追加し、内部カウンタが「適切な値」と異なる場合は、内部ラベルが表示されるページ (表示される場合) を があるページと比較します\label{special label}
。コンテンツが出荷されない場合 (floatrow
測定など)、対応する内部ラベルは定義されないため、私が「特別なラベルは同じページで以前に定義されています」および「特別なラベルは以前のページで定義されました」に変更したテキストは測定に影響しません (コードを参照)。
はい、これはちょっとハックっぽいですね!