活动字符和分隔参数

活动字符和分隔参数

假设我有一个宏\foo,它接受一个参数,在开始和结束时用 分隔。问题是,在调用if!时,我不知道是否由 french调用。\foo!\activebabel

目前,我使用以下方法。它可以工作,但我需要维护相同宏的两个版本,一个处理非活动状态!,另一个处理活动状态!

\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

相关内容