
he creado una preguntapreviamentesobre mi código, pero era demasiado grande y confuso para aislar adecuadamente mi problema (y el código ha cambiado mientras tanto). Aquí hay una nueva versión de mi pregunta sobre un ejemplo de juguetes muy simples que la hace más precisa y muy breve.
Intento crear una función que pueda llamarse con el mismo argumento como:
\myFunction{foo} some text \myFunction{foo}
pero necesito que esta función dé un resultado diferente en el segundo caso y, además, que se defina una etiqueta solo en el primer caso (para que un "\ref" haga referencia solo a la primera llamada del comando).
El resultado necesario:
"Foo is OK and labeled" some text "you have defined foo before, this is not labeled" !
He probado varias cosas que no dan este resultado de manera sólida. El resultado tiende a ser diferente según los entornos y/o las múltiples compilaciones.
Intenté usar el mecanismo de etiquetas porque tiene algunas advertencias integradas que son útiles y como parece que \label{foo} crea una variable r@foo, escribí:
\newcommand{\MyTesting}[1]
{
\ifcsname r@#1\endcsname
Already defined
\else
\label{#1}
\fi
}
El resultado de esto es... extraño, ya que parece que la etiqueta escribe en el archivo auxiliar (u otro archivo como este) una llamada simple como:
\MyTesting{test}
dará a través de las sucesivas recopilaciones:
- Es posible que la etiqueta haya cambiado; vuelva a ejecutarla para obtener la referencia cruzada correcta
- nada
- Es posible que la etiqueta haya cambiado; vuelva a ejecutarla para obtener la referencia cruzada correcta
- nada
- etc...
Entonces, el resultado parece cambiar una compilación en dos, lo cual no es el resultado deseado.
Pero aún así, en este punto no es crítico. Probémoslo con:
\MyTesting{test} some text \MyTesting{test}
Aquí tenemos, a través de la sucesiva recopilación:
- Es posible que la etiqueta haya cambiado; vuelva a ejecutarla para obtener la referencia cruzada correcta
- Etiqueta 'prueba' definida multiplicadamente
- Es posible que la etiqueta haya cambiado; vuelva a ejecutarla para obtener la referencia cruzada correcta
- Etiqueta 'prueba' definida multiplicadamente
- etc...
Aquí realmente no entiendo la lógica... incluso si la etiqueta se guarda en el auxiliar, la prueba al comienzo de \MyTesting debería evitar la definición múltiple.
Criterios de bonificación para las respuestas: la llamada de la función también debe ser sólida a través de un entorno como el título en la «figura» que parece evaluarse dos veces...
Acepto cualquier ayuda con este problema;)
El 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}
Respuesta1
Su prueba de \r@label
pruebas si la etiqueta ya está definidasegún el .aux
expediente. De hecho, \label
escribe \newlabel
llamadas al .aux
archivo. Este .aux
archivo se lee:
at
\enddocument
time, lo que permite a LaTeX advertir sobre etiquetas definidas múltiples veces;al inicio del documento, lo que permite que las
\newlabel
llamadas presentes en el.aux
archivo se definan\r@label
para cada etiqueta que se definió\label
durante la ejecución de compilación anterior.
De este modo:
Cuando su macro se encuentra
\r@test
definida, significa que fue llamada\label{test}
en elejecución de compilación anterior; imprimirá “ya definido” yno llamará\label{test}
en esta carrera, y esto es válido paratodoinvocaciones de su macro con argumentotest
durante esta ejecución de compilación.La próxima vez que compile, el
.aux
archivo no tendrá ninguna\newlabel
llamada para labeltest
, por lo tanto su macro siempre encontrará que\r@test
no está definido y siempre llamará\label{test}
en esta ejecución de compilación, de ahí la advertencia "Etiqueta 'prueba' definida múltiplesmente" cada vez. momento en que se llama a su macro con un argumentotest
durante esta ejecución de compilación. Las\label{test}
llamadas escriben\newlabel
llamadastest
en el.aux
archivo, por lo tanto, en la próxima ejecución de compilación, volveremos al paso 1.
Creo que lo que quieres es lo siguiente. La \ifx\protect\@typeset@protect
prueba nos permite asegurarnos de que no se filtre nada en los subtítulos de las figuras.en la lista de tablas o lista de figuras(La prueba es verdadera durante la composición tipográfica, pero no cuando los subtítulos se escriben en los archivos .lot
o .lof
mediante \addtocontents
—este último usa \protected@write
, lo que temporalmente hace \protect
\let
-igual a \@unexpandable@protect
).
Editar: bueno, debido a cómo floatrow
funciona, lidiar con \MyLabel
el interior de un título floatrow
es mucho más complicado que eso, pero lo siguiente parece funcionar bien. Tenga en cuenta que se necesitan varias ejecuciones de compilación para que las etiquetas se estabilicen.
\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}
Cómo funciona
Cuidado, esto es un poco técnico. El principal problema que tuvimos floatrow
es que escribe el texto del título muchas veces con \protect
un valor igual a \@typeset@protect
(¡5 veces para un solo título en mi prueba!). De hecho, parece medirlo de varias maneras antes de decidir enviarlo. Entonces, para cada particularetiqueta especial(aquellos declarados \myInit
y utilizados en \MyLabel
), debemos detectar la primera vez a dónde se envía (es decir, se envía al archivo DVI o PDF) ysolo por esta vezusar \label
. Para momentos anteriores, no debemos generar nada (de lo contrario, podríamos alterar las mediciones) y para momentos posteriores, debemos generar "ya definido" como se solicita en la pregunta, pero sin \label
llamada.
Ahora bien, ¿cómo se \MyLabel
detecta cuándo se envía una etiqueta determinada por primera vez? Para cada etiqueta, cuenta el número de veces que se llama en modo de composición tipográfica ( \protect
igual a \@typeset@protect
) y \write
envía el valor correspondiente del contador al .aux
archivo (este es el value
in \my@MaybeDefine{special label}{value}
). Este es el truco principal. una \write
es unaque es eso(cf. TeXbook), por lo tanto, algo que se mete dentro de las cajas, ysolo da como resultado una escritura real en un archivo si se envía la caja que contiene el whatsit. Entonces, las llamadas ficticias utilizadas por floatrow
u otros paquetes para medir el texto del título y demás se manejan de esta manera: sin envío, sin escritura en el .aux
archivo. El value
primer \my@MaybeDefine{special label}{value}
escrito en el .aux
archivo indica que \MyLabel
se usó por primera vez con el primer argumento special label
dentro de una caja que se envió. Entonces, cuando el contador interno for special label
es igual a este primer valor, asumiendo que el archivo fuente no cambió desde la última compilación, esto significa que el material que contiene special label
se está tipografiando “de verdad” por primera vez.
Una cosa más: debido a los flotadores (tablas, figuras...), es posible que algún material asociado a unetiqueta especialescribirse (incluso con \protect
igual a \@typeset@protect
) antes que el \label
comando para eletiqueta especial, pero aparecen más adelante en el archivo de salida. En tales casos, el contador interno asociado aletiqueta especialtendría un valor inferior al "buen valor" cuando el material se está componiendo para la publicación inicial, pero aún así, necesita el texto "ya definido" ya que el material aparecerá más tarde que el \label
. Por esta razón, agregué etiquetas internas y cuando el contador interno es diferente del "buen valor", comparo la página en la que aparece la etiqueta interna, si es que aparece, con la página donde se \label{special label}
encuentra. Cuando el contenido no se envía ( floatrow
haciendo mediciones, etc.), las etiquetas internas correspondientes no se definen, de ahí el texto, que modifiqué a "la etiqueta especial se definió anteriormente en la misma página" y "la etiqueta especial fue definida anteriormente en la misma página". definido en una página anterior” no altera las mediciones (ver código).
¡Sí, esto es un poco hack!