我希望能够对以逗号分隔的列表进行排序。
在我写一些需要\ensureunix
:-) 的东西并让它帮我完成排序之前,我想尝试在 TeX 中完成它。因此,在参考文献中列出的两个问题的帮助下,我能够让它工作,但是仅有的对于列表的情况不是在宏中定义。不幸的是,我只需要它起作用的情况和宏定义列表,但如果有一个版本可以同时处理这两个问题就更好了。
注释掉
%\def\SupportMacroDefinedList{}
后(如给定的 MWE 所示),您可以看到,当列表未在宏中定义时(图像右侧),排序工作正常。没有虚假空格,空列表处理得很好:对于宏定义列表(即
\def\SupportMacroDefinedList{}
未注释),这是我能做的最好的:这被标记为“几乎”工作有几个原因,请注意,每种情况的列表宏定义都不同。其中一些问题是:
%
第一个条目后需要尾随。没有它,排序顺序就会改变!!- 它不能正确处理空列表(上面的非宏版本没有问题)。
- 多个连续的逗号被正确地处理为非列表成员,如
Zebra,,
,但如果逗号之间有空格,如 ,Zebra, ,
则会产生错误Undefined control sequence. <argument> \@xfor@endmarker
。 - 类似地,不能只有逗号的一行(可能与多个连续逗号中的空格存在同样的问题)。
- 这需要
%
在最后一个列表成员后面添加一个尾随。尝试将其删除,或在之前添加一个空格%
,并理解排序顺序。
笔记:
- 输出中的逗号仅打印在每个列表成员的末尾,以便查看是否存在任何虚假空格。
- 根据如何处理以逗号分隔的列表?,
\clist_map_inline:nn
应该能够去除虚假空格,但对我来说似乎不行。相反,它将宏定义列表视为单个成员列表。此外,这个相同的链接问题引用了但这对我来说\clist_map_inline:on
会产生编译时错误: 。Undefined control sequence
参考:
最低要求:
为了对我有用,我真的只需要它来处理
空列表(仅逗号、空格和/或换行符),
,
最后一个列表成员无尾随:\newcommand*{\ListMembers}{% Odd, Zebra, Even, Alpha }%
,
最后一个成员尾随:\newcommand*{\ListMembers}{% Odd, Zebra, Even, Alpha, }%
不使用expl3
语法的东西也很好,因为这样我至少可以在需要的时候修改它。:-)
代码:
%\def\SupportMacroDefinedList{}%
\documentclass{article}
\usepackage{datatool}
\usepackage{xparse}
\usepackage{xstring}
\newcommand*{\SortDBName}{sortDB}%
\newcommand*{\DBKey}{label}%
\newcommand{\TitleA}{\textit{My Enumerated List:}}%
\newcommand{\TitleB}{\textit{My Empty List:}}%
%------------------------------------- Sort CSV list
% Adapted from:
% https://tex.stackexchange.com/questions/40031/how-to-process-a-comma-separated-list
% https://tex.stackexchange.com/questions/6988/how-to-sort-an-alphanumeric-list
\newcommand{\SortItem}[1]{%
\IfStrEq{#1}{\empty}{%
% Skip empty list members
}{%
\DTLnewrow{\SortDBName}%
\IfEndWith{#1}{\space}{% attempt to remove any trailing space
\DTLnewdbentry{\SortDBName}{\DBKey}{\StrGobbleRight{#1}{1}}%
}{%
\DTLnewdbentry{\SortDBName}{\DBKey}{#1}%
}%
}%
}
% How do I make \SortCommaSeparatedList work for BOTH cases
\ifdefined\SupportMacroDefinedList%
\ExplSyntaxOn
\NewDocumentCommand{\SortCommaSeparatedList}{>{\SplitList {,}}m}
{ \clist_map_inline:Nn {#1} { \SortItem {##1} } }% List in macro
% Note: nn is supposed to handle spurious spaces as per ... but doesn't
\ExplSyntaxOff
\else
\ExplSyntaxOn
\NewDocumentCommand{\SortCommaSeparatedList}{>{\SplitList {,}}m}
{ \tl_map_inline:nn {#1} { \SortItem {##1} } }% Non macro list
\ExplSyntaxOff
\fi%
\newcommand{\SortedList}[3]{%
% #1 = type of list
% #2 = title to print
% #3 = list content (Comma separated list)
%
\noindent#2\par%
%
\DTLifdbexists{\SortDBName}%
{\DTLcleardb{\SortDBName}}% DB exists, so just clear it
{\DTLnewdb{\SortDBName}}% DB does not exist, so create it
%
\SortCommaSeparatedList{#3}%
\DTLsort{\DBKey}{\SortDBName}%
\IfEq{\DTLrowcount{\SortDBName}}{0}{%
\par%
No items to print for ``#1" list.%
}{%
\begin{#1}%
\DTLforeach*{\SortDBName}{\CurrentItem=\DBKey}{%
\item \CurrentItem,%
}%
\end{#1}%
}%
}%
\ifdefined\SupportMacroDefinedList%
\newcommand*{\ListMembers}{% Why is % required after the first line?
Odd,%
Zebra,
%,% Can't have this
Even,
Alpha%
}%
\newcommand*{\EmptyListMembers}{%
, ,% %%% Note: Spaces after last comma NOT-ok for macro version
}%
\else%
\newcommand*{\ListMembers}{%
Odd,,
Zebra, ,
,
Even,
Alpha ,
}%
\newcommand*{\EmptyListMembers}{%
, , % %%% Note: Spaces after last comma ok for non-macro version
}%
\fi%
\begin{document}
\ifdefined\SupportMacroDefinedList%
\section*{Almost works for Macro Defined List}%
\else%
\section*{Works for non-macro Defined List}%
\fi%
\begin{minipage}[t]{0.45\linewidth}\noindent%
\textbf{Macro defined list}\medskip\par%
\SortedList{enumerate}{\TitleA}{\ListMembers}
\SortedList{itemize}{\TitleB}{\EmptyListMembers}
\end{minipage}%
%
\hfill
%
\begin{minipage}[t]{0.45\linewidth}%
\textbf{Non-macro defined list}\medskip\par\noindent%
\SortedList{enumerate}{\TitleA}{ Odd, Zebra, , Even , Alpha ,}
\SortedList{itemize}{\TitleB}{ , , }
\end{minipage}%
\end{document}
答案1
解决这个问题的方法有很多:选择哪种方法取决于您的具体要求。
以问题中的例子为例,显然失败的原因在于\clist_map_inline:nn
非常expl3
小心,不会“意外”扩展任何东西。因此,当抓取的参数是宏时包含逗号分隔的列表,代码永远不会看到逗号:您知道,这可能是一个只有一个项目的列表,而这个项目恰好是一个包含另一个列表的宏!expl3
处理“存储”和“内联”逗号列表的函数之间也存在差异。本质上,这个想法是“存储”列表已经经过清理,删除了空格和空项目。所以你需要做的是使用排队列出函数并扩展您的输入一次:
\NewDocumentCommand \SortCommaSeparatedList { m }
{ \exp_args:No \clist_map_function:nN {#1} \SortItem }
这将适用于您输入的两种形式,因为您的“内联”列表仅包含不可扩展的标记。一般来说,您不能假设这一点,所以我认为应该将其\SortCommaSeparatedList
描述为接受包含列表的宏,或接受列表,但不能同时接受两者。
为了避免expl3
,也许最简单的方法是使用 LaTeX2e\@for
以及一些空格剥离代码,同样尽可能少地进行更改:
\makeatletter
\newcommand*{\SortCommaSeparatedList}[1]{%
\expandafter\@for\expandafter\@tempa\expandafter:\expandafter=#1\do{%
\edef\@tempa{\expandafter\trim@spaces\expandafter{\@tempa}}%
\expandafter\SortItem\expandafter{\@tempa}%
}
}
% This is expl3's \tl_trim_spaces:n
\def\@tempa#1{%
\newcommand{\trim@spaces}[1]{%
\unexpanded\trim@spaces@aux@i\@mark##1\@nil\@mark#1{}\@mark
\trim@spaces@aux@ii\trim@spaces@aux@iii#1\@nil\trim@spaces@aux@iv\@stop
}
\newcommand{\trim@spaces@aux@i}{}
\long\def\trim@spaces@aux@i##1\@mark#1##2\@mark##3{%
##3%
\trim@spaces@aux@i\@mark##2\@mark#1{##1}%
}
\newcommand{\trim@spaces@aux@ii}{}
\long\def\trim@spaces@aux@ii##1\@mark\@mark##2{%
\trim@spaces@aux@iii##2%
}
\newcommand{\trim@spaces@aux@iii}{}
\long\def\trim@spaces@aux@iii##1#1\@nil##2{%
##2%
##1\@nil
\trim@spaces@aux@iii
}
\newcommand{\trim@spaces@aux@iv}{}
\long\def\trim@spaces@aux@iv##1\@nil##2\@stop{%
\expandafter{\@gobble##1}%
}
}
\@tempa{ }
\makeatother
这再次扩展了参数一次,这次我们对每个项目进行更尴尬的循环。空间修剪代码与 中的代码完全相同expl3
,但以更“传统”的形式编写。(您可以使用 中的代码更有效地编写循环expl3
,但这似乎需要付出更多努力,而实际收获却很少。)
您可以通过几种方式进一步了解。首先,如果您愿意坚持使用,expl3
那么您可以避免加载xstring
并使用等等进行比较\tl_if_empty:nTF
。还有一个实验性的排序模块可以为您完成整个工作!另一方面,由于expl3
需要\pdfstrcmp
原语,您可以使用它进行排序,尽管这会稍微复杂一些,因为它纯粹基于字符代码。最后,当然您可以使用 LuaTeX,并在 Lua 中进行排序(我猜您想要一些相当通用的,所以这可能不行)。