根据参数值创建命令

根据参数值创建命令

我正在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。您的宏存在问题:

  1. 您正在添加不需要的空格。请参阅行末百分号(%)有什么用?

  2. 如果你写\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 个参数,但它试图使用其第一个参数,因此参数编号非法。

  3. 您对字符特定的新命令的定义是使用 完成的,因此它是本地的,并且如果您在内部使用\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 字符)。

在此处输入图片描述

相关内容