在 cleveref 的 \cref 中添加 `UsedOn` 选项以模仿 hyperref 的 \backcite

在 cleveref 的 \cref 中添加 `UsedOn` 选项以模仿 hyperref 的 \backcite

(这个问题和答案导致了新的一揽子计划cleveref-usedon,请参阅CTAN 版本或者开发版本在 github 上。)

我正在尝试使\label和超载\cref,以便发生以下情况(本质上类似于backref\backcite):

  • 在一些预备章节中,我在定理A中定义了一个标签\label{thm:A}
  • 稍后在正文中我将通过引用定理\cref{thm:A}A。
  • 回顾起来,我希望通过在定理 A 的标题后面加上“在 XYZ 页上使用”的短语,让序言章节的读者知道结果将在正文的后面用到哪里。

我尝试了以下方法,该方法在标签仅被引用一次时有效。基本上,我创建一个命令\crefusedon,该命令反过来定义一个标签,该标签backref:在原始标签名称上添加前缀,例如backref:thm:A。label 命令被重载以搜索是否存在backref:LABELNAME并打印上面的文本。\cref命令被重载以包含\crefusedon。这样,我只需要手动将选项 [usedon] 添加到\cref我的主要文本中所有需要它的 ,其余的工作与以前一样。

问题):

  • 缺点是,目前它只适用于一次(或者说最后一次)调用\crefusedon。当然,如果真的有多次调用,最好能记录下所有\crefusedon调用,并写上“用于页面 X、Y 和 Z”之类的短语。我可以用backref这个吗?
  • 我可以\RenewDocumentCommand \label,但不知为何不行\cref。知道为什么吗?
  • 有点令人困惑的是,中的超链接\cpageref没有链接到此 MWE 中的正确页面。但是,在我的正常文档中,\cpageref超链接有效,但有时相反的\crefusedon超链接指向(非常)错误的页面。

我猜我可以以某种方式使用或修改\backcite来做我想做的事情,但我一直无法理解backrefcleveref的代码库。有什么合理的解决方案吗?如果 backref 可以做到这一点,我很乐意放弃下面的半解决方案。顺便说一句,我在普通文档中使用 biblatex,并且知道在这种情况下 backref 可能会有问题。

\documentclass{amsart}
\usepackage{amsthm}             %AMS theorems
\usepackage{thmtools}           %better theorem editing interface
\declaretheorem[name=Theorem]{theorem}

\usepackage{tikz}
\usepackage{etoolbox}
\usepackage{xparse}

\usepackage{hyperref}
%=========================================
% -------- Exceptions to Hyperref (load last) ----------
%=========================================
\usepackage[capitalise]{cleveref}               % needs to be loaded AFTER hyperref
                                                % to not conflict with hyperref's \autoref

%=========================================
% -------- BEGIN Overload label and cref ----------
%=========================================
\makeatletter
\NewDocumentCommand{\usedon}{ m }{%
    % Check if the label `backref:<LABELNAME>` exists
    \ifcsdef{r@backref:#1}{%
        \emph{(Used on \cpageref{backref:#1}.)}%
        \\%
    }{}
}%
\makeatother
\AtBeginDocument{
    \let\origlabel\label
    \let\origcref\cref
    \RenewDocumentCommand{\label}{ m }{%
        \origlabel{#1}\usedon{#1}%
    }%
    \NewDocumentCommand{\crefusedon}{ s o m }{%
        \IfBooleanTF{#1}{%      
            \origcref*{#3}
        }{%
            \origcref{#3}
        }%
        \ifstrequal{#2}{usedon}{%
            % \cref can also get a list of labels as input, loop through them
            \foreach \x in {#3}{
                \label{backref:\x}%
            }%
        }{}%
    }%
    \let\cref\crefusedon
}%
%=========================================
% -------- END Overload label and cref ----------
%=========================================

\begin{document}
\section{Introduction to wonders}
\begin{theorem}
\label{thm:egregium}
This is a great theorem about wonderful mathematics.
\end{theorem}

\begin{theorem}
\label{thm:inconspicuam}
A plane is flat.
\end{theorem}

\clearpage
As seen in \cref[usedon]{thm:egregium} the earth is positively curved
and according to \cref{thm:inconspicuam} flat earth is not.

\clearpage
As seen in \cref[usedon]{thm:egregium} the earth is positively curved
and according to \cref{thm:inconspicuam} flat earth is not.

\end{document}

答案1

(这个问题和答案导致了新的一揽子计划的产生cleveref-usedon,请参阅CTAN 版本或者开发版本在 github 上。)

我不确定这个功能对除了我之外的其他人是否有用,并且可能\zref会使用更适合的软件包。无论如何,这促使我学习并实施解决方案。欢迎对expl3我的代码中的任何错误发表评论。运行两次以查看结果。我使用了来自的 bugfixexpl3pdflatex这里修复使用多个参数cleveref调用时的范围问题。\cpageref

命令\cref\Cref\label保留其正常行为,但\cref命令有一个可选的额外参数。当通过以下方式UsedOn引用标签时,会发生:\cref[UsedOn]{LABELNAME}

  • \AtBeginDocument\ReadFromAux:我们从 .aux 文件(参见\WriteToAux)中读取一个键值列表,该列表存储在全局 proplist 中\g_UsedOn_kv_prop。对于每个键(LABELNAME),我们创建并设置一个计数器LastRun@LABELNAME来表示该值(max calls from last run)。
  • \cref并且\Cref:他们先调用原始cleveref版本,然后调用定制版本\crefUsedOnProcessor
  • \crefUsedOnProcessor:如果可选参数UsedOn传递给了,\cref那么我们循环遍历LABELNAME传递给的 s \cref(它可以接收标签名称列表)。对于每个,LABELNAME我们增加(或创建并增加)计数器ThisRun@UsedOn@LABELNAME。如果需要,更新并将计数器存储LastRun@UsedOn@LABELNAME在 中。然后我们使用原始命令\g_UsedOn_kv_prop创建一个新标签,其中 是当前运行的 的当前值。UsedOn@LABELNAME@NUMBER\labelNUMBERThisRun@UsedOn@LABELNAME
  • \label:调用原始cleveref版本,然后调用自定义版本\PrintUsedOnLabel
  • \PrintUsedOnLabel:如果至少LABELNAME有一次带有 option 的引用,则该引用存在。在这种情况下,我们创建一个要传递给创建短语的参数列表。我们需要知道的值在哪里。UsedOnUsedOn@LABELNAME@1\cpagerefUsed on page...UsedOn@LABELNAME@1UsedOn@LABELNAME@MAXMAXLastRun@UsedOn@LABELNAME
  • \AtEndDocument并且\WriteToAux:我们将全局 proplist 存储\g_UsedOn_kv_prop在 .aux 文件中。
\documentclass{article}
\usepackage{amsthm}             %AMS theorems
\usepackage{mathtools}          %AMSmath++ facilities
\usepackage{thmtools}           %better theorem editing interface

\usepackage{xparse}

\usepackage{hyperref}
%=========================================
% -------- Exceptions to Hyperref (load last) ----------
%=========================================
\usepackage[capitalise]{cleveref}   % needs to be loaded AFTER hyperref
                                    % because both update \label at the hook 
                                    % \AtBeginDocument and we want to use 
                                    % cleveref's \label
\crefname{page}{page}{pages}        % do NOT capitalise pages for more visual appeal
% This fixes the range bug for \cpageref in cleveref 0.21.4
% See https://tex.stackexchange.com/q/603514/267438
\makeatletter
\newcommand*{\@setcpagerefrange}[3]{%
    \@@setcpagerefrange{#1}{#2}{cref}{#3}}
\newcommand*{\@setCpagerefrange}[3]{%
    \@@setcpagerefrange{#1}{#2}{Cref}{#3}}
\newcommand*{\@setlabelcpagerefrange}[3]{%
    \@@setcpagerefrange{#1}{#2}{labelcref}{#3}}
\makeatother

% declaretheorem needs to be executed AFTER loading cleveref 
% with the additional refname and Refname options
% see https://tex.stackexchange.com/a/498242/267438
% and the thmtools documentation
\declaretheorem[numberwithin=section,
                name=Theorem,
                refname={Theorem,Theorems},
                Refname={Theorem,Theorems}]{theorem}
\declaretheorem[sibling=theorem,
                name=Lemma,
                refname={Lemma,Lemmas},
                Refname={Lemma,Lemmas}]{lemma}

\usepackage{showlabels}     % to see the labels in the margin


%=========================================
% -------- BEGIN Overload label and cref ----------
%=========================================
\makeatletter
\ExplSyntaxOn
\NewDocumentCommand{\PrintUsedOnLabel}{ m }{%
    % Check if the reference `UsedOn@<LABELNAME>@1` exists
    % Here the @1 means that  <LABELNAME> has been referenced
    % with option `UsedOn` at least once
    \cs_if_exist:cT {r@UsedOn@#1@1}
    {   % Store in a tmp clist all the references of the form
        %   `UsedOn@<LABELNAME>@<NUMBER>` 
        % where NUMBER between 1 and \value{LastRun@UsedOn@<LABELNAME>}
        % if the latter exists, otherwise until 1
        % Should/will normally need two consecutive runs of pdflatex
        \cs_if_free:cTF {c@LastRun@UsedOn@#1}
            { \int_set:Nn \l_tmpa_int { 1 } }
            { \int_set:Nn \l_tmpa_int { \value{LastRun@UsedOn@#1} } }
        \int_set:Nn \l_tmpb_int { 1 }
        \int_while_do:nn { \l_tmpb_int <= \l_tmpa_int } 
        {
            \clist_put_right:Nx \l_tmpa_clist { UsedOn@#1@\int_use:N \l_tmpb_int }
            \int_incr:N \l_tmpb_int
        }
        % Print `UsedOn` text by calling \cpageref with the parameter clist above
%           Arguments~of~cpageref~are:
%           \par\clist_use:Nn \l_tmpa_clist {\par}\par
        \emph{(Used~on~\cpageref{\l_tmpa_clist}.)}  \\%
    }
}%
\NewDocumentCommand{\UsedOnProcessor}{ o m }{%
    \IfValueT{#1}{
        \str_if_eq:nnTF {#1} {UsedOn}
        {   % Loop through (potential) label list in arg of \cref (or \Cref)
            \seq_set_from_clist:Nn \l_tmpa_seq {#2}
            \seq_map_inline:Nn \l_tmpa_seq 
            {   % if the label has not been referenced yet, 
                % create a counter and save the label in a global key sequence
                \seq_if_in:NxF \g_UsedOn_k_seq {UsedOn@##1}
                {
                    \newcounter{ThisRun@UsedOn@##1}
                    \cs_if_free:cT {c@LastRun@UsedOn@##1}
                        { \newcounter{LastRun@UsedOn@##1} }
                    \seq_gput_right:Nx \g_UsedOn_k_seq {UsedOn@##1}
                }
                % increase the counter and compare with max counter
                \stepcounter{ThisRun@UsedOn@##1}
                \setcounter{LastRun@UsedOn@##1}{%
                    \fp_eval:n { max(%
                        \value{ThisRun@UsedOn@##1},%
                        \value{LastRun@UsedOn@##1} ) }%
                }
                % store the value in global key-value property list
                \prop_gput:Nxx \g_UsedOn_kv_prop 
                    {UsedOn@##1} {\arabic{LastRun@UsedOn@##1}}
                % create a label for the UsedOn reference and number this label
                \origlabel{UsedOn@##1@\arabic{ThisRun@UsedOn@##1}}
            }
        }
        {
            \msg_fatal:nn {UsedOn} 
                {Fatal~spelling~error~when~passing~option~`UsedOn`~to~cref~or~Cref}
        }
    }
}
\NewDocumentCommand{\crefUsedOn}{ s o m }{%
    \IfBooleanTF{#1}{ \origcref*{#3} }{ \origcref{#3} }%
    \UsedOnProcessor[#2]{#3}
}%
\NewDocumentCommand{\CrefUsedOn}{ s o m }{%
    \IfBooleanTF{#1}{ \origCref*{#3} }{ \origCref{#3} }%
    \UsedOnProcessor[#2]{#3}
}%
\NewDocumentCommand{\ReadFromAux}{ }{%  
    % for each key create and set a counter
    \prop_map_inline:Nn \g_UsedOn_kv_prop
    {
        \newcounter{LastRun@##1}
        \setcounter{LastRun@##1}{##2}
    }
}%
\NewDocumentCommand{\WriteToAux}{ }{%
    % Clear global key-value prop list \g_UsedOn_kv_prop
    % and rebuilt with info from current run
    \prop_clear:N \g_UsedOn_kv_prop
    \seq_map_inline:Nn \g_UsedOn_k_seq
        { \prop_gput:Nxx \g_UsedOn_kv_prop {##1}{\arabic{ThisRun@##1}} }
    % turn on expl3 functionality in aux file   
    \iow_now:cx { @auxout }
        { \token_to_str:N \ExplSyntaxOn }
    % loop through prop list and write to aux file
    \prop_map_inline:Nn \g_UsedOn_kv_prop
    {
        \iow_now:cx { @auxout }
            { \prop_gput_from_keyval:Nn \token_to_str:N \g_UsedOn_kv_prop {##1=##2} }
    }
    % turn off expl3 functionality in aux file  
    \iow_now:cx { @auxout }
        { \token_to_str:N \ExplSyntaxOff }
}%

% global key sequence \g_UsedOn_k_seq: 
%   container for `UsedOn` label names
\seq_new:N \g_UsedOn_k_seq
% global key-val prop list \g_UsedOn_kv_prop: 
%   container for `UsedOn` label names and their LastRunValue
\prop_new:N \g_UsedOn_kv_prop

%Read from .aux file and patch commands
\AtBeginDocument{%
    \ReadFromAux
    % Patch label and cref to include the new `UsedOn` capabilities
    \let\origlabel\label%
    \let\origcref\cref%
    \let\origCref\Cref%
    \RenewDocumentCommand{\label}{ m }{%
        \origlabel{#1}\PrintUsedOnLabel{#1}%
    }%
    \let\cref\crefUsedOn
    \let\Cref\CrefUsedOn
}%
% Write to .aux file
\AtEndDocument{%
    \WriteToAux
}%
\ExplSyntaxOff
\makeatother
%=========================================
% -------- END Overload label and cref ----------
%=========================================
\begin{document}
\section{A new section}
\label{sec:TestSection}
Some text is written here.
\begin{theorem}
\label{thm:TestTheorem}
The identity
\begin{equation*}
    1=1
\end{equation*}
is true.
\end{theorem}

\begin{lemma}
\label{lemma:TestLemma}
The identity
\begin{equation*}
    2=2.
\end{equation*}
is also true.
\end{lemma}

\subsection{A new subsection}

\par    The statements in \cref[UsedOn]{thm:TestTheorem} is true
and has been called with `UsedOn`. \\
\par    The statements in \cref[UsedOn]{lemma:TestLemma} is true
and has been called with `UsedOn`. \

\clearpage
.
\clearpage
.
\clearpage
\par    The statements in \cref[UsedOn]{thm:TestTheorem,lemma:TestLemma} are true
and have been called with `UsedOn`. \\

\clearpage
\par    \Cref[UsedOn]{thm:TestTheorem} is a true statement
and has been called with `UsedOn`. \\
\par    \Cref[UsedOn]{lemma:TestLemma} is a true statement
and has been called with `UsedOn`. \\

\clearpage
\par    \Cref[UsedOn]{thm:TestTheorem,lemma:TestLemma} are true statements
and have been called with `UsedOn`. \\

\subsection{Another subsection}
Here is yet another theorem.
\begin{theorem}
\label{thm:TestTheoremSecond}
Content of theorem
\end{theorem}

The statement in \cref{thm:TestTheoremSecond} is also true 
but it has \emph{not} been called with `UsedOn`.

\subsection{Yet another subsection}
Printing counters. \\
Note that \emph{thm:TestTheoremSecond} does \emph{not} have a `UsedOn` counter.
\ExplSyntaxOn
\begin{itemize}
    \prop_map_inline:Nn \g_UsedOn_kv_prop
    {
        \item The~value~of~the~counter~\emph{#1}~is~#2.
    }
\end{itemize}
\ExplSyntaxOff
\end{document}

相关内容