我如何迭代、循环、处理或以其他方式执行某物,每个元素都来自以逗号分隔的事物列表?
这个问题有 到过 问 也许 许多 许多 次,而且已经有很多非常好的答案。但是我认为,如果有一个单一的参考点,详细解释所有这些替代技术,并讨论它们的优点和缺点,那也是很有价值的。
欢迎所有 plain-TeX、LaTeX、LaTeX3、LuaTeX、Context、etoolbox、pgffor 等的回答。请考虑解释一下,例如:
- 命令/方法如何处理项目周围的空格?
- 命令/方法如何处理空的/丢失的项目?(例如
a,b,,c
) - 命令/方法如何处理尾随逗号?
- 存储在宏上的列表的命令/方法可以起作用吗?
- 每次迭代都是全局评估还是在本地组进行评估?(即,如果在迭代期间定义了命令,那么该命令是否在循环中继续存在?)
- 物品在被处理之前是否会被扩大或者以某种方式被损坏?
key=value
那么包含项目或其他类型的配对的列表怎么样?- 该方法是否适用于用逗号以外的其他符号分隔的列表?
- 在决定使用此方法之前我还应该了解其他什么事情?
答案1
LaTeX3 中逗号分隔列表的主循环是
\clist_map_inline:nn
第一个参数是一个显式列表,第二个参数告诉 LaTeX 如何处理每个项目。例如,我们想enumerate
从这些项目中打印一个环境:
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\makeenumerate}{ m }
{
\begin{enumerate}
\clist_map_inline:nn { #1 } { \item \fbox{##1} }
\end{enumerate}
}
\ExplSyntaxOff
\begin{document}
\makeenumerate{a, b ,c d, ,e }
\end{document}
我用来\fbox
说明一些特点:
项目两边的空格都被删除了;
空项将被忽略(空表示逗号之间只有空格);
该项目未进行任何扩展。
请注意,当前项目用 表示,#1
并且它的确实可用,这与通常的情况不同\@for
,其中当前项隐藏在宏中。在上面的代码中,我们必须使用 double,##1
因为我们在宏定义中。
类似的函数是\clist_map_function:nN
,它的优点是可以完全展开(但只能x
完全展开,不能f
)。上面的例子是
\NewDocumentCommand{\makeenumerate}{ m }
{
\begin{enumerate}
\clist_map_function:nN { #1 } \xyz_make_item:n
\end{enumerate}
}
\cs_new_protected:Npn \xyz_make_item:n #1
{
\item \fbox { #1 }
}
在这种情况下,当前项目作为参数传递给指示的函数。
列表映射可能会被破坏;比如,在上面的例子中,如果某个项目是以下情况,我们想停止处理\stop
:
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\makeenumerate}{ m }
{
\begin{enumerate}
\xyz_make_items:n { #1 }
\end{enumerate}
}
\cs_new_protected:Npn \xyz_make_items:n #1
{
\clist_map_inline:nn { #1 }
{
\tl_if_eq:nnTF { ##1 } { \stop }
{
\clist_map_break:
}
{
\item \fbox { ##1 }
}
}
}
\ExplSyntaxOff
\begin{document}
\makeenumerate{a, b ,c d, \stop ,e }
\end{document}
请注意,不应使用复杂的代码\NewDocumentCommand
,因此我为此定义了一个辅助函数。
也可以使用
\clist_map_break:n
并且在破坏映射之前将执行赋予此函数的参数。
使用时适用相同的功能
\keys_set:nn { <module> } { <comma list of key-value pairs> }
用于评估一组键值对:前导和尾随空格以及空(空白)项将被忽略。
如果逗号分隔列表存储在宏中,则可以使用
\clist_map_inline:Nn
\clist_map_function:NN
具有相同的想法。在我看来,允许同时输入和定义变量是一种糟糕的编程风格。
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\makeenumerate}{ sm }
{
\begin{enumerate}
\IfBooleanTF{#1}
{
\clist_set:NV \l_xyz_input_clist #2
\xyz_make_items:V \l_xyz_input_clist
}
{
\xyz_make_items:n { #2 }
}
\end{enumerate}
}
\clist_new:N \l_xyz_input_clist
\cs_new_protected:Npn \xyz_make_items:n #1
{
\clist_map_inline:nn { #1 }
{
\tl_if_eq:nnTF { ##1 } { \stop }
{
\clist_map_break:
}
{
\item \fbox { ##1 }
}
}
}
\cs_generate_variant:Nn \xyz_make_items:n { V }
\ExplSyntaxOff
\begin{document}
\newcommand{\mylist}{A, B ,C,}
\makeenumerate{a, b ,c d, \stop ,e }
\makeenumerate*{\mylist}
\end{document}
设置一个变量来保存保存逗号分隔列表的宏内容是因为这个过程“规范化”了逗号分隔列表,以便在中更好地使用\clist_map_inline:Nn
。
如果需要其他分隔符,更好的方法是转到序列;使用\seq_map_inline:Nn
或\seq_map_function:NN
将输入拆分为组件后
\seq_set_split:Nnn \l_xyz_input_seq { ; } { #1 }
完整示例:
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\makeenumerate}{ O{,} m }
{
\begin{enumerate}
\xyz_make_items:nn { #1 } { #2 }
\end{enumerate}
}
\seq_new:N \l_xyz_input_seq
\cs_new_protected:Npn \xyz_make_items:nn #1 #2
{
\seq_set_split:Nnn \l_xyz_input_seq { #1 } { #2 }
\seq_map_inline:Nn \l_xyz_input_seq
{
\tl_if_eq:nnTF { ##1 } { \stop }
{
\seq_map_break:
}
{
\item \fbox { ##1 }
}
}
}
\ExplSyntaxOff
\begin{document}
\makeenumerate[;]{a; b ;c, d; \stop ;e }
\end{document}
此处的拆分也会忽略前导和尾随空格以及空项。
答案2
该etoolbox
软件包提供了一些列表处理工具。对于逗号分隔的列表,有
\docsvlist{<list>}
其中,每个项目都用 放入输入流中。这意味着必须提供\do{<item>}
的适当定义。\do
\forcsvlist{<handler>}{<list>}
其中每个项目都作为参数放入输入流中,<handler>
只要其最后一个标记具有强制参数,该参数就可以是任意代码。
以下示例显示从列表中删除空项。对于每个项目领导删除空格,尾随空格不是!
\documentclass{article}
\usepackage{etoolbox}
\makeatletter
\newcommand\makeenumerate[1]{%
\begin{enumerate}
\forcsvlist{\makeenumerate@item}{#1}
\end{enumerate}
}
\newcommand\makeenumerate@item[1]{\item \fbox{#1}}
\makeatother
\begin{document}
\makeenumerate{a, b ,c d, ,e }
\end{document}
对于带有其他分隔符的列表,可以使用以下方法定义解析器
\DeclareListParser*{<macro>}{<separator>}
上面提到的两个解析器都是用它来定义的:
\DeclareListParser{\docsvlist}{,}
\DeclareListParser*{\forcsvlist}{,}
作为上述示例的变体的一个例子:
\documentclass{article}
\usepackage{etoolbox}
\makeatletter
\DeclareListParser*\formylist{;}
\newcommand\makeenumerate[1]{%
\begin{enumerate}
\formylist{\makeenumerate@item}{#1}
\end{enumerate}
}
\newcommand\makeenumerate@item[1]{\item \fbox{#1}}
\makeatother
\begin{document}
\makeenumerate{a; b ;c d; ;e }
\end{document}
对于列表处理不是直接处理用户输入etoolbox
提供了相当多不同的宏。它们都处理保存列表的宏。上述示例的下一个变体显示了\listadd{<list>}{<item>}
和,\forlistloop{<handler>}{<list>}
其中两次都<list>
表示保存列表的宏etoolbox
:
\documentclass{article}
\usepackage{etoolbox}
\makeatletter
\newcommand*\mylist{}
\newcommand\makeenumerate[1]{%
\forcsvlist{\listadd\mylist}{#1}%
\begin{enumerate}
\forlistloop{\makeenumerate@item}{\mylist}
\end{enumerate}
}
\newcommand\makeenumerate@item[1]{\item \fbox{#1}}
\makeatother
\begin{document}
\makeenumerate{a, b ,c d, ,e }
\end{document}
还有相当多的附加宏(例如\listeadd
用于添加扩展项或条件语句等\ifinlist
...),它们都已解释在手册中。
在所有 的etoolbox
列表中,解析器\listbreak
均可用于中断循环:
\documentclass{article}
\usepackage{etoolbox}
\makeatletter
\newcommand*\mylist{}
\newcommand\makeenumerate[1]{%
\forcsvlist{\listadd\mylist}{#1}%
\begin{enumerate}
\forlistloop{\makeenumerate@item}{\mylist}
\end{enumerate}
}
\newcommand\makeenumerate@item[1]{%
\ifstrequal{#1}{\stop}
{\listbreak}
{\item \fbox{#1}}%
}
\makeatother
\begin{document}
\makeenumerate{a, b ,c d, \stop ,e }
\end{document}
答案3
使用 egreg 模板直接使用 LaTeX
\documentclass{article}
\makeatletter
\newcommand{\makeenumerate}[2][,]{\begin{enumerate}%
\def\dolist##1{\expandafter\@dolist##1#1\@eol}%
\def\@dolist##1#1##2\@eol{%
\begingroup\setbox0=\hbox{##1\unskip}\ifdim\wd0=0pt\endgroup\else\endgroup\item \fbox{\ignorespaces##1\unskip}\fi%
\ifx\@eol##2\@eol\else\@dolist##2\@eol\fi}%
\dolist{#2}%
\end{enumerate}}
\makeatother
\begin{document}
\makeenumerate{a, b ,c d, ,e }
\makeenumerate[;]{a; b ;c d; ;e }
\end{document}
- 命令/方法如何处理项目周围的空格:它会删除前导和尾随空格
- 命令/方法如何处理空/缺失的项目:忽略它们
- 命令/方法如何处理尾随逗号:忽略它
- 存储在宏中的列表的命令/方法是否可以起作用:否
- 每次迭代是全局评估还是在本地组进行评估:本地
- 项目在处理之前是否会被扩展或以某种方式损坏:是的,它们会扩展为
box0
- 那么列表项为键=值或其他类型的对呢:不应该有关系
- 该方法是否适用于用逗号以外的其他符号分隔的列表:是的
- 在决定使用此方法之前我应该知道的其他事情:如果你不试图忽略空项目,你就不会遇到
box0
问题
答案4
包裹kvsetkeys
- 包裹
kvsetkeys
提供键值列表和逗号分隔列表的解析器。 - 支持多种格式:LaTeX、纯 TeX,甚至可以与 iniTeX 一起使用。
句法:
\comma@parse{⟨comma separated list⟩}{⟨code/processor with one argument⟩}
例子
给出了 iniTeX 的示例来展示该包的最低要求。
% Setup for iniTeX
\catcode`\{=1
\catcode`\}=2
\catcode`\#=6
\catcode`\@=11
% Package is loaded; LaTeX: \usepackage{kvsetkeys}
\input kvsetkeys.sty
% -------------------------------------------------------------------
% Call with entry processor that takes the entry as argument
% -------------------------------------------------------------------
\def\ProcessEntry#1{\message{<#1>}}
\comma@parse{a, b,, c ,d,}\ProcessEntry
% Result: <a> <b> <c> <d>
% -------------------------------------------------------------------
% Funny entries
% -------------------------------------------------------------------
\long\def\@gobble#1{}% from LaTeX
\def\@empty{}% from LaTeX
\comma@parse{
a, #1, \fi, \iffalse, {b}, {{c}}, d e f, {}, \empty, { }, {g,h}
}{%
\immediate\write16{[\meaning\comma@entry]}%
\@gobble
}
% Result:
% [macro:->a]
% [macro:->##1]
% [macro:->\fi ]
% [macro:->\iffalse ]
% [macro:->b]
% [macro:->{c}]
% [macro:->d e f]
% [macro:->\empty ]
% [macro:-> ]
% [macro:->g,h]
% -------------------------------------------------------------------
% \comma@break allows the premature ending of the list processing
% -------------------------------------------------------------------
\comma@parse{
2,3,5,7,0,1,4
}{%
\ifnum\comma@entry=0 %
\comma@break
\else
\message{<\comma@entry>}%
\fi
\@gobble
}
% Result: <2> <3> <5> <7>
% -------------------------------------------------------------------
% Active commas are supported as separators
% -------------------------------------------------------------------
\catcode`\,=13 % active
\def,{;}
\comma@parse{a,b,c}{\message{(\comma@entry)}\@gobble}
% Result: (a) (b) (c)
% End of job
\csname @@end\endcsname\end
问题答案
- 命令/方法如何处理项目周围的空格?
项目周围的空格(也可以超过一个)被删除。
- 命令/方法如何处理空的/丢失的项目?(例如
a,b,,c
)
空项将被忽略。可以使用一对花括号指定空项:a, {}, b
- 命令/方法如何处理尾随逗号?
这被解释为一个空的最后一项,它将被当作其他空项来处理,参见前面的答案。
- 存储在宏上的列表的命令/方法可以起作用吗?
由于列表是的第一个参数\comma@parse
,因此可以使用以下方法轻松解包列表宏\expandafter
:
\def\list{a,b,c}
\expandafter\comma@parse\expandafter{\list}{...}
- 每次迭代都是全局评估还是在本地组进行评估?(即,如果在迭代期间定义了命令,那么该命令是否在循环中继续存在?)
循环不会放入本地组,当前组级别不会改变。因此,包括定义在内的赋值在循环中仍然存在。
- 物品在被处理之前是否会被扩大或者以某种方式被损坏?
否。如果使用了带参数的处理器,则该条目可直接使用\comma@parse
。否则,该条目将存储在宏中并可用\comma@entry
。
如果值被括在花括号中,则将精确删除一层花括号。这样,通过将项目放在一对花括号中,该项目可以为空,可以包含空格或逗号,并且不会触及正确嵌套的花括号。
key=value
那么包含项目或其他类型的配对的列表怎么样?
该包kvsetkeys
还支持带有键值对的列表。
- 该方法是否适用于用逗号以外的其他符号分隔的列表?
支持活动逗号(以及用于键值处理的活动等号)。但其他符号不能用作分隔符。
- 在决定使用此方法之前我还应该了解其他什么事情?
\comma@break
如果不再需要以下条目,则实现结束列表解析的快捷方式。- 目前以
\par
break开头的条目\comma@parse
。下一版本将支持此功能。