假设我有一个宏\foo
,它接受一个参数,在开始和结束时用 分隔。问题是,在调用if!
时,我不知道是否由 french调用。\foo
!
\active
babel
目前,我使用以下方法。它可以工作,但我需要维护相同宏的两个版本,一个处理非活动状态!
,另一个处理活动状态!
。
\documentclass{standalone}
\makeatletter
\begingroup
\catcode`\!=12\relax
\gdef\foo@nonactive!#1!{Non active: #1.}
\catcode`\!=\active\relax
\gdef\foo@active!#1!{Active: #1.}
\endgroup
\def\foo{%
\ifnum\the\catcode`\!=\active\relax
\expandafter\foo@active
\else
\expandafter\foo@nonactive
\fi}
\makeatother
\begin{document}
\catcode`\!=12\relax
\foo!bar!
\catcode`\!=\active\relax
\def !{This exclamation mark is active}
\foo!bar!!
\end{document}
问题
这种方法是否可靠,还有其他方法可以做到这一点吗(有和/或没有eTeX
)。
答案1
另一种方法是:
\documentclass{standalone}
\makeatletter
\def\foo#1{\def\foo@##1#1{[non]active ##1}\foo@}
\makeatother
\begin{document}
\catcode`\!=12\relax
\foo!bar!
\catcode`\!=\active\relax
\def !{This exclamation mark is active}
\foo!bar!!
\end{document}
正如 egreg 在评论中指出的那样,如果你尝试,这个定义是“脆弱的”
\tableofcontents
\section{ aaa \protect\foo\protect!ddd\protect! bbb }
由于在(写入文件)期间\def
内部\foo
无法发生任何事件,因此事情进展得非常糟糕。\write
.toc
如果你试试
\tableofcontents
\section{ aaa \protect\foo!ddd! bbb }
然后@egreg指出事情仍然会失败,因为\foo
不会扩大,但两者!
会扩展,因此不能用作分隔符。简写实际上可以定义为自我保护,因此在需要保护的环境中,它们应该扩展为自身(如\noexpand!
)。
\documentclass{article}
\makeatletter
\def\foo#1{\def\foo@##1#1{[non]active ##1}\foo@}
\makeatother
\begin{document}
\catcode`\!=12\relax
\foo!bar!
\catcode`\!=\active\relax
\def !{\ifx\protext\relax This exclamation mark is active\else\noexpand!\fi}
\foo!bar!!
\tableofcontents
\section{ aaa \protect\foo!ddd! bbb }
\end{document}
答案2
这是一个通用的解决方案(取自regexpatch.sty
):
\documentclass{article}
\usepackage[french]{babel}
\usepackage{expl3}
\ExplSyntaxOn
\seq_new:N \g_ddef_commands_seq
\tl_new:N \l_ddef_prefix_tl
\tl_new:N \l_ddef_arg_tl
\tl_new:N \l_ddef_replacement_tl
\cs_new_protected:Npn \delimited #1 \def #2
{
\seq_gput_right:Nn \g_ddef_commands_seq { #2 }
#1 \tex_def:D #2
}
\AtBeginDocument{
\seq_map_inline:Nn \g_ddef_commands_seq { \ddef_normalize:N #1 }
}
\cs_new_protected:Npn \ddef_normalize:N #1
{
\tl_set:Nf \l_ddef_prefix_tl { \token_get_prefix_spec:N #1 }
\tl_set_rescan:Nnx \l_ddef_prefix_tl { } \l_ddef_prefix_tl
\tl_set:Nf \l_ddef_arg_tl { \token_get_arg_spec:N #1 }
\tl_set_rescan:Nnx \l_ddef_arg_tl { } \l_ddef_arg_tl
\tl_set:Nf \l_ddef_replacement_tl { \token_get_replacement_spec:N #1 }
\tl_set_rescan:Nnx \l_ddef_replacement_tl { } \l_ddef_replacement_tl
\use:x
{
\exp_not:V \l_ddef_prefix_tl
\tex_def:D
\exp_not:N #1
\exp_not:V \l_ddef_arg_tl
{ \exp_not:V \l_ddef_replacement_tl }
}
}
\ExplSyntaxOff
\delimited\def\foo!#1!{Whatever with #1}
\delimited\long\def\bar+#1+{Another with #1}
\begin{document}
\foo!x!
\number\catcode`!
\bar+y+
\number\catcode`+
\show\bar
\end{document}
这将打印
任何与 x
13
另一个与 y
12
基本上,我们记住所有用 ; 前缀定义的命令\delimited\def
,例如\long
或\protected
必须位于这两个标记之间,但\protected
如果命令用于移动参数,则使用可能会引发一系列麻烦。然后在开始文档时,因此在babel
可能激活一些字符之后,命令将在当前类别代码下重建。
请注意,替换文本中的标记也可能会改变类别代码,但这不应该是个问题。
通过加载regexpatch
代码更简单:
\usepackage{regexpatch}
\ExplSyntaxOn
\seq_new:N \g_ddef_commands_seq
\cs_new_protected:Npn \delimited #1 \def #2
{
\seq_gput_right:Nn \g_ddef_commands_seq { #2 }
#1 \tex_def:D #2
}
\AtBeginDocument{
\seq_map_inline:Nn \g_ddef_commands_seq { \ddef_normalize:N #1 }
}
\cs_new_protected:Npn \ddef_normalize:N #1
{
\xpatch_get_all:N #1
\xpatch_rebuild:N #1
}
\ExplSyntaxOff
笔记
如果我们想确保代码在babel
激活角色后执行(但babel
应该在很早就加载的包中),可以说
\usepackage{etoolbox}
并使用\AfterEndPreamble
代替\AtBeginDocument
。