50 cleveref宏包在重定义\thepage中含有\textbf命令时出现报错的原因

发布于 2025-01-07 14:56:38

@u30 群主提出了一个cleveref宏包的疑似bug:

\documentclass{book} 
\usepackage{cleveref}
 
\renewcommand{\thepage}{\arabic{chapter}\textbf{/}\arabic{page}} 
\begin{document}

Text.\label{bug}

\end{document}

这将会导致

! Use of \label@optarg doesn't match its definition.
\text@command #1->\edef \reserved@a {
                                     \unexpanded {#1}}\ifx \reserved@a \@emp.

感觉这是个展开的问题,于是尝试添加抑制展开的\unexpanded后正常:

\documentclass{book} 
\usepackage{cleveref}
\renewcommand{\thepage}{\unexpanded{\textbf{abc}}} 
\begin{document}

Text.\label{bug}

\end{document}

我又尝试了其他的宏如\aaa,又一切正常。

\documentclass{book}
\def\aaa{234}
\usepackage{cleveref}
\renewcommand{\thepage}{\aaa} 
\begin{document}

Text.\label{bug}

\end{document}

除此之外联想到之前的提问,我有测试了ReNewDocumentCommandReNewExpandableDocumentCommand,前者正常而后者与\renewcommand报相同的错误.

\documentclass{book} 
\usepackage{cleveref}
\RenewDocumentCommand{\thepage}{}{\arabic{page}\textbf{/}\arabic{page}} % works
% \RenewExpandableDocumentCommand{\thepage}{}{\arabic{page}\textbf{/}\arabic{page}} % fails
\begin{document}

Text.\label{bug}

\end{document}

结合报错信息找到了cleveref.sty中的\label@optarg定义如下:

% Line 82-96
\def\label@optarg[#1]#2{%
    \cref@old@label{#2}%
    \@bsphack%
    \edef\@tempa{{page}{\the\c@page}}%
    \setcounter{page}{1}%
    \edef\@tempb{\thepage}%
    \expandafter\setcounter\@tempa%
    \cref@constructprefix{page}{\cref@result}%
    \protected@edef\cref@currentlabel{%
      \expandafter\cref@override@label@type%
        \cref@currentlabel\@nil{#1}}%
    \protected@write\@auxout{}%
      {\string\newlabel{#2@cref}{{\cref@currentlabel}%
      {[\@tempb][\arabic{page}][\cref@result]\thepage}}}%
    \@esphack}%
雾月老师提到过,所有DocumentCommandprotect宏,而newcommandNewExpandableCommand均可以被\edef展开,这应该也是上面为何两者均报错(吧)。

在上述\label@optarg命令定义中,为何需要保护\textbf(看latexdef他已经被\protect了)而不需要保护\aaa\bbb不被展开。


谢谢鱼老师的解释,可是下面的例子却是正常的(?):

\documentclass{book}
\def\bbb#1{txt{#1}txt}
\usepackage{cleveref}
\renewcommand{\thepage}{\bbb{111}} 
\begin{document}

Text.\label{bug}

\end{document}

另附latexdef \textbf的结果如下是一个protect`宏:

\textbf:
macro:->\protect \textbf

\textbf :
\long macro:#1->\ifmmode \nfss@text {\bfseries #1}\else \hmode@bgroup \text@command {#1}\bfseries \check@icl #1\check@icr \expandafter \egroup \fi

查看更多

关注者
0
被浏览
183
雾月
雾月 4天前
这家伙很懒,什么也没写!

同样是在这个链接的评论里提到,

为了使得 robust cmd 达到应有的效果,必须使用这几个 \protected@ 开头的命令,而 protected 宏则没有这个要求。

因为 \label@optarg 在第一次展开 \thepage 时用的是 \edef,而不是 \protected@edef,只有后者才能保护 \protect 宏。问题就在于此。

由于 LaTeX 内核用 begindocument 钩子 patch 了 cleveref 的几个命令,\label@optarg 恰好在其中,所以自己 patch 也必须使用这个钩子才行。可查看 latex2e-first-aid-for-external-files.pdf 这个文件了解 patch 了哪些命令。

2 个回答
鱼香肉丝没有鱼先生
脾气不好,别来惹我!!!

\aaa 是一个 macro, 不需要吃参数
\textbf 是一个 function, 需要吃参数

撰写答案

请登录后再发布答案,点击登录

发布
问题

分享
好友

手机
浏览

扫码手机浏览