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
  • 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, 2014 at 8:23
  • +1 Adding further levels gives even stranger ordering... Feb 10, 2015 at 13:35

3 Answers 3

8

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

11

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.

0

Thanks to @Karalga who solved my (exactly the same) question.

I change it into a list to make it less stupid. (maybe we can change it to key-val pairs using packages like pgfkeys)

Here is my code.

\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

One can simply extend the mapping by redefining \cref@resetby@list, which should be a list of the form <ChildEnvironment-1>,<ParentEnvironment-1>;<ChildEnvironment-2>,<ParentEnvironment-2>;etc. The last symbol of the list must be semicolon(;), and an empty <ChildEnvironment> ends the list. I will always append ,; to the tial of the list and thus one do not need to worry about the ending the list.

The idea is simple:

  1. Check if <ChildEnvironment> in list is empty. If so, drop the rest of the list.
  2. Check if <ChildEnvironment> in list equals to #1. If so, define #2 and then drop the rest of the list.
  3. Move to the next pair and continue checking.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .