
Ich habe eine Frage erstelltvorherüber meinen Code, aber er war zu groß und unscharf, um mein Problem richtig zu isolieren (und der Code hat sich inzwischen geändert). Hier ist eine neue Version meiner Frage anhand eines sehr einfachen Spielzeugbeispiels, die sie präziser und sehr kurz macht.
Ich versuche, eine Funktion zu erstellen, die mit demselben Argument aufgerufen werden kann, wie:
\myFunction{foo} some text \myFunction{foo}
aber ich brauche, dass diese Funktion im zweiten Fall ein anderes Ergebnis liefert und außerdem, dass nur im ersten Fall ein Label definiert ist (damit ein "\ref" nur auf den ersten Aufruf des Befehls verweist).
Das benötigte Ergebnis:
"Foo is OK and labeled" some text "you have defined foo before, this is not labeled" !
Ich habe mehrere Dinge ausprobiert, die dieses Ergebnis nicht zuverlässig liefern. Das Ergebnis ist je nach Umgebung und/oder mehreren Kompilierungen tendenziell unterschiedlich.
Ich habe versucht, den Label-Mechanismus zu verwenden, da dieser einige nützliche Warnungen integriert hat und da es so aussieht, als ob \label{foo} eine Variable r@foo erstellt, habe ich Folgendes geschrieben:
\newcommand{\MyTesting}[1]
{
\ifcsname r@#1\endcsname
Already defined
\else
\label{#1}
\fi
}
Das Ergebnis ist ... seltsam, da es so aussieht, als würde das Label in die AUX-Datei (oder eine andere Datei dieser Art) einen einfachen Aufruf wie den folgenden schreiben:
\MyTesting{test}
wird durch die aufeinander folgenden Zusammenstellungen geben:
- Das Etikett hat sich möglicherweise geändert. Führen Sie den Vorgang erneut aus, um den Querverweis richtig zu stellen.
- Nichts
- Das Etikett hat sich möglicherweise geändert. Führen Sie den Vorgang erneut aus, um den Querverweis richtig zu stellen.
- Nichts
- usw...
Das Ergebnis scheint also darin zu bestehen, dass sich jede zweite Kompilierung ändert, was jedoch nicht dem gewünschten Ergebnis entspricht.
Aber an diesem Punkt ist es noch nicht kritisch. Testen wir es mit:
\MyTesting{test} some text \MyTesting{test}
Hier haben wir, durch die sukzessive Zusammenstellung:
- Das Etikett hat sich möglicherweise geändert. Führen Sie den Vorgang erneut aus, um den Querverweis richtig zu stellen.
- Label 'Test' mehrfach definiert
- Das Etikett hat sich möglicherweise geändert. Führen Sie den Vorgang erneut aus, um den Querverweis richtig zu stellen.
- Label 'Test' mehrfach definiert
- usw...
Hier erschließt sich mir die Logik nicht so ganz... auch wenn das Label im Aux gespeichert wird, sollte der Test am Anfang des \MyTesting die Mehrfachdefinition verhindern.
Bonuskriterium für die Antworten: Der Aufruf der Funktion sollte auch gegenüber Umgebungen wie der Beschriftung in „Abbildung“ robust sein, die anscheinend zweimal ausgewertet werden …
Ich bin bei diesem Problem für jede Hilfe dankbar ;)
Das 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}
Antwort1
Ihr Test prüft \r@label
, ob das Label bereits definiert istgemäß der .aux
Datei. Tatsächlich \label
schreibt \newlabel
Aufrufe in die .aux
Datei. Diese .aux
Datei wird gelesen:
zu
\enddocument
der Zeit, die es LaTeX ermöglicht, vor mehrfach definierten Beschriftungen zu warnen;beim Beginn des Dokuments, wodurch die
\newlabel
in der Datei vorhandenen Aufrufe für jedes Etikett.aux
definiert werden können, das während des vorherigen Kompilierungslaufs\r@label
definiert wurde .\label
Daher:
Wenn Ihr Makro
\r@test
definiert ist, bedeutet dies, dass es aufgerufen wurde\label{test}
invorheriger Kompilierungslauf; es wird „bereits definiert“ gedruckt undwerde nicht anrufen\label{test}
in diesem Lauf, und das gilt füralleAufrufe Ihres Makros mit Argumententest
während dieses Kompilierungslaufs.Beim nächsten Kompilieren wird die
.aux
Datei keinen\newlabel
Aufruf für label enthaltentest
, daher wird Ihr Makro immer feststellen, dass\r@test
nicht definiert ist, und wird bei diesem Kompilierungslauf immer aufgerufen , daher wird jedes Mal, wenn Ihr Makro während dieses Kompilierungslaufs\label{test}
mit Argument aufgerufen wird, die Warnung „Label 'test' mehrfach definiert“ ausgegeben . Die Aufrufe schreiben Aufrufe für in die Datei, daher sind wir beim nächsten Kompilierungslauf wieder bei Schritt 1.test
\label{test}
\newlabel
test
.aux
Ich glaube, Sie wollen Folgendes. Mit dem \ifx\protect\@typeset@protect
Test können wir sicherstellen, dass nichts in die Bildunterschriften gelangt.im Tabellen- bzw. Abbildungsverzeichnis.lot
(der Test ist während des Schriftsatzes wahr, aber nicht, wenn Beschriftungen über in die - oder .lof
-Dateien geschrieben werden \addtocontents
. Letzteres verwendet \protected@write
, was - vorübergehend \protect
\let
gleich macht \@unexpandable@protect
).
Bearbeiten: Nun, aufgrund der Funktionsweise ist floatrow
der Umgang mit \MyLabel
innerhalb einer Beschriftung floatrow
viel komplizierter, aber das Folgende scheint gut zu funktionieren. Beachten Sie, dass mehrere Kompilierungsläufe erforderlich sind, damit Beschriftungen stabil bleiben.
\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}
Wie es funktioniert
Achtung, das ist ein bisschen technisch. Das Hauptproblem, das wir damit hatten, floatrow
ist, dass der Untertiteltext viele Male mit „ \protect
gleich“ gesetzt wird \@typeset@protect
(5 Mal für einen einzelnen Untertitel in meinem Test!). Tatsächlich scheint es ihn auf verschiedene Weise zu messen, bevor entschieden wird, ihn auszuliefern. Also, für jeden einzelnenSonderetikett(die mit deklariert \myInit
und in verwendet werden \MyLabel
), müssen wir das erste Mal erkennen, wo es versendet wird (d. h. an die DVI- oder PDF-Datei gesendet wird) undnur für dieses malverwenden \label
. Für frühere Zeitpunkte dürfen wir nichts ausgeben (sonst könnten wir die Messungen stören) und für spätere Zeitpunkte müssen wir wie in der Frage gefordert „bereits definiert“ ausgeben, aber keinen \label
Aufruf.
Wie erkennt man nun, \MyLabel
wann ein bestimmtes Etikett zum ersten Mal verschickt wird? Für jedes Etikett zählt es, wie oft es im Satzmodus aufgerufen wird ( \protect
gleich \@typeset@protect
) und \write
fügt den entsprechenden Wert des Zählers in die .aux
Datei ein (das ist die value
in \my@MaybeDefine{special label}{value}
). Das ist der Haupttrick. A \write
ist einWas ist es(vgl. TeXbook), also etwas, das in Kisten hineinkommt, undes wird nur dann tatsächlich in eine Datei geschrieben, wenn die Schachtel mit dem Dingsbums verschickt wird. Die von oder anderen Paketen verwendeten Dummy-Aufrufe floatrow
zum Messen des Untertiteltexts und dergleichen werden also folgendermaßen behandelt: kein Versand, kein Schreiben in die .aux
Datei. Das value
in der ersten \my@MaybeDefine{special label}{value}
in die Datei geschriebenen .aux
Datei gibt an, dass es zum ersten Mal \MyLabel
mit dem ersten Argument in einer Box verwendet wurde special label
, die versandt wurde. Wenn also der interne Zähler special label
für diesem ersten Wert entspricht (vorausgesetzt, die Quelldatei hat sich seit der letzten Kompilierung nicht geändert), bedeutet dies, dass das enthaltene Material special label
zum ersten Mal „wirklich“ gesetzt wird.
Noch etwas: Aufgrund von Floats (Tabellen, Abbildungen...) ist es möglich, dass einige Materialien zu einemSonderetikettmuss (auch mit \protect
gleich \@typeset@protect
) vor dem \label
Befehl für dasSonderetikett, erscheinen aber später in der Ausgabedatei. In solchen Fällen wird der interne Zähler, der demSonderetiketthätte einen Wert, der niedriger ist als der „gute Wert“, wenn das Material für den frühen Float gesetzt wird, aber es braucht trotzdem den „bereits definierten“ Text, da das Material später erscheinen wird als das \label
. Aus diesem Grund habe ich interne Beschriftungen hinzugefügt und wenn der interne Zähler vom „guten Wert“ abweicht, vergleiche ich die Seite, auf der die interne Beschriftung erscheint, wenn sie überhaupt erscheint, mit der Seite, auf der das \label{special label}
gefunden wird. Wenn Inhalte nicht versendet werden ( floatrow
Messungen durchführen usw.), werden die entsprechenden internen Beschriftungen nicht definiert, daher stört der Text, den ich in „die spezielle Beschriftung ist früher auf derselben Seite definiert“ und „die spezielle Beschriftung wurde auf einer früheren Seite definiert“ geändert habe, die Messungen nicht (siehe Code).
Ja, das ist ein bisschen hackig!