为什么我对 addcontentsline 的修补在 \AtEndPreamble 和 \bool_if:NT 中损坏了?

为什么我对 addcontentsline 的修补在 \AtEndPreamble 和 \bool_if:NT 中损坏了?

我想修补\thepage序言中的某个布尔\addcontentsline2*\c@page + 1是否为真。我将代码简化为:

\documentclass{article}
\usepackage{etoolbox}
\makeatletter
\ExplSyntaxOn
\AtEndPreamble
{
  \bool_if:NT \c_true_bool
    {\patchcmd{\addcontentsline}{\thepage}{\int_eval:n {2 * \c@page + 1}}{}{\fail}}
}
% \patchcmd{\addcontentsline}{\thepage}{\int_eval:n {2 * \c@page + 1}}{}{\fail}
\ExplSyntaxOff
\makeatother
\begin{document}
  \tableofcontents
  \section{test}
\end{document}

.toc里面的东西

\contentsline {section}{\numberline {1}test}{\int _eval:n {2*\c@page +1}}{}%

我们可以看到 是\int _eval:n {2*\c@page +1}错误的,没有计算3。但是,如果我删除\AtEndPreamble\bool_if:NT \c_true_bool,我会得到

\contentsline {section}{\numberline {1}test}{1}{}%

1仍然是1。这似乎“\patchcmd”仅在组中起作用,是否有“全局”版本?

如果我删除两个命令并删除该组,1则会更改为3,这正是我想要的。

我的问题是为什么会发生这种情况以及我该如何解决它?


编辑:在此示例中,@user202729 的方法有效。但是,如果我添加hyperref\thepage\addcontentsline不再可修补。

[debug] tracing \ifpatchable on input line 23
[debug] analyzing '\addcontentsline'
[debug] ++ control sequence is defined
[debug] ++ control sequence is a macro
[debug] -- macro cannot be retokenized cleanly
[debug] -> the macro may have been defined under a category
[debug]    code regime different from the current one
[debug] -> the replacement text may contain special control
[debug]    sequence tokens formed with \csname...\endcsname;
[debug] -> the replacement text may contain carriage return,
[debug]    newline, or similar characters

类似问题:hyperref 和 \addcontentsline 在 expl3 块中的补丁

答案1

您的代码存在一些问题,主要问题是由于类别代码造成的。发生了什么?

  1. 您需要\ExplSyntaxOn按顺序编写代码,以便 TeX 能够正确解释\int_eval:n

  2. 修补程序在不同的上下文中执行,此时\ExplSyntaxOn不再有效。

这就是\patchcmd失败的地方。我们需要了解它的工作原理。在 TeX 中,如果不知道宏的参数文本到底是什么,就无法访​​问宏的替换文本,也无法知道参数文本到底是什么。我们只能通过 检查并保存参数和替换文本\meaning,但这会将它们生成为类别代码为 12 个字符的字符串(但空格的类别代码为 10)。

它会这样做\patchcmd,首先通过 重建宏的副本,以便\scantokens将其与要修补的宏进行比较。如果比较失败,则\patchcmd使用@类别代码 11 并再次尝试重建和比较。如果这次尝试也失败了,则\patchcmd放弃并告诉您该宏不可修补。否则它将继续执行修补。同样,它必须使用类别代码为 12 的字符串并执行\scantokens。这就是您的案例失败的地方:当尝试修补时,_类别代码为 8,但您的代码希望它具有类别代码 11,并且\patchcmd根本无法知道这一点,除非您告诉它。

\documentclass{article}
\usepackage{etoolbox}

\makeatletter
\ExplSyntaxOn
\AtEndPreamble
 {
  \bool_if:NT \c_true_bool
   {
    \ExplSyntaxOn
    \patchcmd{\addcontentsline}{\thepage}{\int_eval:n {2 * \c@page + 1}}{}{\fail}
    \ExplSyntaxOff
   }
 }
\ExplSyntaxOff
\makeatother

\begin{document}

\tableofcontents

\section{test}

\end{document}

这是因为要修补的宏没有空格,其参数或替换文本中_也没有空格:。否则修补会因与之前相同的原因而失败。

一个更简单的策略:在安全的地方进行修补并避免“奇怪”的类别代码。

\documentclass{article}
\usepackage{etoolbox}
\usepackage{hyperref}

\NewCommandCopy{\patchedaddcontentsline}{\addcontentsline}
\patchcmd{\patchedaddcontentsline}{\thepage}{\makeodd{page}}{}{\fail}

\ExplSyntaxOn
\NewExpandableDocumentCommand{\makeodd}{m}
 {
  \int_eval:n {2*\value{#1}+1}
 }
\AtEndPreamble
 {
  \bool_if:NT \c_true_bool
   {
    \RenewCommandCopy{\addcontentsline}{\patchedaddcontentsline}
   }
 }
\ExplSyntaxOff

\begin{document}

\tableofcontents

\section{test}

\end{document}

答案2

  1. 正如文档所述

    该任务是本地的。

    你不能使它全球化。

    虽然...你可以这样作弊

    \patchcmd{\addcontentsline}{\thepage}{\int_eval:n {2 * \c@page + 1}}{}{\fail}
    \global\let\addcontentsline\addcontentsline
    

    这似乎有效,但我不确定它对保存堆栈有什么不良影响。

    或者,复制的定义\patchcmd并添加\global到适当的位置。

  2. 对于 catcode 部分,如果文档中有你不明白的地方,不要直接跳过。重要信息都写在那里。

    请注意,修补过程涉及对⟨command⟩的替换文本进行去标记化,并在修补后根据当前类别代码制度对其进行重新标记。@ 符号的类别代码暂时设置为 11。如果⟨command⟩的替换文本包含任何具有非标准类别代码的标记,修补前必须调整相应的类别代码

    尤其

    • 这里的“替换文本”由组成\int_eval:n {2 * \c@page + 1}
    • \int_eval:n其中包括具有非标准类别代码(\ExplSyntaxOn区域)的标记。

    所以一种方法就是这样做。

    \documentclass{article}
    \usepackage{etoolbox}
    \makeatletter
    \ExplSyntaxOn
    \AtEndPreamble
    {
      \bool_if:NT \c_true_bool
        {
        \ExplSyntaxOn
        \patchcmd{\addcontentsline}{\thepage}{\int_eval:n {2 * \c@page + 1}}{}{\fail}
        \ExplSyntaxOff
    }
    }
    % \patchcmd{\addcontentsline}{\thepage}{\int_eval:n {2 * \c@page + 1}}{}{\fail}
    \ExplSyntaxOff
    \makeatother
    \begin{document}
      \tableofcontents
      \section{test}
    \end{document}
    

    对于\makeatletter\makeatother,您恰好不需要,因为它是自动的,如上面的文档中所述。但对于 expl3 代码,您确实需要它。

    附注:您仍然需要外部\ExplSyntaxOn,您无法更改命令参数内的 catcode,请参阅宏 - 为什么 \makeatletter 在 \newcommand 中不起作用? - TeX - LaTeX Stack Exchange

    或者,使用如下包装器命令,让 ⟨replacement text⟩ 在正常的 catcode 下是安全的。

    \documentclass{article}
    \usepackage{etoolbox}
    \makeatletter
    \ExplSyntaxOn
    \cs_new:Npn \myExpandableCommandSafeUnderNormalCatcode {
        \int_eval:n {2 * \c@page + 1}
    }
    \AtEndPreamble
    {
      \bool_if:NT \c_true_bool
        {
        \patchcmd{\addcontentsline}{\thepage}{\myExpandableCommandSafeUnderNormalCatcode}{}{\fail}
    }
    }
    % \patchcmd{\addcontentsline}{\thepage}{\int_eval:n {2 * \c@page + 1}}{}{\fail}
    \ExplSyntaxOff
    \makeatother
    \begin{document}
      \tableofcontents
      \section{test}
    \end{document}
    

答案3

使用\inteval代替\int_eval:n。您也可以\c@page用以下方式替换\value{page}

\documentclass{article}
\usepackage{etoolbox}
\usepackage{hyperref}
\makeatletter
\ExplSyntaxOn
\AtEndPreamble
{
  \bool_if:NT \c_true_bool
    {    
    \patchcmd{\addcontentsline}{\thepage}{\inteval {2 *  \value{page} + 1}}{}{\fail}    
}
}
\ExplSyntaxOff
\makeatother
\begin{document}
  \tableofcontents
  \section{test}
\end{document}

相关内容