
Eu criei uma perguntaanteriormentesobre meu código, mas era muito grande e confuso para isolar adequadamente meu problema (e o código mudou entretanto). Aqui está uma nova versão da minha pergunta sobre um exemplo de brinquedos muito simples que a torna mais precisa e muito curta.
Tento fazer uma função que possa ser chamada com o mesmo argumento como:
\myFunction{foo} some text \myFunction{foo}
mas preciso que esta função dê um resultado diferente no segundo caso e, além disso, que um rótulo seja definido apenas no primeiro caso (para que um "\ref" faça referência apenas à primeira chamada do comando).
O resultado necessário:
"Foo is OK and labeled" some text "you have defined foo before, this is not labeled" !
Eu tentei várias coisas que não dão esse resultado de forma robusta. O resultado tende a ser diferente dependendo dos ambientes e/ou de múltiplas compilações.
Tentei usar o mecanismo de rótulo porque ele tem alguns avisos integrados que são úteis e como parece que \label{foo} crie uma variável r@foo que escrevi:
\newcommand{\MyTesting}[1]
{
\ifcsname r@#1\endcsname
Already defined
\else
\label{#1}
\fi
}
O resultado disso é ... estranho, pois parece que o rótulo escreve no arquivo aux (ou outro arquivo como este) uma chamada simples como:
\MyTesting{test}
dará através das sucessivas compilações:
- O rótulo pode ter mudado. Execute novamente para obter a referência cruzada correta
- nada
- O rótulo pode ter mudado. Execute novamente para obter a referência cruzada correta
- nada
- etc...
Então, o resultado parece mudar uma compilação em duas, o que não é o resultado desejado.
Mas ainda assim, neste ponto não é crítico. Vamos testar com:
\MyTesting{test} some text \MyTesting{test}
Aqui temos, através da compilação sucessiva:
- O rótulo pode ter mudado. Execute novamente para obter a referência cruzada correta
- Rótulo 'teste' multiplicado definido
- O rótulo pode ter mudado. Execute novamente para obter a referência cruzada correta
- Rótulo 'teste' multiplicado definido
- etc...
Aqui eu realmente não entendi a lógica ... mesmo que o rótulo seja salvo no aux, o teste no início do \MyTesting deve impedir a definição múltipla.
Critérios de bônus para as respostas: A chamada da função também deve ser robusta por meio de ambiente como legenda em «figura» que parece ser avaliada duas vezes....
Aceito qualquer ajuda neste problema ;)
O 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}
Responder1
Seu teste de \r@label
testes se o rótulo já está definidode acordo com o .aux
arquivo. Na verdade, \label
grava \newlabel
chamadas no .aux
arquivo. Este .aux
arquivo é lido:
às
\enddocument
vezes, o que permite ao LaTeX avisar sobre rótulos definidos de forma múltipla;no início do documento, o que permite que as
\newlabel
chamadas presentes no.aux
arquivo sejam definidas\r@label
para cada rótulo que foi definido\label
durante a execução de compilação anterior.
Por isso:
Quando sua macro estiver
\r@test
definida, significa que ela foi chamada\label{test}
noexecução de compilação anterior; irá imprimir “já definido” enão vou ligar\label{test}
nesta corrida, e isso vale paratodosinvocações de sua macro com argumentotest
durante esta execução de compilação.Na próxima vez que você compilar, o
.aux
arquivo não terá nenhuma\newlabel
chamada para labeltest
, portanto, sua macro sempre descobrirá que\r@test
não está definido e sempre chamará\label{test}
nesta execução de compilação, daí a saída de aviso “Label 'test' multiplamente definida” a cada vez que sua macro é chamada com argumentotest
durante a execução da compilação. As\label{test}
chamadas escrevem\newlabel
chamadas fortest
no.aux
arquivo, portanto, na próxima compilação, voltaremos ao passo 1.
Acredito que o que você quer é o seguinte. O \ifx\protect\@typeset@protect
teste nos permite garantir que nada vaze nas legendas das figurasna lista de tabelas ou lista de figuras(o teste é verdadeiro durante a composição, mas não quando as legendas são gravadas nos arquivos .lot
ou por meio de —o último usa , o que temporariamente torna -equal para )..lof
\addtocontents
\protected@write
\protect
\let
\@unexpandable@protect
Editar: bem, pela forma como floatrow
funciona, lidar com \MyLabel
uma legenda floatrow
é muito mais complicado do que isso, mas o seguinte parece funcionar bem. Observe que são necessárias várias execuções de compilação para que os rótulos se estabilizem.
\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}
Como funciona
Cuidado, isso é um pouco técnico. O principal problema que tivemos floatrow
é que ele compõe o texto da legenda muitas vezes \protect
igual a \@typeset@protect
(5 vezes para uma única legenda no meu teste!). Na verdade, parece medi-lo de algumas maneiras antes de decidir enviá-lo. Então, para cada particularetiqueta especial(aqueles declarados \myInit
e usados em \MyLabel
), precisamos detectar a primeira vez em que ele é enviado (ou seja, enviado para o arquivo DVI ou PDF) esó por esta vezusar \label
. Para horários anteriores, não devemos gerar nada (caso contrário, poderemos atrapalhar as medições) e para horários posteriores, precisamos gerar “já definido” conforme solicitado na pergunta, mas nenhuma \label
chamada.
Agora, como \MyLabel
detecta quando uma determinada etiqueta está sendo enviada pela primeira vez? Para cada rótulo, conta o número de vezes que é chamado no modo de composição ( \protect
igual a \@typeset@protect
) e \write
é o valor correspondente do contador ao .aux
arquivo (este é o value
in \my@MaybeDefine{special label}{value}
). Este é o truque principal. Um \write
é umo que é isso(cf. TeXbook), portanto, algo que entra nas caixas, eisso só resulta em uma gravação real em um arquivo se a caixa que contém o whatsit for enviada. Portanto, as chamadas fictícias usadas por floatrow
ou outros pacotes para medir o texto da legenda e outros enfeites são tratadas desta maneira: sem envio, sem gravação no .aux
arquivo. O value
primeiro \my@MaybeDefine{special label}{value}
escrito no .aux
arquivo indica a primeira vez que \MyLabel
foi usado com o primeiro argumento special label
dentro de uma caixa que foi enviada. Então, quando o contador interno for special label
é igual a este primeiro valor, assumindo que o arquivo fonte não mudou desde a última compilação, isso significa que o material que contém special label
está sendo digitado “de verdade” pela primeira vez.
Mais uma coisa: por causa dos carros alegóricos (tabelas, figuras...), é possível que algum material associado a umetiqueta especialser digitado (mesmo com \protect
igual a \@typeset@protect
) antes do \label
comando para oetiqueta especial, mas aparecem posteriormente no arquivo de saída. Nestes casos, o contador interno associado aoetiqueta especialteria um valor inferior ao “valor bom” quando o material está sendo composto para o float inicial, mas ainda assim, precisa do texto “já definido”, pois o material aparecerá depois do \label
. Por isso adicionei etiquetas internas e quando o contador interno é diferente do “valor bom”, comparo a página onde aparece a etiqueta interna, se aparecer, com a página onde se \label{special label}
encontra. Quando o conteúdo não é enviado ( floatrow
fazendo medições, etc.), as etiquetas internas correspondentes não são definidas, daí o texto, que modifiquei para “a etiqueta especial está definida anteriormente na mesma página” e “a etiqueta especial foi definido em página anterior” não atrapalha as medições (ver código).
Sim, isso é um pouco hackeado!