11

I wanted to use the cleveref to automatically sort cross-references, but for some reason it isn't sorting labels in nested enumerate environments properly. This is my reduced code:

\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}

The result I get is "The reference comes out as: items 1a, 2a and 1b", instead of what I expected, which would be "items 1a, 1b and 2a". It seems as if cleveref is sorting from right to left, so 2a is placed before 1b because a<b. How can I fix it?

Update: I sent an email to the package author at the time, but I never received an answer.

  • 2
    Welcome to TeX.SE! It looks like you've come across a bug in cleveref. You should contact the author of the package and send him your MWE. – Mico Oct 27 '14 at 8:23
  • +1 Adding further levels gives even stranger ordering... – Andrew Swann Feb 10 '15 at 13:35
7

Usually, counters in latex reference their parent counter when being initialised: \newcounter{NameOfTheNewCounter}[NameOfTheOtherCounter]. This way, the \label command can access all the parents in the tree structure and their numeric index values. Cleveref makes use of this fact, and notes down the entire structure when the \label command is used. This is written into the .aux file. So for Subsection 1.3.4 with label \label{aSubSection}, this would be:

 \newlabel{aSubSection@cref}{{[subsubsection][4][3,1]1.3.4}{1}}

So, when sorting, cleveref just compares the indicies 4, 3, 1 in reverse order. No lexicographical ordering at all - clever cleveref!

Now, you have stumbled on a bug in cleveref. Until a fix is published, here is a workaround:

\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}

The bug is basically an incorrect string comparison function used (testing parameter values with \ifx) - comparing strings in latex can be so hard!

This gives the output:

example Output

10

Turns out @Karalga's explanation of the cause of this bug points in the right direction, but doesn't get to the bottom of the problem. Sorting of nested list labels was broken for two completely independent reasons!

@Karalga's work-around accidentally fixed both of these, without fully understanding why. Unfortunately, I can't use that method in the package, as it depends on the \pdfstreq internal, which only exists in (sufficiently recent versions of) pdftex.

The string comparisons in cleveref use a technique that should work. (It's even one of the techniques explained at that cited stackexchange answer. The comparisons fail in the specific case of enumeration list counter names for reasons that are still somewhat mysterious to me, but which may have something to do with the counter names acquiring strange catcodes.

However, the bigger issue is that LaTeX doesn't use the counter reset list mechanism at all when resetting of enum counters in nested lists! The resetting is hard-coded in the internals of the enumerate and list environment definitions. So even if the string comparisons worked, cleveref still wouldn't be able to detect that enum counters get reset by higher-level enum counters.

Given this, the only reasonable fix is to hard-code enums as special cases in the cref@resetby macro. (As well as fixing the string comparisons to be catcode-independent.)

I've done this in the latest version (0.20) available from my web site. I'll upload this to CTAN only after I've done some more testing, as this bug was somewhat subtle.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.