使用 `\@ifnextchar` 的可变参数

使用 `\@ifnextchar` 的可变参数

为了保持一致的引用样式,定义了一个\secn能够处理多个参数的 Latex 宏。如果我提供一个参数s1,它应该返回Section~\ref{sec:lbl}。如果我提供多个参数s1,...,,sn它应该返回Sections~\ref{sec:s1}, ..., \ref{sec:s(n-1)} and \ref{sec:sn}

Sections下面的 Latex 宏几乎实现了这一点,只是在有多个参数的情况下它不会生成复数。

\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}

\@ifnextchar定义中的命令总是\myref返回 false。为什么会这样?我如何才能获得复数形式?

提前致谢

答案1

我建议不要使用带有可变参数的命令,而是使用以逗号分隔的列表作为参数的命令。您还可以使用包cleveref,已经提供了这样的命令:

\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}

使用选项排序

如果没有选项,则使用sort默认值sort&compress,结果如下:

带有 sort&compress 选项

答案2

您总是遇到 else 子句的问题,是因为您写了\newcommand{\myref}[4]{\@ifnextchar\bgroup{#2}{#1}~\myreflist{#3}{#4}},即\@ifnextchar\bgroup{#2}{#1}总是将 视为~下一个字符。 if-next-char 表示它后面的下一个字符,而不是它所属的宏!

您需要确保它后面没有任何内容,因此我建议将剩余的代码移到子句中:

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

答案3

我会避免使用“可变数量的参数”:用逗号分隔的列表更容易输入和管理。

xparse以下是使用和的实现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}

在此处输入图片描述

但是,使用cleveref更简单(并且更加可定制)。输出是相同的。

\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}

相关内容