Como funciona

Como funciona

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:

  1. O rótulo pode ter mudado. Execute novamente para obter a referência cruzada correta
  2. nada
  3. O rótulo pode ter mudado. Execute novamente para obter a referência cruzada correta
  4. nada
  5. 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:

  1. O rótulo pode ter mudado. Execute novamente para obter a referência cruzada correta
  2. Rótulo 'teste' multiplicado definido
  3. O rótulo pode ter mudado. Execute novamente para obter a referência cruzada correta
  4. Rótulo 'teste' multiplicado definido
  5. 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@labeltestes se o rótulo já está definidode acordo com o .auxarquivo. Na verdade, \labelgrava \newlabelchamadas no .auxarquivo. Este .auxarquivo é lido:

  • às \enddocumentvezes, o que permite ao LaTeX avisar sobre rótulos definidos de forma múltipla;

  • no início do documento, o que permite que as \newlabelchamadas presentes no .auxarquivo sejam definidas \r@labelpara cada rótulo que foi definido \labeldurante a execução de compilação anterior.

Por isso:

  1. Quando sua macro estiver \r@testdefinida, 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 argumento testdurante esta execução de compilação.

  2. Na próxima vez que você compilar, o .auxarquivo não terá nenhuma \newlabelchamada para label test, portanto, sua macro sempre descobrirá que \r@testnã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 argumento testdurante a execução da compilação. As \label{test}chamadas escrevem \newlabelchamadas for testno .auxarquivo, portanto, na próxima compilação, voltaremos ao passo 1.

Acredito que o que você quer é o seguinte. O \ifx\protect\@typeset@protectteste 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 .lotou 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 floatrowfunciona, lidar com \MyLabeluma 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}

insira a descrição da imagem aqui

Como funciona

Cuidado, isso é um pouco técnico. O principal problema que tivemos floatrowé que ele compõe o texto da legenda muitas vezes \protectigual 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 \myInite 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 \labelchamada.

Agora, como \MyLabeldetecta 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 ( \protectigual a \@typeset@protect) e \writeé o valor correspondente do contador ao .auxarquivo (este é o valuein \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 floatrowou outros pacotes para medir o texto da legenda e outros enfeites são tratadas desta maneira: sem envio, sem gravação no .auxarquivo. O valueprimeiro \my@MaybeDefine{special label}{value}escrito no .auxarquivo indica a primeira vez que \MyLabelfoi usado com o primeiro argumento special labeldentro 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 labelestá 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 \protectigual a \@typeset@protect) antes do \labelcomando 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 ( floatrowfazendo 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!

informação relacionada