
Я создал вопросранеео моем коде, но он был слишком большим и нечетким, чтобы должным образом изолировать мою проблематику (и код тем временем изменился). Вот новая версия моего вопроса на очень простом примере игрушек, которая делает его более точным и очень коротким.
Я пытаюсь создать функцию, которую можно вызвать с тем же аргументом, например:
\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}
даст через последовательные компиляции:
- Метка могла измениться, повторите попытку, чтобы получить правильную перекрестную ссылку.
- ничего
- Метка могла измениться, повторите попытку, чтобы получить правильную перекрестную ссылку.
- ничего
- и т. д...
Таким образом, результат, по-видимому, меняет одну компиляцию из двух, что не является желаемым результатом.
Но пока это не критично. Давайте проверим это с помощью:
\MyTesting{test} some text \MyTesting{test}
Здесь мы имеем, посредством последовательной компиляции:
- Метка могла измениться, повторите попытку, чтобы получить правильную перекрестную ссылку.
- Метка «тест» многократно определена
- Метка могла измениться, повторите попытку, чтобы получить правильную перекрестную ссылку.
- Метка «тест» многократно определена
- и т. д...
Здесь я не совсем понимаю логику... даже если метка сохранена в 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
во время предыдущего запуска компиляции.
Таким образом:
Когда ваш макрос
\r@test
определен, это означает, что он вызван\label{test}
впредыдущий запуск компиляции; он выведет «уже определено» ине позвонит\label{test}
в этом запуске, и это справедливо длявсевызовы вашего макроса с аргументомtest
во время этого запуска компиляции.В следующий раз, когда вы компилируете,
.aux
файл не будет содержать\newlabel
вызовов для labeltest
, поэтому ваш макрос всегда будет обнаруживать, что\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
), и \write
s соответствующее значение счетчика в .aux
файле (это value
in \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
проведение измерений и т. д.), соответствующие внутренние метки не определяются, поэтому текст, который я изменил на «специальная метка определена ранее на той же странице» и «специальная метка была определена на более ранней странице», не нарушает измерения (см. код).
Да, это немного хакерски!