# How can I automatically abbreviate page ranges in citations (biblatex)

I am using biblatex for my citations, and I was wondering if there was a way to automatically abbreviate the page ranges in citations. (The reason I ask is because I have them all in long-style, and I need to change about 200 citations). So what I want is:

\cite[111-119]{book1}


to give me:

Author1, Title1, pp. 111-9


Is this possible in biblatex?

-
You might find newsgroups.derkeiler.com/Archive/Comp/comp.text.tex/2008-07/… interesting. –  N.N. Jul 19 '11 at 12:32
@N.N., the solution given in the discussion you linked to is for BibTeX and cannot be used with biblatex. –  domwass Jul 19 '11 at 13:33

The page range compression recently feature added to biblatex (version 1.6) has been mentioned a couple times in the existing answers. This answer just provides some details.

Compression is carried out using the command \mkcomprange. It can be applied to both the postnote and pages fields via \DeclareFieldFormat. In postnotes \mkcomprange can handle lists of page ranges provided that they are delimited by commas and/or semicolons.

Behaviour of \mkcomprange can be configured using three different counters. By default:

\setcounter{mincomprange}{10}
\setcounter{maxcomprange}{100000}
\setcounter{mincompwidth}{1}


Compression is applied to any range where the lower limit is greater than mincomprange and has no more digits than maxcomprange. The upper limit in a compressed range is displayed using at least the same number of digits in mincompwidth. An exception to this is when the compressed upper limit has leading zeros; these are suppressed.

\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage[american]{babel}
\usepackage{csquotes}
\usepackage[style=authoryear,maxcitenames=1]{biblatex}

\DeclareFieldFormat{postnote}{\mkcomprange[{\mkpageprefix[pagination]}]{#1}}
\DeclareFieldFormat{pages}{\mkcomprange{#1}}

% Compress ranges where lower limit > 100
\setcounter{mincomprange}{100}

% Don't compress beyond the fourth digit
\setcounter{maxcomprange}{1000}

% Display compressed upper limit with at least two digits,
% unless leading digit is zero
\setcounter{mincompwidth}{10}

\begin{document}
\cites{salam}[100--110, 101--109, 101--110]{companion} \\
\cite[100--110; 101--110, esp. last paragraph]{companion} \\
\cite[101-110, 1000--1100, 10000--11000]{companion}
\printbibliography
\end{document}


-
Great! This worked perfectly, and is by far the easiest of the solutions given. Thanks! –  Tom de Bruin Aug 28 '11 at 15:12

This question was answered in March 2010 by Marco Daniel at the German-speaking forum mrunix.de. Marco used the xstring package and some (in my opinion) ingenious low-level TeX hacks. In the following example, I have used the original code of Marco's core macro \mdprintpages (and marked two minor changes), but removed the need to change the article bibibliography driver (by instead using \DeclareFieldFormat{pages}{\mdprintpages}).

\documentclass{article}

\usepackage{biblatex}

\usepackage{xstring}

% NB: These are much more restrictive than defaults in biblatex.sty
\DeclareRangeChars{-}
\DeclareRangeCommands{\bibrangedash}

\DeclareFieldFormat{pages}{\mkabbrpagerange{#1}}
\DeclareFieldFormat{postnote}{\mkabbrpagerange{#1}}

\newrobustcmd{\mkabbrpagerange}[1]{%
\ifthenelse{\ifpages{#1}\AND\NOT\ifnumeral{#1}}
{\ppno\ppspace\mdprintpages{#1}}
{\mkpageprefix[pagination]{#1}}}

\makeatletter

% START OF ORIGINAL CODE BY MARCO DANIEL
\newif\ifnum@one@empty
\newif\ifnum@two@empty
\def\mdprintpages#1{% EDITED
\StrDel{#1}{ }[\md@tempI]% EDITED
\StrSubstitute{\md@tempI}{\bibrangedash}{,}[\md@tempI]%
\expandafter\expandafter\expandafter\md@printpages\md@tempI,,\@nil}
\def\md@printpages#1,#2,#3\@nil{%
\def\num@one{#1}%
\def\num@two{#2}%
\ifx\@empty\num@one
\num@one@emptytrue
\else
\num@one@emptyfalse
\fi
\ifx\@empty\num@two
\num@two@emptytrue
\else
\num@two@emptyfalse
\fi
\ifnum@two@empty
\ifnum@one@empty
\else
%                ,\ \num@one% DELETED BY LOCKSTEP
\fi
\else
\StrCompare{\num@one}{\num@two}[\Result]%
%            ,\ \num@one\,--\,\StrGobbleLeft{0\num@two}{\Result}% DELETED BY LOCKSTEP
\fi
}
\makeatother
% END OF ORIGINAL CODE BY MARCO DANIEL

\usepackage{filecontents}

\begin{filecontents}{\jobname.bib}
@article{Abel1995,
author = {S. Abel and M. D. Nguyen and W. Chow and A. Theologis},
title = {\textit{ACS4}, a primary etc.},
journaltitle = {J Biol Chem},
year = {1995},
volume = {270},
number = {32},
pages = {19093--19099},
}
\end{filecontents}

\begin{document}

Abbreviated page range: \cite[19093-19097]{Abel1995}

Abbreviated page range: \cite[19093--19097]{Abel1995}

Abbreviated page range: \cite[19093 \bibrangedash 19097]{Abel1995}

Page reference, but not a range: \cite[19093]{Abel1995}

Unabbreviated page range: \cite[19093--20093]{Abel1995}

Not recognized as a page range, so the page prefix must be added manually:
\cite[19093, 19094]{Abel1995}
\cite[19092, 19093--19097]{Abel1995}
\cite[19093--19095, last paragraph]{Abel1995}

\printbibliography

\end{document}


Note that there's also a feature request for abbreviated page ranges at SourceForge.net:biblatex.

EDIT 1: Taking a closer look, this is only a partial solution because it only affects the format of page numbers in the bibliography. Hopefully, someone versed in low-level TeX hackery will show how to format citation postnotes the same way.

EDIT 2: The solution now abbreviates pages ranges as postnotes in citations, but ranges are specified only by -. So prefixes must be added manually to page ranges no longer recognized as such. This is restrictive, but avoids having additional material in the postnote being omitted by \mdprintpages.

EDIT 3: The feature request at SourceForge.net:biblatex is now pending and will be implemented in biblatex v1.6. See PLK's comment to his answer for details.

EDIT 4: biblatex 1.6, which includes range compression, was released on July 29th, 2011.

-
You can use a modification of this code in the postnote-field: \DeclareFieldFormat{postnote}{\mkpageprefix[pagination]{#1}} –  Marco Daniel Jul 19 '11 at 19:35
@Marco: Sorry, I can't get this to work. Would you mind to edit "my" answer or to provide your own one? –  lockstep Jul 19 '11 at 19:39
@Marco: \mdprintpages uses \abx@field@pages -- this can't be right for the postnote field? –  lockstep Jul 19 '11 at 19:44
I mean the algorithm of xstring. Tomorrow I can modify my code. –  Marco Daniel Jul 19 '11 at 19:46
Adding onto the comment from @Marco: The argument #1 could take the place of \abx@field@pages. To correctly parse the page ranges, you'll at least need -- and - in place of \bibrangedash. Identifying page ranges in postnotes is tough, though. One quick way to do it would be to specify \DeclareRangeChars{-} and use \ifpages. –  Audrey Jul 19 '11 at 19:48

I'm looking into implementing this directly in biber via a biblatex option for the next release. For range "111-118", something like:

rangeend=auto


would get you

111--8


and

rangeend=2


would get

111--18


default would be

rangeend=none

111--118

-
By the implementation in biber I have no influence to the \cite command. So I think it is better to create an option on the tex-level. –  Marco Daniel Jul 20 '11 at 16:22
Right, this only really applies to the bibliography and the PAGES field. –  PLK Jul 20 '11 at 19:28
In fact, I think this won't be done in biber as it makes more sense to do all page ranges in biblatex for consistency. Please add your comment requesting this to the open biblatex feature request mentioned above. –  PLK Jul 23 '11 at 15:08
I thought it was worth letting people know, this will be implemented in biblatex 1.6 which is out soon. The biblatex author came up with a good solution. It will work in citations and in the bibliography with user-settable customisations to define the ranges which are compressed. –  PLK Jul 24 '11 at 16:29
Well -- Thanks for the information –  Marco Daniel Jul 24 '11 at 17:19

Try this one:

\listfiles
\setcounter{errorcontextlines}{999}
\documentclass{article}

\usepackage{biblatex}

\usepackage{xstring}

% NB: These are much more restrictive than defaults in biblatex.sty
\DeclareRangeChars{-}
\DeclareRangeCommands{\bibrangedash}

\DeclareFieldFormat{pages}{\mkpageprefix[bookpagination]{#1}}
%\DeclareFieldFormat{postnote}{\mkabbrpagerange{#1}}
\DeclareListParser*{\testdoranges}{,}
\long\def\mdprintpages#1{%
\ifboolexpr{ test {\ifnumeral{#1}} or test {\ifnumerals{#1}}}%
{\mkpageprefix[pagination]{#1}}%
{\printtext{#1}}%
}
\DeclareFieldFormat{postnote}{%
\testdoranges{\mdprintpages}{#1}%
}

\makeatletter
\long\def\blx@mkpageprefix@i[#1]#2{#1{\md@printpages{#2}}\endgroup}

% START OF ORIGINAL CODE BY MARCO DANIEL
\newif\ifnum@one@empty
\newif\ifnum@two@empty
\def\md@printpages#1{% EDITED
\StrDel{#1}{ }[\md@tempI]% EDITED
\StrSubstitute{\md@tempI}{\bibrangedash}{,}[\md@tempI]%
\expandafter\expandafter\expandafter\md@@printpages\md@tempI,,\@nil}
\def\md@@printpages#1,#2,#3\@nil{%
\def\num@one{#1}%
\def\num@two{#2}%
\ifx\@empty\num@one
\num@one@emptytrue
\else
\num@one@emptyfalse
\fi
\ifx\@empty\num@two
\num@two@emptytrue
\else
\num@two@emptyfalse
\fi
\ifnum@two@empty
\ifnum@one@empty
\else
%                ,\ \num@one% DELETED BY LOCKSTEP
\fi
\else
\StrCompare{\num@one}{\num@two}[\Result]%
%            ,\ \num@one\,--\,\StrGobbleLeft{0\num@two}{\Result}% DELETED BY LOCKSTEP
\fi
}
\makeatother
% END OF ORIGINAL CODE BY MARCO DANIEL

\usepackage{filecontents}

\begin{filecontents}{\jobname.bib}
@article{Abel1995,
author = {S. Abel and M. D. Nguyen and W. Chow and A. Theologis},
title = {\textit{ACS4}, a primary etc.},
journaltitle = {J Biol Chem},
year = {1995},
volume = {270},
number = {32},
pages = {19093--19099},
}
\end{filecontents}

\begin{document}

Abbreviated page range: \cite[19093-19097]{Abel1995}

Abbreviated page range: \cite[19093--19097]{Abel1995}

Abbreviated page range: \cite[19093 \bibrangedash 19097]{Abel1995}

Page reference, but not a range: \cite[19093]{Abel1995}

Unabbreviated page range: \cite[19093--20093]{Abel1995}

Not recognized as a page range, so the page prefix must be added manually:
\cite[19093, 19094]{Abel1995}
\cite[19092, 19093--19097]{Abel1995}
\cite[19093--19095, last paragraph]{Abel1995}
\cite[first paragraph, 19093--19095]{Abel1995}

\cite[first paragraph, 19093--19095, 19092, 19093--19097 ]{Abel1995}

\printbibliography

\end{document}

-