4

For consistent style of references, defined a Latex macro \secn that is able to handle multiple arguments. If I provide a single argument s1, it should return Section~\ref{sec:lbl}. If I provire multiple arguments s1, ..., sn, it should return Sections~\ref{sec:s1}, ..., \ref{sec:s(n-1)} and \ref{sec:sn}.

The following Latex macro almost accomplishes this, except that it does not generate the plural Sections in case of multiple arguments.

\documentclass{article}

\def\secn{\myref{Section}{Sections}{sec}}

\makeatletter
\newcommand{\myref}[4]{\@ifnextchar\bgroup{#2}{#1}~\myreflist{#3}{#4}}
\newcommand{\myreflist}[2]{%
    \@ifnextchar\bgroup{\ref{#1:#2}\myreflisttail{#1}}{\ref{#1:#2}}}
\newcommand{\myreflisttail}[2]{\@ifnextchar\bgroup{, 
\ref{#1:#2}\myreflisttail{#1}}{ and \ref{#1:#2}}}
\makeatother

\begin{document}

\section{test}
\secn{s1} \par
\secn{s1}{s2} \par
\secn{s1}{s2}{s3}

\section{test1}
\label{sec:s1}
\section{test2}
\label{sec:s2}
\section{test3}
\label{sec:s3}

\end{document}

The \@ifnextchar command in the definition of \myref always returns false. Why is this the case? How can I get the plural form?

Thanks in advance

  • 2
    The cleveref package with the \cref command produces such output by default. – Andrew Swann Dec 28 '17 at 15:19
  • 1
    Macros with “variable number of arguments” are not the best way to go here. – egreg Dec 28 '17 at 15:26
  • A somewhat off-topic question: Am I the only one seeing the title incorrectly rendered? I see "Variable arguments with ∘ if ≠ xtchar". – md2perpe Jan 12 '18 at 18:59
4

Instead of using a command with variable arguments I would suggest to use a command with a comma-separated list as argument. You can also use package cleveref, that already provides such a command:

\documentclass{article}
\usepackage[sort]{cleveref}

\begin{document}

\section{test}
\cref{sec:s1} \par
\cref{sec:s1,sec:s2} \par
\cref{sec:s1,sec:s2,sec:s3}

\section{test1}
\label{sec:s1}
\section{test2}
\label{sec:s2}
\section{test3}
\label{sec:s3}

\end{document}

with option sort

Without option sort the default sort&compress is used, that would result in:

with options sort&compress

3

The problem you get always the else clause is because you wrote \newcommand{\myref}[4]{\@ifnextchar\bgroup{#2}{#1}~\myreflist{#3}{#4}}, i.e. \@ifnextchar\bgroup{#2}{#1} always sees the ~ as next character. The if-next-char means the next char after it, not the macro it is part of!

You need to make sure that there is nothing after it, so I would suggest to move the remaining code into the clauses:

\newcommand{\myref}[4]{\@ifnextchar\bgroup{#2~\myreflist{#3}{#4}}{#1~\myreflist{#3}{#4}}}
3

I'd avoid “variable number of arguments”: a comma separated list is even simpler to type and to manage.

Here's an implementation with xparse and expl3:

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\generalref}{mmmm}
 {
  \kasper_generalref:nnnn { #1 } { #2 } { #3 } { #4 }
 }
\NewDocumentCommand{\secn}{m}
 {
  \generalref{Section}{Sections}{sec}{#1}
 }

\seq_new:N \l_kasper_generalref_seq

\cs_new_protected:Nn \kasper_generalref:nnnn
 {
  % clear the sequence
  \seq_clear:N \l_kasper_generalref_seq
  % populate it
  \clist_map_inline:nn { #4 }
   {
    \seq_put_right:Nn \l_kasper_generalref_seq { \ref{#3:##1} }
   }
  % use first argument if less than two items
  \int_compare:nTF { \seq_count:N \l_kasper_generalref_seq < 2 } { #1 } { #2 }
  % no break between label and first reference
  \nobreakspace
  % issue the references (here ~ means a normal space)
  \seq_use:Nnnn \l_kasper_generalref_seq
   { ~and\nobreakspace } % between two
   { ,~ } % between more than two
   { ~and\nobreakspace } % between last two
 }

\ExplSyntaxOff

\begin{document}

\section{test}
\secn{s1} \par
\secn{s1,s2} \par
\secn{s1,s2,s3}

\section{test1}
\label{sec:s1}
\section{test2}
\label{sec:s2}
\section{test3}
\label{sec:s3}

\end{document}

enter image description here

Using cleveref is simpler, though (and more customizable}. The output is the same.

\documentclass{article}
\usepackage{xparse}
\usepackage[nosort]{cleveref}

\ExplSyntaxOn

\NewDocumentCommand{\gcref}{mm}
 {% #1 is the common prefix, #2 is the list of labels
  \kasper_gcref:nn { #1 } { #2 }
 }
\NewDocumentCommand{\secn}{m}
 {
  \gcref{sec}{#1}
 }

\seq_new:N \l_kasper_gcref_seq

\cs_new_protected:Nn \kasper_gcref:nn
 {
  % clear the sequence
  \seq_clear:N \l_kasper_gcref_seq
  % populate it
  \clist_map_inline:nn { #2 }
   {
    \seq_put_right:Nn \l_kasper_gcref_seq { #1:##1 }
   }
  \Cref {\seq_use:Nn \l_kasper_gcref_seq { , } }
 }

\ExplSyntaxOff

\begin{document}

\section{test}
\secn{s1} \par
\secn{s1,s2} \par
\secn{s1,s2,s3}

\section{test1}
\label{sec:s1}
\section{test2}
\label{sec:s2}
\section{test3}
\label{sec:s3}

\end{document}

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.