我正在尝试创建一个命令\placeDate
,在第一次调用时打印一些内容,然后不打印任何内容,如果再次调用它则会产生错误。
这个想法是像往常一样打印宏,最后调用另一个宏(\inhibit
)来重新定义宏并生成一个可以适用于任何宏的错误。在错误消息中,我希望看到我们多次调用的宏的名称(这里是 placeDate)。
因此,我必须更新我必须作为参数发送\placeDate
到的命令\inhibit
。当生成错误时,我需要删除反斜杠以写入描述消息。
\removebs
是该命令。重点是我想在更新之前保存字符串,以便我们可以在消息中调用该字符串。
\documentclass[a4paper,12pt]{report}
\usepackage[english]{babel}
\usepackage[utf8x]{inputenc}
\usepackage[T1]{fontenc}
\newcommand{\removebs}[1]{%
{\catcode92=9 \endlinechar-1 \scantokens{#1}}%
}
\newcommand{\inhibit}[2]
{
\newcommand{\obj}{\removebs{#1}}
\renewcommand#1[#2]
{
\GenericError{}{Error: multiple \obj instantiation}{Only one \obj possible.}{}
}
}
\newcommand{\placeDate}[1]
{
\noindent #1, \today.\\\\
\inhibit{\placeDate}{1}
}
\begin{document}
\placeDate{City} %ok
\placeDate{City} %generates error
\end{document}
显然这里不起作用,因为当\obj
被调用时,\removebs{#1}
会变成\removebs{\placeDate}
,然后我怀疑\placeDate
在处理之前就被扩展了\removebs
。所以我希望得到一些帮助来解决这个问题。
以下是我们在 Overleaf 中获得的结果:
先感谢您。
编辑:事实上,我的代码中有一些类似的宏,我需要以同样的方式抑制它们。因此,这些宏中的每一个都会通过调用通用抑制宏来抑制自身。
例如:\macroA
预期结果:
错误:多个宏A 实例。 仅可能有一个宏A。
答案1
如果你希望错误消息打印不带反斜杠的宏名称,你可以这样做
\documentclass[a4paper,12pt]{report}
\usepackage[english]{babel}
%\usepackage[utf8x]{inputenc} % don't use utf8x
\usepackage[T1]{fontenc}
\makeatletter
\newcommand{\inhibit}[2]{%
\renewcommand{#1}[#2]{%
\GenericError{}{Oops!}{%
Error: multiple \expandafter\@gobble\string#1\space instantiation%
}{Only one \expandafter\@gobble\string#1\space possible.}%
}%
\global\let#1#1% in case the macro appears in a group
}
\makeatother
\newcommand{\placeDate}[1]{%
\noindent #1, \today.\par
\inhibit{\placeDate}{1}%
}
\begin{document}
\placeDate{City} %ok
\placeDate{City} %generates error
\end{document}
控制台输出:
! Oops!.
Error: multiple placeDate instantiation
Type H <return> for immediate help.
...
l.25 \placeDate{City}
%generates error
? h
Only one placeDate possible.
但我只会用\string#1
\documentclass[a4paper,12pt]{report}
\usepackage[english]{babel}
%\usepackage[utf8x]{inputenc} % don't use utf8x
\usepackage[T1]{fontenc}
\newcommand{\inhibit}[2]{%
\renewcommand{#1}[#2]{%
\GenericError{}{Oops!}{%
Error: multiple \string#1 instantiation%
}{Only one \string#1 possible.}%
}%
\global\let#1#1% in case the macro appears in a group
}
\newcommand{\placeDate}[1]{%
\noindent #1, \today.\par
\inhibit{\placeDate}{1}%
}
\begin{document}
\placeDate{City} %ok
\placeDate{City} %generates error
\end{document}
控制台输出
! Oops!.
Error: multiple \placeDate instantiation
Type H <return> for immediate help.
...
l.23 \placeDate{City}
%generates error
? h
Only one \placeDate possible.
您可能需要一个抽象层,这可以通过来完成expl3
。
\documentclass{article}
\ExplSyntaxOn
\NewDocumentCommand{\newonetimecommand}{mO{0}+m}
{% #1 = command name, #2 = number of parameters, #3 = replacement text
\vivier_otc_define:nnn { #1 } { #2 } { #3 }
}
\cs_new_protected:Nn \vivier_otc_define:nnn
{
% define the internal name
\cs_new_protected:cn { vivier_otc_\cs_to_str:N #1 : \prg_replicate:nn { #2 } { n } }
{
% the first time use the given replacement text
#3
% and globally redefine to issue an error and gobble the arguments
\cs_gset_protected:Npn #1
{
% issue the error
\msg_error:nnx { vivier } { multiple-instance } { \token_to_str:N #1 }
% gobble the arguments
\use:c { use_none: \prg_replicate:nn { #2 } { n } }
}
}
% define the external command
\cs_gset_eq:Nc #1 { vivier_otc_\cs_to_str:N #1 : \prg_replicate:nn { #2 } { n } }
}
\msg_new:nnnn { vivier } { multiple-instance }
{
multiple~#1~instantiation
}
{
Only~one~#1~possible
}
\ExplSyntaxOff
\newonetimecommand{\placeDate}[1]{%
\par\noindent #1, \today.\par
}
\begin{document}
\placeDate{City} %ok
\placeDate{City} %generates error
\end{document}
控制台输出:
! Package vivier Error: multiple \placeDate instantiation
For immediate help type H <return>.
...
l.47 \placeDate
{City} %generates error
? h
Only one \placeDate possible
答案2
根据您的用例描述,我认为您可以使用更简单的代码,而无需担心 catcode 和 scantokens 的复杂性。
\documentclass[a4paper,12pt]{report}
\showhyphens{entdimensionalisiert}
\usepackage[english]{babel}
% avoid utf8x unless needed for compatbility with old documents \usepackage[utf8x]{inputenc}
\usepackage[T1]{fontenc}
\newcommand{\placeDate}[1]
{% dont generate spurious space
\noindent #1, \today.\par % don't use consecutive \\
\gdef\placeDate{\inhibit\placeDate}%
}
\newcommand\inhibit[1]{%
\GenericError{}{Error: multiple \string#1 instantiation}%
{Only one \string#1 possible.}{}}
\begin{document}
\placeDate{City} %ok
\placeDate{City} %generates error
\end{document}
答案3
一些问题:
到目前为止提出的所有解决方案都依赖于相关命令在第一次使用之前不被重命名/不被复制。
例如,如果您在第一次
\let\bar=\foo
执行之前这样做,那么执行多次将触发所需的错误消息,但执行(几次)永远不会触发所需的错误消息,因为不会被重新定义为传递错误消息但会被重新定义为传递错误消息。\foo
\foo
\bar
\bar
\foo
用于重新定义任何其他命令的通用命令,如果第二次使用则提供错误消息,需要查找
- 要重新定义的命令处理的参数/参数。
- 重新定义命令背后的宏观机制。
(宏机制:例如,根据
\newcommand
处理可选参数定义的命令或根据定义的命令或根据\DeclareRobustCommand
定义的\NewDocumentCommand
命令实际上是由调用内部宏的用户级宏组成的宏机制。如果您希望重新定义这样的命令,则可能需要重新定义用户级宏和内部宏。找出底层宏机制的类型,即找出属于宏机制的宏可能并不是一件容易的事。)
为了避免这些问题,我建议实现标志来表示命令是否已被使用。
\NewInhibitflag{⟨control sequence token⟩}
定义一个命令,其名称是通过在名称前面添加⟨控制序列标记⟩序列MyPrefix@ReservedForDefinedFlags@
,并将该序列附加到@flag@WeirdPostfix
与字符标记相等的位置0
。
\CheckInhibitflag{⟨control sequence token⟩}%
{⟨tokens if control sequence token is used the 1st time⟩}
检查命令的名称是否通过在命令名称前面添加⟨控制序列标记⟩序列MyPrefix@ReservedForDefinedFlags@
并附加该序列@flag@WeirdPostfix
是否等于字符标记1
。
如果不是,⟨控制序列标记⟩尚未使用,因此该命令被定义为等于字符标记1
和⟨如果第一次使用控制序列 token,则为 token⟩已送达。
如果是,⟨控制序列标记⟩已被使用并发出错误消息。
\CheckInhibitflag
可以用于任何不需要扩展的命令的定义文本中 — — 无论它处理什么参数/参数,也无论它是根据\newcommand
或\DeclareRobustCommand
或定义的\NewDocumentCommand
。
例如,对于处理参数但不产生可见输出的宏,您可以\CheckInhibitflag{...}{...}
在 之间换行\@bsphack...\@esphack
。
\makeatletter
% \NewInhibitflag{<control sequence token>}%
\DeclareRobustCommand\NewInhibitflag[1]{%
\begingroup
\escapechar=-1
\@ifundefined{MyPrefix@ReservedForDefinedFlags@\string#1@flag@WeirdPostfix}%
{\global\expandafter\let\csname MyPrefix@ReservedForDefinedFlags@\string#1@flag@WeirdPostfix\endcsname=0}%
{%
\GenericError{}{Control sequence underlying flag for \@backslashchar\string#1 already defined}%
{Control sequence underlying flag for \@backslashchar\string#1 already defined.}{}%
}%
\endgroup
}
% \CheckInhibitflag{<control sequence token>}%
% {<tokens if control sequence token is used the 1st time>}
\DeclareRobustCommand\CheckInhibitflag[1]{%
\begingroup
\escapechar=-1
\@ifundefined{MyPrefix@ReservedForDefinedFlags@\string#1@flag@WeirdPostfix}{%
\GenericError{}{Control sequence underlying flag for \@backslashchar\string#1 undefined}%
{Use \@backslashchar\string\NewInhibitflag{\@backslashchar\string#1} for defining it.}{}%
\endgroup\@firstofone
% the argument of \@firstofone is
% {<tokens if control sequence token is used the 1st time>}.
% This is provided by the user with the call to \CheckInhibitflag
}{%
\expandafter\ifx\csname MyPrefix@ReservedForDefinedFlags@\string#1@flag@WeirdPostfix\endcsname1%
\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
{\@firstoftwo}%
{\global\expandafter\let\csname MyPrefix@ReservedForDefinedFlags@\string#1@flag@WeirdPostfix\endcsname=1\endgroup\@secondoftwo}%
{%
\GenericError{}{Error: multiple \@backslashchar\string#1 instantiation}{Only one \@backslashchar\string#1 possible.}{}%
\endgroup
}%
% the 2nd argument of \@firstoftwo/\@secondoftwo is
% {<tokens if control sequence token is used the 1st time>}.
% This is provided by the user with the call to \CheckInhibitflag
}%
}%
\makeatother
\documentclass{report}
\NewInhibitflag{\placeDate}%
\newcommand\placeDate[1]{%
\CheckInhibitflag{\placeDate}{%
#1, \today.\par
}%
}%
\begin{document}
\noindent\placeDate{City}%ok
\noindent Text
%\noindent\placeDate{Town}%generates error
\noindent Text
\end{document}
不要使用在移动参数中只允许一次性使用的命令,例如分段或\caption
命令的参数,这些参数也可能出现在目录、页眉/页脚或 pdf 书签中。