我想定义一个宏,通过指定以下参数来定义进一步的宏来显示元素的序列/列表/元组:
- 新命令的名称。
- 空序列、列表或元组的显示内容。
- 如何打开单例序列/列表/元组。
- 如何关闭单例序列/列表/元组。
- 如何打开包含多个元素的序列/列表/元组。
- 如何关闭具有多个元素的序列/列表/元组。
- 在序列/列表/元组中两个元素之间显示什么作为分隔对象。
我查看了以下问题和相应的答案:
- 何时使用 \edef、\noexpand 和 \expandafter?
- \csname 和 \endcsname 到底起什么作用?
- 在另一个新命令中定义一个带有变量名的新命令
- 变量名 \newcommand 带有另一个 \newcommand 中的参数
因此,我尝试编写命令,并得出以下两个版本(第二个版本在下面的代码中被注释掉了)。不幸的是,它们都不起作用,我不知道问题出在哪里:
\documentclass{article}
\makeatletter
\newcommand{\definelistcommand}[7]{%
\expandafter\newcommand\csname @start#1\endcsname{\expandafter\@ifnextchar\csname @stop#1\endcsname{#2\csname @end#1\endcsname}{\csname @first#1\endcsname}}%
\expandafter\newcommand\csname @first#1\endcsname[1]{\expandafter\@ifnextchar\csname @stop#1\endcsname{#3##1#4\csname @end#1\endcsname}{#5##1\csname @next#1\endcsname}}%
\expandafter\newcommand\csname @next#1\endcsname[1]{#7##1\expandafter\@ifnextchar\csname @stop#1\endcsname{#6\csname @end#1\endcsname}{\csname @next#1\endcsname}}%
\expandafter\newcommand\csname @end#1\endcsname[1]{}% consumes the \stop command
\expandafter\newcommand\csname #1\endcsname[1]{\csname @start#1\endcsname##1\csname @stop#1\endcsname}%
}
\makeatother
% \makeatletter
% \edef\definelistcommand#1#2#3#4#5#6#7{%
% \noexpand\newcommand\expandafter\noexpand\csname @start#1\endcsname{\noexpand\@ifnextchar\expandafter\noexpand\csname @stop#1\endcsname{#2\expandafter\noexpand\csname @end#1\endcsname}{\expandafter\noexpand\csname @first#1\endcsname}}%
% \noexpand\newcommand\expandafter\noexpand\csname @first#1\endcsname[1]{\noexpand\@ifnextchar\expandafter\noexpand\csname @stop#1\endcsname{#3##1#4\expandafter\noexpand\csname @end#1\endcsname}{#5##1\expandafter\noexpand\csname @next#1\endcsname}}%
% \noexpand\newcommand\expandafter\noexpand\csname @next#1\endcsname[1]{#7##1\noexpand\@ifnextchar\expandafter\noexpand\csname @stop#1\endcsname{#6\expandafter\noexpand\csname @end#1\endcsname}{\expandafter\noexpand\csname @next#1\endcsname}}%
% \noexpand\newcommand\expandafter\noexpand\csname @end#1\endcsname[1]{}% consumes the \stop command
% \noexpand\newcommand\expandafter\noexpand\csname #1\endcsname[1]{\expandafter\noexpand\csname @start#1\endcsname##1\expandafter\noexpand\csname @stop#1\endcsname}%
% }
% \makeatother
\definelistcommand{mylist}{empty}{[}{]}{(}{)}{,}
\begin{document}
\mylist{{a}{b}{c}}
\mylist{{a}{b}}
\mylist{a}
\mylist{{b}}
\mylist{}
\end{document}
期望的输出是:
(a,b,c)
(a,b)
[a]
[b]
empty
对于第一个版本,我收到此错误消息:
! Missing \endcsname inserted.
<to be read again>
\let
l.27 \mylist{{a}{b}{c}}
对于第二个版本,我得到了这个:
! Undefined control sequence.
l.27 \mylist
{{a}{b}{c}}
我的命令中存在哪些错误以及正确的解决方案如何起作用?
编辑(向未来的读者指出,接受的答案并不是唯一有趣的答案):虽然我接受了 Christian Hupfer 的答案(因为它包含对我的尝试的解释和修复),但其他答案也包含有价值的贡献。特别是,我将使用 egreg 的解决方案,因为它允许使用键值对指定我想要的参数。不幸的是,我不能接受两个答案。
答案1
这是一个expl3
带有变量的版本seq
。
该命令\NewListCommand
声明了与第一个参数同名的命令列表宏,并使用了seq
带有某些前缀的全局变量,参见\g_cryingshadow_mynewlist_seq
等等。
这是由\mynewlist
命令填充的,并且根据从#3到#6的其他参数的内容显示内容。第7个参数用作分隔符
一些注意事项:该empty
参数可以是可选的。
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\NewListCommand}{mmmmmmm}{%
\seq_new:c {g_cryingshadow_#1_seq} % Make a new sequence
\expandafter\NewDocumentCommand\csname #1\endcsname{m}{%
\seq_gclear:c {g_cryingshadow_#1_seq}% Clear the sequence
\seq_gclear:N \l_tmpa_seq % Clear a temporary `\l_tmpa_seq
\seq_set_split:Nnn \l_tmpa_seq {} {##1} % Split the sequence into tokens.
\seq_set_eq:cN {g_cryingshadow_#1_seq} \l_tmpa_seq % copy the temporary sequence to the real one → this would not be necessary if the sequence is not be used outside
\seq_if_empty:cTF {g_cryingshadow_#1_seq} {%
#2% Checked --> is empty
}{%
% Display the various styles
\int_case:nn { \seq_count:c {g_cryingshadow_#1_seq} }
{%
{1} {#3 \seq_use:cnnn {g_cryingshadow_#1_seq} {,} {#7} {#7} #4}
}%
\int_compare:nNnT { \seq_count:c {g_cryingshadow_#1_seq}} > 1 {%
#5 \seq_use:cnnn {g_cryingshadow_#1_seq} {#7} {#7} {#7} #6
}
}
}
}
\ExplSyntaxOff
\NewListCommand{mynewlist}{empty}{[}{]}{(}{)}{,}
\begin{document}
\mynewlist{{A}{B}{C}}
\mynewlist{{A}{B}}
\mynewlist{{a}}
\mynewlist{}
\mynewlist{{A}{B}{C}{D}{E}{F}}
Astronomer's alphabet:
\mynewlist{{Oh}{be}{a}{fine}{girl}{kiss}{me}}
\end{document}
编辑:这是通常的 LaTeX 的工作版本:
\documentclass{article}
\usepackage{etoolbox}
\makeatletter
\def\definelistcommand#1#2#3#4#5#6#7{%
\expandafter\def\csname @firstinlist#1\endcsname##1{%
\@ifnextchar\@stoplist{%
#3##1#4\@endlist%
}{%
#5##1\csname @nextlist#1\endcsname%
}%
}
\expandafter\def\csname @nextlist#1\endcsname##1{%
\@ifnextchar\@stoplist{%
#7##1#6\@endlist%
}{%
#7##1\csname @nextlist#1\endcsname%
}%
}
\expandafter\def\csname @startlist#1\endcsname##1{%
\@ifnextchar\@stoplist{%
#3##1#4\@endlist%
}{%
\csname @firstinlist#1\endcsname##1%
}%
}
\expandafter\def\csname #1\endcsname##1{%
\ifblank{##1}{%
#2%
}{%
\csname @startlist#1\endcsname##1\@stoplist%
}%
}
}
\def\@endlist#1{}
\definelistcommand{mycmd}{empty}{[}{]}{(}{)}{,}
\definelistcommand{othercmd}{empty}{\textbraceleft}{\textbraceright}{(}{)}{+}
\begin{document}
\makeatother
\mycmd{{A}{B}{C}}
\mycmd{A}
\mycmd{{A}{B}}
\mycmd{}
\othercmd{{O}{B}}
\othercmd{{O}}
\end{document}
答案2
你可以尝试这个宏:
\newcount\tmpnum
\def\definelistcommand#1#2#3#4#5#6#7{%
\expandafter\def\csname\string#1:list\endcsname##1{%
\ifcase##1 #2\or#3\or#4\or#5\or#6\or#7\fi}%
\def#1{\dolist#1}%
}
\def\dolist#1#2{\tmpnum=0 \def\tmp{}%
\def\listcommand{\csname\string#1:list\endcsname}%
\dolistA#2\end}
\def\dolistA#1{%
\ifx\end#1%
\ifnum\tmpnum=0 \listcommand0%
\else\ifnum\tmpnum=1 \listcommand1\tmp\listcommand2%
\else \listcommand3\tmp\listcommand4%
\fi\fi
\else\edef\tmp{\ifx\tmp\empty\else\tmp\listcommand5\fi#1}%
\advance\tmpnum by1
\expandafter\dolistA\fi
}
\definelistcommand \mylist {empty} [](),
\mylist{{a}{b}{c}}
\mylist{{a}{b}}
\mylist{a}
\mylist{{b}}
\mylist{}
解释。\definelistcommand\foo
定义了两个控制序列。第一个\\foo:list
为\ifcase
。它扩展了适当的声明参数:\\foo:list0
扩展到nothing
、\\foo:list1
等[
。第二个定义的控制序列是\foo
。这是宏\dolist\foo
。
的用法\foo{text}
扩展为\dolist\foo{text}
。此宏重置\tmp
为空,\tmpnum
为零并保存\listcommand
为\\foo:list
。然后它运行\dolistA text\end
。
重复读取\dolistA
文本中的项。它\tmp
使用 将这些项保存到宏中\edef\tmp
。如果不为空,则在前一个内容和下一个项之间添加\tmp
逗号(别名) 。计算项数。当达到 时,根据值打印。\listcommand5
#1
\tmpnum
\end
\tmp
\tmpnum
请注意,我们不需要任何外部包,我们可以非常简单地声明类似列表的宏。
答案3
我认为,与使用一长串参数相比,使用键值语法更好。
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\definelistcommand}{mm}
{% #1 is the command name, #2 is the key-value setup
% clear the temporary property list
\prop_clear:N \l_cryingshadow_list_temp_prop
\keys_set:nn { cryingshadow/list } { #2 }
% check if left-single or right-single have been set
\prop_if_in:NnF \l_cryingshadow_list_temp_prop { left-single }
{
\prop_put:Nnx \l_cryingshadow_list_temp_prop { left-single }
{ \prop_item:Nn \l_cryingshadow_list_temp_prop { left } }
}
\prop_if_in:NnF \l_cryingshadow_list_temp_prop { right-single }
{
\prop_put:Nnx \l_cryingshadow_list_temp_prop { right-single }
{ \prop_item:Nn \l_cryingshadow_list_temp_prop { right } }
}
% allocate a specific property list and make it equal to the temporary one
\prop_new:c { g_cryingshadow_list_#1_prop }
\prop_gset_eq:cN { g_cryingshadow_list_#1_prop } \l_cryingshadow_list_temp_prop
% define the runtime macro to call the generic one with the current name as argument
\cs_new_protected:cpn { #1 } ##1 { \cryingshadow_list_print:nn { #1 } { ##1 } }
}
% allocate some variables
\prop_new:N \l_cryingshadow_list_temp_prop
\seq_new:N \l_cryingshadow_list_input_seq
% syntactic sugar
\cs_new_protected:Nn \__cryingshadow_list_put:nn
{
\prop_put:Nnn \l_cryingshadow_list_temp_prop { #1 } { #2 }
}
\cs_new_protected:Nn \__cryingshadow_list_item:nn
{
\prop_item:cn { g_cryingshadow_list_#1_prop } { #2 }
}
% define the keys
\keys_define:nn { cryingshadow/list }
{
empty .code:n = \__cryingshadow_list_put:nn { empty } { #1 },
left-single .code:n = \__cryingshadow_list_put:nn { left-single } { #1 },
right-single .code:n = \__cryingshadow_list_put:nn { right-single } { #1 },
left .code:n = \__cryingshadow_list_put:nn { left } { #1 },
right .code:n = \__cryingshadow_list_put:nn { right } { #1 },
delimiter .code:n = \__cryingshadow_list_put:nn { delimiter } { #1 },
}
\cs_new_protected:Nn \cryingshadow_list_print:nn
{
% split the input into items at commas
\seq_set_split:Nnn \l_cryingshadow_list_input_seq { , } { #2 }
% branch between empty or single and multiple items
\int_compare:nTF { \seq_count:N \l_cryingshadow_list_input_seq = 1 }
{
% if the sequence has just one item it could be empty
\tl_if_blank:nTF { #2 }
{ \__cryingshadow_list_item:nn { #1 } { empty } }
{
\__cryingshadow_list_item:nn { #1 } { left-single }
#2
\__cryingshadow_list_item:nn { #1 } { right-single }
}
}
{
% otherwise it has multiple items
\__cryingshadow_list_item:nn { #1 } { left }
\seq_use:Nn \l_cryingshadow_list_input_seq { \__cryingshadow_list_item:nn { #1 } { delimiter } }
\__cryingshadow_list_item:nn { #1 } { right }
}
}
\ExplSyntaxOff
\definelistcommand{firstlist}{
empty=empty,
left-single=[,
right-single=],
left=(,
right=),
delimiter={, },
}
\definelistcommand{secondlist}{
empty=nothing,
left=\{,
right=\},
delimiter={--},
}
\begin{document}
\firstlist{A,B,C}
\firstlist{A,B}
\firstlist{a}
\firstlist{}
\secondlist{A,B,C}
\secondlist{A,B}
\secondlist{a}
\secondlist{}
\end{document}
这是如何运作的?
第\definelistcommand
一个设置了一个特定于要定义的列表命令的属性列表,其中存储了各个项目(外部分隔符、内部分隔符、空情况的处理方式);然后它定义宏来调用通用函数来产生输出。
通过检查第二个参数为属性列表提供值。如果未指定left-single
或,则提供或的right-single
值。left
right
在调用时,通用宏从特定属性列表中获取值,用逗号分隔参数,然后分支:如果参数为空,则打印案例的值;如果只有一项,则使用left-single
和right-single
外部分隔符;否则使用left
和right
,并在项之间使用内部分隔符。