6

EDIT: This issue has been resolved by cleveref version 0.21.


I was hoping that I could get cleveref to compress multiple references to the same section, the way it compresses multiple references to the same page. Alas, I cannot figure out how to do it.

Minimal working example results in

Expressions and definitions occur in sections 1.1 and 1.1 on page 1.

But what I was hoping for was

Expressions and definitions occur in section 1.1 on page 1.

The key is I don't want to know whether the two labels are in the same section or are in different sections. I was hoping that cleveref could keep track of this for me. My real document has a large number of these pairs (a couple of dozen), and checking sections by hand does not appeal—they could change.

How can I get cleveref to compress the two references to section 1.1 into a single reference?

\documentclass{book}
\usepackage{cleveref}
\begin{document}

\chapter{This chapter}

\section{A section}

Expressions\label{exp.int}

Definitions\label{def.int}

\section{Another section}

Expressions and definitions occur in 
\cref{exp.int,def.int} on
\cpageref{exp.int,def.int}. 

\end{document}
6
  • You are aware that exp.int and def.int are the same labels effectively? They both refer to same section
    – user31729
    Commented Dec 4, 2015 at 22:34
  • 2
    @ChristianHupfer I think this is precisely the point: OP wants to make references to parts of his document without having to wonder if they point to the same section or not. For the moment, exp.int and def.int are in the same section, but he could change that without having to think about re-labelling and changes in the cref commands.
    – Clément
    Commented Dec 4, 2015 at 22:37
  • @Clément: Hm, perhaps it would be just easier to get rid of them manually ;-)
    – user31729
    Commented Dec 4, 2015 at 22:38
  • @ChristianHupfer clarifying, there are a lot of these pairs, and I really, really don't want to track them by hand. I especially don't want to eliminate duplicates by hand and then have a reorganization of the document result in losing a reference. (The document in question is 1000 pages.) Commented Dec 5, 2015 at 1:00
  • 1
    @ChristianHupfer give me a break. Perhaps you have heard of automatically generated labels? Commented Dec 6, 2015 at 15:59

4 Answers 4

7

Assuming I understand your approach, here is a wrapper for \cref which removes duplicate labels on the basis of the "type" (i.e., the counter to which they refer) and the "label" (i.e., the value of the counter for the label). If multiple labels in a list refer to the same "type" and "label" combination, only the first is retained.

The guts:

The comma separated list is parsed with \forcsvlist from etoolbox. The "type" and "label" for each entry are retrieved using the \cref@gettype and \cref@getlabel from cleveref. A unique global command (comprised of the "type" and "label") is used to indicate if the combination has been used before. If the unique command is not defined or it is equal to \relax, then the unique command is redefined to be equal to the label and the label is appended to \compressed@list which contains the compressed list. If the unique command exists and it is not equal to \relax, the label is skipped and a warning is raised (this may be commented out if you so desire).

The compressed list is then passed to the old version of \cref. Finally, each of the unique commands are reinitialized to \relax with \forcsvlist\@clear@list{\compressed@list} in preparation for subsequent \cref calls. \DeclareRobustCommand was used to retain the native functionality of \cref in headings.

Edit: As pointed out by the OP in the comments below, the original version of this answer did not pass undefined labels through to \cref; this version has been corrected.

\documentclass{book}

\usepackage{cleveref}

%%pre-compress labels to remove duplicates
\usepackage{etoolbox}    

\makeatletter
    %Robust, allowing \cref to be used in headings (may or may not be desired)
    \DeclareRobustCommand\@create@list[1]{%
            \ifcsname r@#1@cref\endcsname%only process if the label is defined
                \cref@gettype{#1}{\@temptype}%cleveref command, set \@temptype to type associated with label (i.e., the counter name)
                \cref@getlabel{#1}{\@templbl}%cleveref command, set \@templbl to the label (i.e., number) associated with the label
                \ifcsname Addval\@temptype\@templbl\endcsname%already defined
                    \expandafter\if\csname  Addval\@temptype\@templbl\endcsname\relax%equal to relax
                        \expandafter\gdef\csname Addval\@temptype\@templbl\endcsname{#1}%save the label
                        \if\compressed@list\relax%
                            \gdef\compressed@list{#1}\else
                            \g@addto@macro\compressed@list{,#1}\fi
                    \else%already defined and not equal to \relax
                        \@latex@warning{Label #1 is a duplicate of \@temptype\space\@templbl}%
                    \fi
                \else%not defined yet
                    \expandafter\gdef\csname Addval\@temptype\@templbl\endcsname{#1}%save the label
                    \if\compressed@list\relax%
                        \gdef\compressed@list{#1}\else
                        \g@addto@macro\compressed@list{,#1}\fi
                \fi
            \else%Added in edit: label not defined...add to the list anyway for "standard" handling
                \if\compressed@list\relax%
                    \gdef\compressed@list{#1}\else
                    \g@addto@macro\compressed@list{,#1}\fi
            \fi}
    %Robust, allowing \cref to be used in headings (may or may not be desired)
    \DeclareRobustCommand\@clear@list[1]{%command to set the addval\@temptype\@templbl commands to relax
        \ifcsname r@#1@cref\endcsname%only process if the label is defined
            \cref@gettype{#1}{\@temptype}%cleveref command, set \@temptype to type associated with label (i.e., the counter name)
            \cref@getlabel{#1}{\@templbl}%cleveref command, set \@templbl to the label (i.e., number) associated with the label
            \expandafter\gdef\csname Addval\@temptype\@templbl\endcsname{\relax}\fi}

    \def\compressed@list{\relax}%initialize
    \let\old@cref=\cref
    \def\cref#1{%Now, cref will compress duplicate labels and provide a warning if found
        \gdef\compressed@list{\relax}%ensure \relax
        \expandafter\forcsvlist\expandafter\@create@list\expandafter{#1}%create compressed list
        \if\relax\compressed@list\relax\else
        \old@cref{\compressed@list}%pass compresed list to cref
        \expandafter\forcsvlist\expandafter\@clear@list\expandafter{\compressed@list}%clear compressed list
        \fi}
\makeatother

\begin{document}

\chapter{This chapter}

\section{A section}

Expressions\label{exp.int}

Definitions\label{def.int}

\section{Another section}

Expressions and definitions occur in 
\cref{exp.int,def.int} on
\cpageref{exp.int,def.int}. 

\end{document}

compressed

7
  • 3
    +1. You may want to ping Toby Cubitt, the author of the cleveref package, and tell him about this solution -- he just may decide to incorporate it into a future version of the package!
    – Mico
    Commented Dec 5, 2015 at 8:20
  • This is brilliant! I have to understand it, then mark it accepted. Commented Dec 6, 2015 at 16:00
  • OK, I've put it into place, and there's just one problem: when given a list on which one of the labels is undefined, it barfs. I think in that case I'll try just adding it to the compressed list and letting \cref deal with the issue. Commented Dec 6, 2015 at 17:01
  • Hmm. I see the code, but it's barfing on \cref{nowhere, nothing}. Commented Dec 6, 2015 at 17:02
  • The issue may be the use of \ifcsname when the command is made \relax Commented Dec 6, 2015 at 17:03
4

I could see no reason not to compress away multiple references to the same label (just as cleveref already did for page references). "Sections 1 and 1" is surely never what's desired.

So I've implemented this in the latest pre-release version (0.21) of cleveref, available from my web site: http://www.dr-qubit.org/latex.html

It should now do what you want.

2
  • Huzzah! Thank you so much! Commented Jun 20, 2019 at 17:10
  • @Toby Cubitt, Dear Toby, if you may please add the "Arabic language support" to the cleveref package, I would be grateful, I have attached the modifications via email since a year ago, my email is [email protected].
    – Silva
    Commented Apr 20, 2020 at 12:09
2

I doubt that you can with the way the OP is doing it. It seems the intent is to be able to refer to the section and the page number, and to do this some thought and planning on where the labels go will help.

Ideally, you should put your label right after the section or paragraph you want to refer to. So it should look something like this:

\documentclass{book}
\usepackage{cleveref}
\begin{document}

\chapter{This chapter}

\section{A section}
\label{sec:section}

Expressions\label{exp.int}

Definitions\label{def.int}

\section{Another section}

This should take care of labeling both the sections and the relevant paragraphs. Now we need to think about what we are referring to when we write our text

Expressions and definitions occur in \cref{sec:section} on \cpageref{exp.int,def.int}. 

\end{document}

Here we are making specific references to both the section and the paragraphs in the right areas. As the pages are the same for paragraphs, cleveref will compress it.

Hope that helps.

1
  • Actually, the intent is to refer to individual paragraphs without regard to whether they are in the same section or in different sections, and have software figure it out. Commented Dec 5, 2015 at 1:01
2

I had to work out a few bugs in guho's answer. It needs \Cref as well as \Cref, plus there is some tricky stuff around undefined things:

  • An undefined label should be passed through, not ignored, so the user can get an error message.

  • Ditto for a label that looks like \relax.

  • \@clear@list{\compressed@list} needed some \expandafters (or one could just pass #1 there.

I turned the whole thing into a wrapper around cleveref. I am a rank amateur, so there are probably things that are odd or wrong.

\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{cleveref-nodups}[2015/12/06]
 % eliminate duplicate refs in cleveref
 % http://tex.stackexchange.com/posts/281611/edit
 %
 % Thanks http://tex.stackexchange.com/users/89497/guho

\newif\ifwarn@compress
\warn@compressfalse

\DeclareOption{warncompress}{\warn@compresstrue}
\DeclareOption{nowarncompress}{\warn@compressfalse}

\DeclareOption*{%
%  \edef\@tempa{warncompress}%
%  \ifx\@tempa\CurrentOption
%     % do nothing
%  \else
%    \edef\@tempa{nowarncompress}%
%    \ifx\@tempa\CurrentOption
%       % do nothing
%    \else
      \PassOptionsToPackage{\CurrentOption}{cleveref}%
%    \fi
%  \fi
}

\ProcessOptions

\RequirePackage{cleveref}
\RequirePackage{etoolbox}
\RequirePackage{xstring}

%   
%   
%   Assuming I understand your approach, here is a wrapper for `\cref`
%   which removes duplicate labels on the basis of the "type" (i.e., the
%   counter to which they refer) and the "label" (i.e., the value of the
%   counter for the label). If multiple labels in a list refer to the same
%   "type" and "label" combination, only the first is retained.
%   
%   **The guts:**
%   
%   The overloaded `\cref` first checks if a comma is contained in the
%   argument. If so, the comma separated list is parsed with `\forcsvlist`
%   from `etoolbox`. The "type" and "label" for each entry are retrieved
%   using the `\cref@gettype` and `\cref@getlabel` from `cleveref`. A
%   unique global command (comprised of the "type" and "label") is used to
%   indicate if the combination has been used before. If the unique
%   command is not defined or it is equal to `\relax`, then the unique
%   command is redefined to be equal to the label and the label is
%   appended to `\compressed@list` which contains the compressed list. If
%   the unique command exists and it is not equal to `\relax`, the label
%   is skipped and a warning is raised (this may be commented out if you
%   so desire).
%   
%   The  compressed   list  is   then  passed  to   the  old   version  of
%   `\cref`. Finally,  each of  the unique  commands are  reinitialized to
%   `\relax`    with     `\forcsvlist\@clear@list{\compressed@list}`    in
%   preparation for subsequent  `\cref` calls. `\DeclareRobustCommand` and
%   `\protect\IfSubStr` were  used to  retain the native  functionality of
%   `\cref` in headings.



%Robust, allowing \cref to be used in headings (may or may not be desired)
\def\addto@compressed@list#1{%
   \if\compressed@list\relax%
      \gdef\compressed@list{#1}%
   \else
     \g@addto@macro\compressed@list{,#1}%
   \fi
}

\DeclareRobustCommand\@create@list[1]{%
    \ifcsname r@#1@cref\endcsname %only process if the label is defined
      \expandafter\if\csname r@#1@cref\endcsname\relax
        \addto@compressed@list{#1}%
      \else
        \cref@gettype{#1}{\@temptype}% \@temptype := type of label (e.g., section)
        \cref@getlabel{#1}{\@templbl}% \@templbl := label (e.g., 2.5)
        \ifcsname Addval\@temptype\@templbl\endcsname %already defined
          \expandafter\if\csname  Addval\@temptype\@templbl\endcsname\relax%equal to relax
            \expandafter\gdef\csname Addval\@temptype\@templbl\endcsname{#1}%save the label
            \addto@compressed@list{#1}%
          \else    % already defined and not equal to \relax
            \ifwarn@compress
              \@latex@warning{Label #1 is a duplicate of \@temptype\space\@templbl}%
            \fi
          \fi
        \else   %not defined yet
          \expandafter\gdef\csname Addval\@temptype\@templbl\endcsname{#1}%save the label
          \addto@compressed@list{#1}%
        \fi
      \fi
    \else
      \addto@compressed@list{#1}%
    \fi}

%Robust, allowing \cref to be used in headings (may or may not be desired)
\DeclareRobustCommand\@clear@list[1]{%
    % sets the addval\@temptype\@templbl commands to relax
  \ifcsname r@#1@cref\endcsname %only process if the label is defined
    \cref@gettype{#1}{\@temptype}% \@temptype := type of label (e.g., section)
    \cref@getlabel{#1}{\@templbl}% \@templbl := label (e.g., 2.5)
    \expandafter\gdef\csname
    Addval\@temptype\@templbl\endcsname{\relax}%
  \fi}

\def\compressed@list{\relax} % initially empty
\def\compress@cref@command#1{%
  \expandafter\let\expandafter\@tempccref\csname#1\endcsname  % \@tempa := original
  \expandafter\let\csname wantsnodups@#1\endcsname\@tempccref % wants... := \@tempa
                  % the original, will not see duplicates
  \@namedef{#1}##1{%
    \protect\IfSubStr{##1}{,}{%  unless arg is a list, avoid fuss
      \gdef\compressed@list{\relax}%ensure \relax
      \forcsvlist\@create@list{##1}%create compressed list
      \csname wantsnodups@#1\endcsname{\compressed@list}%pass compressed list to cref
      \expandafter\forcsvlist\expandafter\@clear@list\expandafter{\compressed@list}%clear compressed list
      }{\csname wantsnodups@#1\endcsname{##1}}}%
}
\compress@cref@command{cref}
\compress@cref@command{Cref}

You must log in to answer this question.

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