包裹kvsetkeys

包裹kvsetkeys

我如何迭代、循环、处理或以其他方式执行某物,每个元素都来自以逗号分隔的事物列表?

这个问题 到过 也许 许多 许多 ,而且已经有很多非常好的答案。但是我认为,如果有一个单一的参考点,详细解释所有这些替代技术,并讨论它们的优点和缺点,那也是很有价值的。

欢迎所有 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. 项目两边的空格都被删除了;

  2. 空项将被忽略(空表示逗号之间只有空格);

  3. 该项目未进行任何扩展。

请注意,当前项目用 表示,#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如果不再需要以下条目,则实现结束列表解析的快捷方式。
  • 目前以\parbreak开头的条目\comma@parse。下一版本将支持此功能。

相关内容