Как это работает

Как это работает

Я создал вопросранеео моем коде, но он был слишком большим и нечетким, чтобы должным образом изолировать мою проблематику (и код тем временем изменился). Вот новая версия моего вопроса на очень простом примере игрушек, которая делает его более точным и очень коротким.

Я пытаюсь создать функцию, которую можно вызвать с тем же аргументом, например:

\myFunction{foo} some text \myFunction{foo}

но мне нужно, чтобы эта функция выдавала другой результат во втором случае и, кроме того, чтобы метка была определена только в первом случае (чтобы "\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. и т. д...

Таким образом, результат, по-видимому, меняет одну компиляцию из двух, что не является желаемым результатом.

Но пока это не критично. Давайте проверим это с помощью:

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

Здесь мы имеем, посредством последовательной компиляции:

  1. Метка могла измениться, повторите попытку, чтобы получить правильную перекрестную ссылку.
  2. Метка «тест» многократно определена
  3. Метка могла измениться, повторите попытку, чтобы получить правильную перекрестную ссылку.
  4. Метка «тест» многократно определена
  5. и т. д...

Здесь я не совсем понимаю логику... даже если метка сохранена в aux, тест в начале \MyTesting должен предотвратить множественное определение.

Бонусные критерии для ответов: Вызов функции также должен быть устойчивым к окружению, такому как заголовок в «figure», который, по-видимому, оценивается дважды...

Я приму любую помощь по этой проблеме ;)

МВЭ:

%%%% 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файлу. Действительно, \labelзаписывает \newlabelвызовы в .auxфайл. Этот .auxфайл читается:

  • в \enddocumentмомент времени, что позволяет LaTeX предупреждать о многократно определенных метках;

  • в начале документа, что позволяет \newlabelвызовам, присутствующим в .auxфайле, определять \r@labelкаждую метку, которая была определена \labelво время предыдущего запуска компиляции.

Таким образом:

  1. Когда ваш макрос \r@testопределен, это означает, что он вызван \label{test}впредыдущий запуск компиляции; он выведет «уже определено» ине позвонит \label{test}в этом запуске, и это справедливо длявсевызовы вашего макроса с аргументом testво время этого запуска компиляции.

  2. В следующий раз, когда вы компилируете, .auxфайл не будет содержать \newlabelвызовов для label test, поэтому ваш макрос всегда будет обнаруживать, что \r@testон не определен, и всегда будет вызываться \label{test}в этом запуске компиляции, отсюда и вывод предупреждения «Label 'test' multiple defined» каждый раз, когда ваш макрос вызывается с аргументом testво время этого запуска компиляции. \label{test}Вызовы записывают \newlabelвызовы testв .auxфайл, поэтому при следующем запуске компиляции мы вернемся к шагу 1.

Я считаю, что вы хотите следующее. Тест \ifx\protect\@typeset@protectпозволяет нам убедиться, что ничего не просачивается в подписи к рисункамв списке таблиц или списке рисунков(тест верен во время набора текста, но не тогда, когда подписи записываются в файлы .lotили .lofчерез \addtocontents— последний использует \protected@write, что временно делает \protect \let-равным \@unexpandable@protect).

Редактировать: ну, из-за того, как floatrowработает, работа с \MyLabelвнутри подписи для floatrowгораздо сложнее, чем это, но следующее, кажется, работает нормально. Обратите внимание, что для стабилизации меток требуется несколько запусков компиляции.

\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(5 раз для одной подписи в моем тесте!). Фактически, он, похоже, измеряет его несколькими способами, прежде чем принять решение об отправке. Таким образом, для каждого конкретногоспециальная этикетка(те, которые объявлены \myInitи используются в \MyLabel), нам нужно определить первый раз, когда он отправляется (т. е. отправляется в файл DVI или PDF) итолько на этот разиспользуйте \label. Для более ранних времен мы не должны ничего выводить (иначе мы можем нарушить измерения), а для более поздних времен нам нужно вывести «уже определено», как указано в вопросе, но без \labelвызова.

Теперь, как \MyLabelопределить, когда данная метка отправляется в первый раз? Для каждой метки она подсчитывает количество раз, когда она вызывается в режиме набора ( \protectравно \@typeset@protect), и \writes соответствующее значение счетчика в .auxфайле (это valuein \my@MaybeDefine{special label}{value}). Это главный трюк. A \write— эточто это(ср. TeXbook), то есть то, что попадает внутрь коробок, иэто приводит к фактической записи в файл только в том случае, если коробка, содержащая что-то, отправлена. Итак, фиктивные вызовы, используемые floatrowили другими пакетами для измерения текста субтитров и т. д., обрабатываются следующим образом: нет отправки, нет записи в .auxфайл. valueПервый \my@MaybeDefine{special label}{value}записанный в .auxфайл указывает на то, что первый раз \MyLabelиспользовался с первым аргументом special labelвнутри коробки, которая была отправлена. Итак, когда внутренний счетчик for special labelравен этому первому значению, предполагая, что исходный файл не изменился с момента последней компиляции, это означает, что содержащийся материал special labelнабирается «по-настоящему» в первый раз.

Еще одно: из-за плавающих элементов (таблиц, рисунков...) возможно, что некоторые материалы, связанные сспециальная этикеткабыть набрано (даже при \protectравенстве \@typeset@protect) раньше, чем \labelкоманда дляспециальная этикетка, но появляются позже в выходном файле. В таких случаях внутренний счетчик, связанный сспециальная этикеткабудет иметь значение, которое ниже «хорошего значения», когда материал набирается для раннего флота, но все равно ему нужен «уже определенный» текст, поскольку материал появится позже, чем \label. По этой причине я добавил внутренние метки, и когда внутренний счетчик отличается от «хорошего значения», я сравниваю страницу, на которой появляется внутренняя метка, если она вообще появляется, со страницей, где находится \label{special label}. Когда содержимое не отправляется ( floatrowпроведение измерений и т. д.), соответствующие внутренние метки не определяются, поэтому текст, который я изменил на «специальная метка определена ранее на той же странице» и «специальная метка была определена на более ранней странице», не нарушает измерения (см. код).

Да, это немного хакерски!

Связанный контент