(这个问题和答案导致了新的一揽子计划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
来做我想做的事情,但我一直无法理解backref
和cleveref
的代码库。有什么合理的解决方案吗?如果 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
我的代码中的任何错误发表评论。运行两次以查看结果。我使用了来自的 bugfixexpl3
pdflatex
这里修复使用多个参数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
\label
NUMBER
ThisRun@UsedOn@LABELNAME
\label
:调用原始cleveref
版本,然后调用自定义版本\PrintUsedOnLabel
。\PrintUsedOnLabel
:如果至少LABELNAME
有一次带有 option 的引用,则该引用存在。在这种情况下,我们创建一个要传递给创建短语的参数列表。我们需要知道的值在哪里。UsedOn
UsedOn@LABELNAME@1
\cpageref
Used on page...
UsedOn@LABELNAME@1
UsedOn@LABELNAME@MAX
MAX
LastRun@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}