![根据参数值创建命令](https://linux22.com/image/413769/%E6%A0%B9%E6%8D%AE%E5%8F%82%E6%95%B0%E5%80%BC%E5%88%9B%E5%BB%BA%E5%91%BD%E4%BB%A4.png)
我正在sides
写一部剧本。要添加角色,请执行以下操作:
\begin{castpage}
\cast{Bob SquarePants}{A sponge}
\end{castpage}
然后你就
\repl{Bob SquarePants} Plop!
\stage{\chara{Bob} do a flip.}
\chara
将字符串小写化的快捷方式
我想要做的是重写\cast
它以执行其功能,并使用传入参数的值声明新命令/别名。如下所示:使用类似以下代码\cast{Bob SquarePants}{A sponge}{bob}
创建的新命令。\bob
\bob{\chara Bob}
目标是\stage{\chara{Bob} do a flip.}
用\stage{\bob do a flip.}
我尝试过:
\let\oldCast\cast
\renewcommand{\cast}[3]{
\oldCast{#1}{#2}
\expandafter\newcommand\csname #3\endcsname{\chara ##1}
}
Illegal parameter number in definition of \bob
但是当我写的时候我得到了错误\cast{Bob SquarePants}{A sponge}{bob}
编辑:
这是一个 MWE(评论了我想要实现的目标):
\documentclass{sides}
\usepackage[T1]{fontenc}
\usepackage{libertine}
\def \stage{\stagedir}
\title{MWE}
\author{Foo}
\begin{document}
\maketitle
\newcommand*{\newcast}[3]{%
\cast{#1}{#2}%
\expandafter\newcommand\csname #3\endcsname{\chara{#1}}%
}
\begin{castpage}
\newcast{Bob SquarePants}{A sponge}{bob}
\newcast{Patrick Star}{A starfish}{pat}
\end{castpage}
\newact
\newscene
\stage{Opens on a black stage.}
\repl{Bob SquarePants} I will do a flip!
\stage{\chara{Bob} do a flip.}%\stage{\bob do a flip}
\repl{Patrick Star} Amazing!
\stage{\chara{Bob} smiles and \chara{Patrick} applauds.} %\stage{\bob smiles and \pat applauds.}
\end{document}
该命令的目的是节省一些输入,而不是为每个新字符添加相同的代码。
答案1
欢迎使用 TeX.SE。您的宏存在问题:
您正在添加不需要的空格。请参阅行末百分号(%)有什么用?
如果你写
\cast{Bob SquarePants}{A sponge}{bob}
,当\cast
展开时,整体将被替换为:\oldCast{Bob SquarePants}{A sponge} \expandafter\newcommand\csname bob\endcsname{\chara #1}
你看,参数标记已被它们的值替换,并且 已
##
被单个 替换#
。问题是,在\expandafter
展开后,最后一行将变成:\newcommand\bob{\chara #1}
换句话说,它定义了一个不带参数但包含
#1
替换文本的宏。这解释了您收到的错误:“定义中的参数编号非法\bob
。”\bob
需要 0 个参数,但它试图使用其第一个参数,因此参数编号非法。您对字符特定的新命令的定义是使用 完成的,因此它是本地的,并且如果您在内部使用
\newcommand
它将被遗忘(环境定义一个 TeX 组)。\end{castpage}
\cast
\begin{castpage} ... \end{castpage}
要修复第 2 点,您需要使用单个#
并在 周围添加括号#1
,以便在 的一个扩展步骤之后\cast{Bob SquarePants}{A sponge}{bob}
,可以得到:
\oldCast{Bob SquarePants}{A sponge}%
\expandafter\newcommand\csname bob\endcsname{\chara{bob}}%
因此,你的定义可能是:
\let\oldcast\cast
\renewcommand*{\cast}[3]{%
\oldCast{#1}{#2}%
\expandafter\newcommand\csname #3\endcsname{\chara{#1}}%
}
请注意,在某些情况下(最明显的是使用 和 定义的命令带有\newcommand
可选参数,或使用 定义的命令\DeclareRobustCommand
),\let\oldcast\cast
不会按您想要的方式工作。在这种情况下,该letltxmacro
包非常有用。
此外,由于您正在更改的语法和语义\cast
,因此以不同的方式命名您的函数可能更明智 - 例如\newcast
:
\newcommand*{\newcast}[3]{%
\cast{#1}{#2}%
\expandafter\newcommand\csname #3\endcsname{\chara{#1}}%
}
现在来谈谈问题 3。我们需要在内部执行动态命令定义\newcast
全球的。这可以通过 来完成\gdef
。为了超级干净,我们还可以另外使用\newcommand
来确保命令(例如\bob
)不存在。事实上,\gdef
会在没有通知的情况下覆盖现有命令。因此,我们可以执行以下操作:
\newcommand*{\newcast}[3]{%
\cast{#1}{#2}%
% Print an error if the command named after #3 is already defined
\expandafter\newcommand\csname #3\endcsname{}%
\expandafter\gdef\csname #3\endcsname{\chara{#1}\ }% there is a control space
}
我在后面添加了一个控制空格,\chara{#1}
因为当你写例如 时\bob do a flip.
,从 TeX 的角度来看, 后面没有空格标记:输入流中后面的空格字符会在变成控制序列标记\bob
后被吃掉(这是在 TeX 处理阶段称为\bob
标记化)。
最后,也可以使用包\csgdef
来完成相同的操作etoolbox
(这是语法糖并且行为相同):
\csgdef{#3}{\chara{#1}\ }
完整代码:
\documentclass{sides}
\usepackage{etoolbox}
\newcommand*{\stage}{\stagedir}
\title{MWE}
\author{Foo}
\begin{document}
\maketitle
\newcommand*{\newcast}[3]{%
\cast{#1}{#2}%
% Print an error if the command named after #3 is already defined
\expandafter\newcommand\csname #3\endcsname{}%
\csgdef{#3}{\chara{#1}\ }% there is a control space before the brace
}
\begin{castpage}
\newcast{Bob SquarePants}{A sponge}{bob}
\newcast{Patrick Star}{A starfish}{pat}
\end{castpage}
\newact
\newscene
\show\bob
\stage{Opens on a black stage.}
\repl{Bob SquarePants} I will do a flip!
\stage{\bob do a flip.}
\repl{Patrick Star} Amazing!
\stage{\bob smiles and \chara{Patrick} applauds.}
\end{document}
答案2
命令\cast
需要一参数,并且对第一个参数不做任何操作,只是将其排版为章节标题。似乎第二个参数只是“部分”的文本。
如果你输入
\begin{castpage}
\cast{Bob SquarePants} A sponge
\end{castpage}
没有任何区别。
该命令\chara
只是将其参数以小写字母形式排版。无论您是否使用过\cast{Patrick Star}{A starfish}
,输入\chara{Patrick}
都会按照定义完成其工作。
\chara
的论点和 中出现的任何内容之间没有任何联系castpage
。
您只需\newcommand{\bob}{\chara{Bob}}
在文档序言中执行并使用
\stagedir{\bob\ do a flip}
这是避免代码重复的一种方法。使用键值语法输入字符数据。
\documentclass{sides}
\usepackage{libertine}
\usepackage{xparse,xspace}
\ExplSyntaxOn
\keys_define:nn { misterjack/cast }
{
shortname .tl_set:N = \l__misterjack_cast_shortname_tl,
fullname .tl_set:N = \l__misterjack_cast_fullname_tl,
char .code:n = \misterjack_cast_full:nV { #1 } \l__misterjack_cast_fullname_tl,
nick .code:n = \misterjack_cast_short:nV { #1 } \l__misterjack_cast_shortname_tl,
desc .tl_set:N = \l__misterjack_cast_desc_tl,
}
\cs_new_protected:Nn \misterjack_cast_full:nn
{
\cs_new:cpn { #1 } { \repl { #2 } \xspace }
}
\cs_generate_variant:Nn \misterjack_cast_full:nn { nV }
\cs_new_protected:Nn \misterjack_cast_short:nn
{
\cs_new:cpn { #1 } { \chara { #2 } \xspace }
}
\cs_generate_variant:Nn \misterjack_cast_short:nn { nV }
\NewDocumentCommand{\Cast}{m}
{
\group_begin:
\keys_set:nn { misterjack/cast } { #1 }
\cast{\l__misterjack_cast_fullname_tl} \l__misterjack_cast_desc_tl
\group_end:
}
\ExplSyntaxOff
\begin{document}
\begin{castpage}
\Cast{
fullname=Bob SquarePants,
desc=A sponge,
shortname=Bob,
char=Bob,
nick=bob,
}
\Cast{
fullname=Patrick Star,
desc=A starfish,
shortname=Patrick,
char=Pat,
nick=pat,
}
\Cast{
fullname=Mr.~Krab,
desc=A crab,
shortname=Mr.~Krab,
char=Krab,
nick=krab,
}
\end{castpage}
\Bob Plop!
\stagedir{\bob do a flip.}
\stagedir{\krab grumbles.}
\end{document}
缩写输入的字符串是任意的(但应该只包含 ASCII 字符)。