引用简历*列表中的项目会呈现附加文本

引用简历*列表中的项目会呈现附加文本

我想让我的生活自动化一点,发现该enumitem包提供了一种创建自定义列表的方法,可以根据前一个列表恢复计数。如果没有这个,我必须使用明确操作计数器,enumi\setcounter可能会变得很痛苦,因为很容易无法正确计数。:-) 无论如何,enumitem使用时,连续计数有效resume*。但是,这样做会破坏 cleverref 的引用!引用该列表中的项目时,resume,,会打印额外的文本。我该如何摆脱它?我在序言中做错了什么,或者这是一种错误?

描述该问题的图片

\documentclass[a4paper,english]{article}

\usepackage{enumitem}
\usepackage{amssymb}
\usepackage{cleveref}

% defining theorem environments for their specialized versions
\makeatletter
\newenvironment{base@thm}[3]%
{$\;$\linebreak\noindent\mbox{$\triangleright\;\textsc{\textbf{\large #1} #2 (#3).}$}}
{\hfill$\;$\linebreak}
\makeatother

% lemma environments take a label and a name
\newcounter{lemma}
\crefname{lemma}{lemma}{lemmata}
\makeatletter
\newenvironment{lemma}[2]%
{\refstepcounter{lemma}\begin{base@thm}{Lemma}{\thelemma}{#2}\label[lemma]{lemma:#1}\def\@currentlabel{#2}\label{t@lemma:#1}}
{\end{base@thm}\noindent}
\makeatother
% referencing a lemma
\newcommand{\lemmaref}[1]{\Cref{lemma:#1}~(\ref{t@lemma:#1})}
\newcommand{\lemref}[1]{\Cref{lemma:#1}}

\newlist{passumptionslist}{enumerate}{1}
\setlist[passumptionslist]{label=(\alph*)}

\crefalias{asm}{passumptionslisti}
\crefalias{goal}{enumi}
\crefname{asm}{assumption}{assumptions}
\crefname{goal}{goal}{goals}
\newenvironment{assumptions}
    {\begin{passumptionslist}[label=(\alph*)]
    }
    {
    \end{passumptionslist}
  }
\newenvironment{goals}
    {\begin{enumerate}[label=(\roman*)]
    }
    {
  \end{enumerate}
  }

\newcommand{\IH}[1][]{I{\kern-1.5pt}H#1}
\newenvironment{passumptions}[1]
  {%
  \begin{passumptionslist}[resume*,label={$\left(#1_{\arabic*}\right)$}]%
  }
  {%
  \end{passumptionslist}%
  }
% individual cases
\newcommand{\asm}[2]{\item\label[asm]{asm:#1} {$#2$}}
\newcommand{\goal}[2]{\item\label[goal]{goal:#1} {$#2$}}
\newcommand{\asmref}[1]{\Cref{asm:#1}}
\newcommand{\goalref}[1]{\Cref{goal:#1}}

\newenvironment{proof}[1][\textbf{Proof}]%
{\restartlist{passumptionslist}\par\noindent\ignorespaces\mbox{\textsc{#1}.}$\;$\\\noindent}
{$\;$\hfill$\square$\\\ignorespacesafterend}


\begin{document}

\begin{lemma}{lemma}{Some Lemma Name}
  If
  \begin{assumptions}
    \asm{lemma}{a}
  \end{assumptions}
  then
  \begin{goals}
    \goal{lemma}{b}
  \end{goals}
\end{lemma}
\begin{proof}
  Unfold \goalref{lemma} and introduce the assumptions.
  \begin{passumptions}{H}
    \asm{lemma0}{v\Downarrow b}
  \end{passumptions}
  What is left to show is:
  \begin{goals}
    \goal{lemma0}{c}
  \end{goals}

  From \asmref{lemma0}, get:
  \begin{passumptions}{H}
    \asm{lemma1}{a=v}
  \end{passumptions}

  Rewrite \asmref{lemma} and \asmref{lemma0} using \asmref{lemma1}:
  \begin{passumptions}{H}
    \asm{lemmap}{v=c}
    \asm{lemma0p}{a\Downarrow b}
  \end{passumptions}

  Use \asmref{lemma0p}:
  \begin{passumptions}{H}
    \asm{lemma2}{a=b}
  \end{passumptions}
  Finally, rewrite \goalref{lemma0} via \asmref{lemma1}.
  Then, \asmref{lemma2} solves it.
\end{proof}


\end{document}

答案1

这个答案解释了为什么resume,,要打印。

从根本上来说,有两件事是相互冲突的。

[resume*]如果没有先例,名单就会失效

您可以通过以下简单的 MWE 看到这种行为:

\documentclass{article}

\usepackage{enumitem}

\begin{document}

\begin{enumerate}[resume*]
        \item Test
\end{enumerate}
\end{document}

为什么会这样?要启用resume*enumitem代码必须将列表配置从上次发布列表传递到下次调用列表。它通过将\enit@resumekeys@<list>(其中<list>替换为列表名称,即enumeratepassumptionslist)设置为(本质上)作为列表的可选参数传递的内容来实现enumitem这将在列表末尾执行,因此当\end{enumerate}在上述示例中遇到时。并且创建了默认值;因此在第一次调用列表之前,宏\enit@resumekeys@<list>不明确的

现在,当resume*被传递给列表时,enumitem会尝试从列表的最后一次调用中加载配置,因此它会查看\enit@resumekeys@<list>。值得注意的是,它使用\csname ... \endcsname构造来实现这一点,因此当\enit@resumekeys@<list>未定义时,这不会出错(出现未定义的宏错误)。当您使用\csname ... \endcsname访问未定义的宏时,它总是返回\relax

为了允许当前调用中的键覆盖先前指定的键,enumitem 添加将此转换为可选参数。因此,resume*在 MWE 中处理之后,有效的可选参数变为\relax,resume

但是现在,要处理选项列表,enumitem需要一次处理一个逗号分隔的列表。它使用标准技巧来实现这一点。它使用以下宏定义

\def\enitkv@do#1,{%
    \ifx\relax#1\empty\else
    \enitkv@split#1==\relax %% This processes the key, ignore it for now
    \expandafter\enitkv@do\fi}

宏以逗号分隔。因此,当输入一个以逗号分隔的列表时,它将吃掉下一串字符,直到下一个,;它将处理该字符串,丢弃它(和逗号),然后对下一个参数再次执行相同的操作。

这个宏怎么知道该怎么停止呢?它遇到 时就停止\relax。实际上,这个宏是通过

\enitkv@do#2,\relax,

其中#2是可选参数列表。现在你看到问题了,当你使用resume*没有先行词时上述宏在调用时会处理

\enitkv@do\relax,resume,\relax,

当第一个\relax命中时,宏终止。所以我们剩下的是一个悬空的

resume,\relax,

由于这些不是任何宏的参数,因此它们将作为文本处理。并且作为\relax无操作,打印时会变成

resume,,

你所看到的。

群组

但是你说,等一下,你还assumptions根据 定义了环境passumptionslist,并且你称它为在您的代码中调用第一个passumptions。这是否意味着实际上应该有一个先行词?

您应该将以下两个 MWE 与之进行比较。

MWE 1(无团体)

\documentclass{article}

\usepackage{enumitem}

\begin{document}

\begin{enumerate}
    \item Test
\end{enumerate}

\begin{enumerate}[resume*]
        \item Test
\end{enumerate}
\end{document}

MWE 2 (与团体)

\documentclass{article}

\usepackage{enumitem}

\begin{document}

\bgroup
\begin{enumerate}
    \item Test
\end{enumerate}
\egroup

\begin{enumerate}[resume*]
        \item Test
\end{enumerate}
\end{document}

MWE 1 可以正常运行,但 MWE 2 则会出现异常。那么这里到底发生了什么?

以下是 中的一项功能enumitem,尽管我不确定这是否是个好主意。在列表中保存当前选项时,enumitem在四种不同情况下的行为有所不同。

  1. 当您有一个series选项(不是resume或 )时resume*,代码将使用 保存当前计数器值和当前可选参数\global\def
  2. 当您有一个series选项也是resume或 时resume*,代码仅通过 保存计数器\global\def,但不会保存当前可选参数。
  3. 当您没有series但有 时resume*,代码仅通过 保存计数器\global\def,但不会保存可选参数。
  4. series当您有一个既不是也不是的普通列表时resume*(但奇怪的是,resume这里没问题),代码会使用 保存计数器和可选参数\def

这会导致几个有趣的副作用。

  • 如果您将 包裹\begin{enumerate}[resume]...\end{enumerate}起来\newenvironment,则会触发第四种情况。但由于环境是分组的,因此\def不会传播出环境。这解释了为什么编号是错误的,正如您在此评论
  • 使用您的原始代码作为参考,当您包装在通过定义的环境\begin{passumptionslist}...\end{passumptionslist}中时,当您调用时,包会尝试保存当前的计数器和列表选项,但正如我们处于情况 4 中一样,这些都是通过本地定义制作的,所以一旦我们击中这些就会丢失。assumptions\newenvironment\begin{assumptions}...\end{assumptions}\end{passumptionslist}\end{assumptions}

上面发现的第一个问题有点奇怪,即 的正常使用enumitem不应该调用[resume*]之前未使用过的列表。也就是说,只需使用不同的标记来划分 中的列表末尾,即可修复此边缘情况\def\enitkv@do#1,

第二个问题实际上有记录(尽管记录得有些差)。在当前版本的 enumitem 文档中,resume第 3.3 节中顺便提到了 local。尽管这种奇怪的交互resume*没有明确记录(但绝对应该记录)。

答案2

我认为没有必要使用passumptionslist包装在各种环境中的底层。最好将所有内容都定义为\newlists。

%%%% UNTIL ANNOUNCED, SAME AS YOUR CODE %%%%
\documentclass[a4paper,english]{article}

\usepackage{enumitem}
\usepackage{amssymb}
\usepackage{cleveref}

% defining theorem environments for their specialized versions
\makeatletter
\newenvironment{base@thm}[3]%
{$\;$\linebreak\noindent\mbox{$\triangleright\;\textsc{\textbf{\large #1} #2 (#3).}$}}
{\hfill$\;$\linebreak}
\makeatother

% lemma environments take a label and a name
\newcounter{lemma}
\crefname{lemma}{lemma}{lemmata}
\makeatletter
\newenvironment{lemma}[2]%
{\refstepcounter{lemma}\begin{base@thm}{Lemma}{\thelemma}{#2}\label[lemma]{lemma:#1}\def\@currentlabel{#2}\label{t@lemma:#1}}
{\end{base@thm}\noindent}
\makeatother
% referencing a lemma
\newcommand{\lemmaref}[1]{\Cref{lemma:#1}~(\ref{t@lemma:#1})}
\newcommand{\lemref}[1]{\Cref{lemma:#1}}

%%%% CHANGES BELOW %%%%
% 1. Removed passumptslist
% 2. Removed your crefaliases

% The crefname are kept
\crefname{asm}{assumption}{assumptions}
\crefname{goal}{goal}{goals}

% 3. Defined assumptions, passumptions, and goals directly as new lists
\newlist{assumptions}{enumerate}{1}
\newlist{passumptions}{enumerate}{1}
\newlist{goals}{enumerate}{1}

% 3. (cont'd) and set their formatting directly
\setlist[assumptions]{label=(\alph*)}
\setlist[goals]{label=(\roman*)}

% 4. Set the format for passumptions; define macro to change the label.
\makeatletter
\newcommand*\setPassLabel[1]{\gdef\@passLabel{#1}}
\setlist[passumptions]{resume,label={$(\@passLabel_{\arabic*})$}}
\makeatother

%%%%% BELOW AGAIN UNCHANGED, EXCEPT MARKED LINES %%%%%%%
\newcommand{\IH}[1][]{I{\kern-1.5pt}H#1}
% individual cases
\newcommand{\asm}[2]{\item\label[asm]{asm:#1} {$#2$}}
\newcommand{\goal}[2]{\item\label[goal]{goal:#1} {$#2$}}
\newcommand{\asmref}[1]{\Cref{asm:#1}}
\newcommand{\goalref}[1]{\Cref{goal:#1}}

%%%% Make the list that is restarted the passumptions list, and not passumptionslist.
\newenvironment{proof}[1][\textbf{Proof}]%
{\restartlist{passumptions}\par\noindent\ignorespaces\mbox{\textsc{#1}.}$\;$\\\noindent}
{$\;$\hfill$\square$\\\ignorespacesafterend}


\begin{document}

\begin{lemma}{lemma}{Some Lemma Name}
  If
  \begin{assumptions}
    \asm{lemma}{a}
  \end{assumptions}
  then
  \begin{goals}
    \goal{lemma}{b}
  \end{goals}
\end{lemma}
\begin{proof}
        \setPassLabel{H}   %%% Set the label for the passumptions. 
  Unfold \goalref{lemma} and introduce the assumptions.
  \begin{passumptions}   %%% No more mandatory argument (Ditto below)
    \asm{lemma0}{v\Downarrow b}
  \end{passumptions}
  What is left to show is:
  \begin{goals}
    \goal{lemma0}{c}
  \end{goals}

  From \asmref{lemma0}, get:
  \begin{passumptions}
    \asm{lemma1}{a=v}
  \end{passumptions}

  Rewrite \asmref{lemma} and \asmref{lemma0} using \asmref{lemma1}:
  \begin{passumptions}
    \asm{lemmap}{v=c}
    \asm{lemma0p}{a\Downarrow b}
  \end{passumptions}

  Use \Cref{asm:lemma0p}; \asmref{lemma0p}:
  \begin{passumptions}
    \asm{lemma2}{a=b}
  \end{passumptions}
  Finally, rewrite \goalref{lemma0} via \asmref{lemma1}.
  Then, \asmref{lemma2} solves it.
\end{proof}

\begin{proof}
        \setPassLabel{K}
        \begin{passumptions}
                \asm{lemma3}{c}
        \end{passumptions}
\end{proof}

\end{document}

在此处输入图片描述

解释

  1. 我认为最好将所有列表环境直接定义为\newlists,而不是将它们\newenvironments包装为列表passumptionslist。原因是这样您就可以一致地使用接口enumitem通过可选参数动态修改列表,也可以全局使用\setlist,从而增加了使用它们的灵活性。

  2. 据我所知,这\crefalias对您的代码没有影响;所以我删除了它们,特别是因为已知它可能存在问题。这样做的好处是,无论何时使用和,hyperref您都会使用可选参数,因此能够从可选参数中获取类型信息。\label\asm\goal\Cref

  3. 我碰巧认为使用\newlists\setlist使得格式的配置更加透明。

  4. 这是我们必须完成的唯一部分。在您的原始代码中,您定义了passumptions一个强制参数,用于设置label底层列表的。我不同意这种设置:首先,从您的代码中可以看出passumptions“每个证明”都使用,因此理想情况下,passumptions同一证明中的连续项应具有相同的标签类型。(我认为如果项目的编号为 H1、H2,然后是 C3、D4、H5,看起来会很奇怪。)考虑到这一点,最好有一种全局更改标签的方法(对于所有后续passumptions列表)。顺便说一句,这样做还可以将passumptions其编码为\newlist,因为以这种方式创建的列表不能采用强制参数。

    基于这种设计理念,我们创建了一个命令来设置 的标签符号/字符passumptions,并使用该符号来配置此列表的格式。我还添加了resume键,因为此列表旨在恢复,直到每次证明开始时重置。

相关内容