@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}
除此之外联想到之前的提问,我有测试了ReNewDocumentCommand
和ReNewExpandableDocumentCommand
,前者正常而后者与\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}%
雾月老师提到过,所有DocumentCommand
是protect
宏,而newcommand
与NewExpandableCommand
均可以被\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
同样是在这个链接的评论里提到,
为了使得 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 了哪些命令。
啊!我好像当时就是这一段带
\protected@
没看懂然后跳过了,后来忘记惹...这下我明白
\edef
和\protected@edef
的区别了我会去看一下
latex2e-first-aid-for-external-files.pdf
,谢谢您!