TeXBook 第 40 页有以下文字(重点添加)
相反,你可以从字符标记列表转换为控制序列,方法是说。出现在和
\csname⟨tokens⟩\endcsname
之间的此构造中的标记可能包含其他控制序列,只要这些控制序列最终扩展为字符而不是 TEX 基元;最终的字符可以是任何类别,不一定是字母。例如,本质上与 相同;但是是非法的,因为扩展为包含\csname
\endcsname
\csname TeX\endcsname
\TeX
\csname\TeX\endcsname
\TeX
\kern
。此外,\csname\string\TeX\endcsname
将产生不寻常的控制序列\\TeX
,即标记 [\TeX
],这是通常无法编写的。
不幸的是,它没有说明如果要删除初始值\
并最终得到控制序列,您可以对最后一点做什么\TeX
。这个问题是关于如何做到这一点的。
我为什么想要这个?
我想创建一个“工厂”命令,可以动态生成几个新命令。以下有效:
\documentclass{article}
\NewDocumentCommand{\myfactory}{m m}{
\NewDocumentCommand{#1}{m}{A command has been created with argument ``#2'' and called with argument ``##1''}
}
\myfactory{\mycommand}{Hello}
\begin{document}
\mycommand{World!}
\end{document}
它给出了预期以下输出:
但是,如果我更改工厂以制作该命令的“特殊”版本,那么它会失败
\documentclass{article}
\NewDocumentCommand{\myfactory}{m m}{
\NewDocumentCommand{#1}{m}{A command has been created with argument ``#2'' and called with argument ``##1''}
\NewDocumentCommand{#1special}{m}{A `special' command has been created with argument ``#2'' and called with argument ``##1''}
}
\myfactory{\mycommand}{Hello}
\begin{document}
\mycommand{World!}
\mycommandspecial{Special world!}
\end{document}
错误信息如下,看起来很合理
./newcommand name mangling.tex:8: LaTeX cmd Error: First argument of '\NewDocum
entCommand' must be a command.
因此我可以尝试使用\csname
和生成命令名称\endcsname
,但由于上述原因,以下操作失败:
\documentclass{article}
\NewDocumentCommand{\myfactory}{m m}{
\NewDocumentCommand{#1}{m}{A command has been created with argument ``#2'' and called with argument ``##1''}
\expandafter\NewDocumentCommand\csname #1special\endcsname{m}{A `special' command has been created with argument ``#2'' and called with argument ``##1''}
}
\myfactory{\mycommand}{Hello}
\begin{document}
\mycommand{World!}
\mycommandspecial{Special world!}
\end{document}
这里我们得到错误
./newcommand name mangling.tex:13: Undefined control sequence.
l.13 \mycommandspecial
{Special world!}
我猜是因为它实际上定义了一个名为的命令\\mycommandspecial
,而该命令没有正常的调用方式。
另一方面,以下做工作,但它有点烦人,因为用户必须像\myfactory{mycommand}{Hello}
而不是 那样来调用它\myfactory{\mycommand}{Hello}
,而我更喜欢后者。
\documentclass{article}
\NewDocumentCommand{\myfactory}{m m}{
\expandafter\NewDocumentCommand\csname #1\endcsname{m}{A command has been created with argument ``#2'' and called with argument ``##1''}
\expandafter\NewDocumentCommand\csname #1special\endcsname{m}{A `special' command has been created with argument ``#2'' and called with argument ``##1''}
}
\myfactory{mycommand}{Hello}
\begin{document}
\mycommand{World!}
\mycommandspecial{Special world!}
\end{document}
答案1
定义您的\csstring
命令。它已包含在 LuaTeX 中;如果未定义,它将使用\cs_to_str:N
from expl3
。
\documentclass{article}
\ExplSyntaxOn
\ProvideExpandableDocumentCommand{\csstring}{m}{\cs_to_str:N #1}
\ExplSyntaxOff
\NewDocumentCommand{\myfactory}{m m}{%
\NewDocumentCommand{#1}{m}{%
A command has been created with argument ``#2'' and called with argument ``##1''%
}%
\ExpandArgs{c}\NewDocumentCommand{\csstring #1special}{m}{%
A `special' command has been created with argument ``#2'' and called with argument ``##1''%
}%
}
\myfactory{\mycommand}{Hello}
\begin{document}
\mycommand{World!}
\mycommandspecial{Special world!}
\end{document}
对于您需要的“特殊”命令\ExpandArgs{c}
,它将跳过以下标记并从后面的括号组构建命令名称。该构造相当于
\expandafter\NewDocumentCommand\csname \csstring#1special\endcsname{m}{...}
但更具可读性。
不要忘记保护端线。
答案2
如果您使用的是 LuaTeX,则可以使用\csstring
原语代替\string
,这样可以省去初始的反斜杠。
使用其他引擎,您可以轻松定义\csstring
自己:
\def\gobble#1{}
\def\csstring{\expandafter\gobble\string}
这是有效的,因为\string
它扩展为一系列 12 类标记(即可打印的非字母字符),您可以在扩展后丢弃第一个标记。
编辑:如下所述,如果\escapechar
设置为-1
或 ,此宏将不起作用32
。为了完整起见,下面的内容在这些情况下也将起作用:
\def\gobble#1{} \def\gobbletwo#1#2{}
\def\csstring{%
\ifnum-1=\escapechar \expandafter\gobbletwo \else
\ifnum32=\escapechar \expandafter\expandafter
\expandafter\gobbletwo\fi\fi
\expandafter\gobble\string}
设置提供了解决问题的另一种方法,例如在 plain.tex 的宏\escapechar=-1
中使用。\newif