How can I automatically abbreviate page ranges in citations (biblatex)
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}
\addbibresource{biblatex-examples.bib}
\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}
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]%
\StrSubstitute{\md@tempI}{--}{,}[\md@tempI]% ADDED
\StrSubstitute{\md@tempI}{-}{,}[\md@tempI]% ADDED
\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
\num@one% ADDED BY LOCKSTEP
\fi
\else
\StrCompare{\num@one}{\num@two}[\Result]%
% ,\ \num@one\,--\,\StrGobbleLeft{0\num@two}{\Result}% DELETED BY LOCKSTEP
\num@one\,--\,\StrGobbleLeft{0\num@two}{\Result}% ADDED 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}
\addbibresource{\jobname.bib}
\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.
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