使い方

使い方

質問を作成しました以前私のコードについてですが、大きすぎて不明瞭で、問題を適切に切り分けることができませんでした (その間にコードが変更されました)。ここでは、非常に単純なおもちゃの例に関する私の質問の新しいバージョンを示します。これにより、質問はより正確で短くなります。

次のように同じ引数で呼び出すことができる関数を作成してみます:

\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. 何もない
  3. ラベルが変更された可能性があります。相互参照を正しくするために再実行してください。
  4. 何もない
  5. 等...

したがって、結果は 1 つのコンパイルが 2 つに変更されるように見えますが、これは望ましい結果ではありません。

しかし、この時点ではまだ重要ではありません。次のようにテストしてみましょう。

\MyTesting{test} some text \MyTesting{test}

連続した編集により、次のようになりました。

  1. ラベルが変更された可能性があります。相互参照を正しくするために再実行してください。
  2. ラベル「テスト」が複数定義されています
  3. ラベルが変更された可能性があります。相互参照を正しくするために再実行してください。
  4. ラベル「テスト」が複数定義されています
  5. 等...

ここでは、ロジックがよくわかりません...ラベルが 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ファイルによると. 確かに、\labelwrites はファイル\newlabelへの呼び出しを行います.aux。この.auxファイルは次のように読み取られます。

  • これによって\enddocument、LaTeX は複数定義されたラベルについて警告できるようになります。

  • ドキュメントの開始時に、ファイル\newlabel内に存在する呼び出しが、前回のコンパイル実行中に定義された各ラベルを.aux定義できるようになります。\r@label\label

したがって:

  1. マクロが定義されている場合、それ\r@test\label{test}前回のコンパイル実行; 「すでに定義済み」と表示され、電話しない \label{test}この実行では、これは全てtestこのコンパイル実行中に引数を指定してマクロを呼び出します。

  2. 次回コンパイルするときには、ファイルにはラベル の呼び出し.auxがないので、マクロは常にが定義されていないことを検出し、このコンパイル実行では常に を呼び出します。そのため、このコンパイル実行中にマクロが引数とともに呼び出されるたびに、「ラベル 'test' が複数定義されています」という警告が出力されます。呼び出しはの呼び出しをファイルに書き込むため、次回のコンパイル実行では、手順 1 に戻ります。\newlabeltest\r@test\label{test}test\label{test}\newlabeltest.aux

あなたが望んでいるのは次のことだと私は思います。\ifx\protect\@typeset@protectテストにより、図のキャプションに何も漏れていないことを確認できます。表のリストまたは図のリスト(テストはタイプセット中は真ですが、キャプションが.lotまたは.lofファイルに 経由で書き込まれるときは真ではありません\addtocontents。後者は を使用するため\protected@write、一時的に\protect \letが と等しくなります\@unexpandable@protect)。

編集: 動作の仕組み上、キャプション内のfloatrowの処理はそれよりもはるかに複雑ですが、次の方法は問題なく動作するようです。ラベルを安定させるには、コンパイルを複数回実行する必要があることに注意してください。\MyLabelfloatrow

\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初めてであることを示します。したがって、 の内部カウンターがこの最初の値に等しい場合、ソース ファイルが最後のコンパイル以降変更されていないと仮定すると、 を含む素材が初めて「実際に」タイプセットされていることを意味します。\MyLabelspecial labelspecial labelspecial label

もう1つ:フロート(表、図など)のせいで、特別なラベルのコマンドよりも先にタイプセットされる(\protect等しい場合でも\@typeset@protect\label特別なラベル、しかし出力ファイルの後半に現れる。このような場合、特別なラベルマテリアルが早期フロート用にタイプセットされているときは、 は「適切な値」よりも低い値になりますが、マテリアルは よりも後に表示されるため、「定義済み」テキストが必要です\label。このため、内部ラベルを追加し、内部カウンタが「適切な値」と異なる場合は、内部ラベルが表示されるページ (表示される場合) を があるページと比較します\label{special label}。コンテンツが出荷されない場合 (floatrow測定など)、対応する内部ラベルは定義されないため、私が「特別なラベルは同じページで以前に定義されています」および「特別なラベルは以前のページで定義されました」に変更したテキストは測定に影響しません (コードを参照)。

はい、これはちょっとハックっぽいですね!

関連情報