我想使用 cleveref 自动对交叉引用进行排序,但出于某种原因,它无法正确对嵌套枚举环境中的标签进行排序。这是我简化的代码:
\documentclass{article}
\usepackage[sort]{cleveref}
\begin{document}
\begin{enumerate}
\item foo
\begin{enumerate}
\item 1 \label{1}
\item 2 \label{2}
\end{enumerate}
\item bar
\begin{enumerate}
\item 3 \label{3}
\end{enumerate}
\end{enumerate}
The reference comes out as: \cref{1,2,3}
\end{document}
我得到的结果是“引用结果为:项目 1a、2a 和 1b”,而不是我预期的“项目 1a、1b 和 2a”。似乎 cleveref 是从右到左排序的,所以 2a 放在 1b 之前,因为 a<b。我该如何修复它?
更新:我当时给软件包作者发了一封电子邮件,但一直没有收到答复。
答案1
通常,latex 中的计数器在初始化时会引用其父计数器:\newcounter{NameOfTheNewCounter}[NameOfTheOtherCounter]
。这样,该\label
命令可以访问树结构中的所有父级及其数字索引值。Cleveref 利用了这一事实,并在\label
使用该命令时记下整个结构。这会写入.aux
文件中。因此对于1.3.4 小节使用标签\label{aSubSection}
,这将是:
\newlabel{aSubSection@cref}{{[subsubsection][4][3,1]1.3.4}{1}}
因此,排序时,cleveref 只是按相反顺序比较索引4, 3, 1
。根本没有字典顺序 - clever cleveref!
现在,您偶然发现了 cleveref 中的一个错误。在发布修复程序之前,这里有一个解决方法:
\documentclass{article}
\usepackage[sort]{cleveref}
\makeatletter
\let\cref@old@resetby\cref@resetby%
\def\cref@resetby#1#2{
\let#2\relax%
\ifnum\pdfstrcmp{#1}{enumii}=\z@
\def#2{enumi}%
\fi%
\ifnum\pdfstrcmp{#1}{enumiii}=\z@
\def#2{enumii}%
\fi%
\ifnum\pdfstrcmp{#1}{enumiv}=\z@
\def#2{enumiii}%
\fi%
\ifnum\pdfstrcmp{#1}{enumv}=\z@
\def#2{enumiv}%
\fi%
\ifx#2\relax%
\cref@old@resetby{#1}{#2}
\fi}%
\makeatother
\begin{document}
\begin{enumerate}
\item foo
\begin{enumerate}
\item 1 \label{1}
\item 2 \label{2}
\begin{enumerate}
\item 5 \label{7}
\item 3 \label{4}
\end{enumerate}
\end{enumerate}
\item bar \label{6}
\begin{enumerate}
\item 3 \label{3}
\begin{enumerate}
\item 5 \label{5}
\end{enumerate}
\end{enumerate}
\end{enumerate}
The reference comes out as: \cref{1,2,3}, \cref{1,2} \cref{2,3}
and \cref{1,2,3,4,5,6,7}. As they should!
\end{document}
该错误基本上是使用了错误的字符串比较函数(使用 \ifx 测试参数值)——在乳胶中比较字符串可能非常困难!
输出结果如下:
答案2
事实证明@Karalga 对此错误原因的解释是正确的,但并没有触及问题的根源。嵌套列表标签的排序被破坏了二完全独立的原因!
@Karalga 的解决方法意外地修复了这两个问题,但我没有完全理解原因。不幸的是,我无法在包中使用该方法,因为它依赖于\pdfstreq
内部方法,而内部方法仅存在于(足够新的版本)中pdftex
。
字符串比较cleveref
使用了一种技术应该工作。(它甚至是当时解释的技术之一引用 stackchange 答案在枚举列表计数器名称的特定情况下,比较会失败,原因对我来说仍然有些神秘,但这可能与计数器名称获取奇怪的 catcode 有关。
然而,更大的问题是,在重置嵌套列表中的枚举计数器时,LaTeX 根本不使用计数器重置列表机制!重置是在enumerate
和list
环境定义内部硬编码的。因此,即使字符串比较有效,cleveref
仍然无法检测到枚举计数器被更高级别的枚举计数器重置。
鉴于此,唯一合理的修复方法是将枚举硬编码为cref@resetby
宏中的特殊情况。(以及修复字符串比较以使其与 catcode 无关。)
我已经在最新版本(0.20)中完成了此操作,可从我的网站。我会在做完更多测试后才将其上传到 CTAN,因为这个错误有点难以察觉。
答案3
感谢@Karalga 解决了我(完全相同)的问题。
我将其改为列表,以使其不那么愚蠢。(也许我们可以使用类似的包将其更改为键值对pgfkeys
)
这是我的代码。
\makeatletter
\def\cref@resetby@list{enumv,enumiv;enumiv,enumiii;enumiii,enumii;enumii,enumi;}
\let\cref@old@resetby\cref@resetby
\def\cref@resetby#1#2{%
\let#2\relax%
\def\@eatall##1\relax{}%
\def\@tmpcode##1,##2;{%
\let\@next\@eatall
\ifx,##1,\else
\ifnum\pdfstrcmp{#1}{##1}=\z@
\def#2{##2}%
\else
\let\@next\@tmpcode
\fi
\fi
\@next
}%
\expandafter\@tmpcode\cref@resetby@list,;\relax
\ifx#2\relax%
\cref@old@resetby{#1}{#2}%
\fi
}
\makeatother
可以通过重新定义来简单地扩展映射\cref@resetby@list
,它应该是形式为<ChildEnvironment-1>,<ParentEnvironment-1>;<ChildEnvironment-2>,<ParentEnvironment-2>;
等的列表。列表的最后一个符号必须<ChildEnvironment>
是分号 (;),列表以空字符结尾。我将始终将其附加,;
到列表末尾,因此无需担心列表的结尾。
这个想法很简单:
- 检查
<ChildEnvironment>
列表是否为空。如果是,则删除列表的其余部分。 - 检查
<ChildEnvironment>
列表中是否等于#1
。如果是,#2
则定义并删除列表的其余部分。 - 移至下一对并继续检查。