I would like to be able to sort a comma separated list.
Before I resort to writing something that would require \ensureunix
:-) and having that do the sort for me, I thought I'd make an attempt to doing it within TeX. So, with the help of the two questions listed in the references I have been able to get this to work, but only for the case where the list is not defined in a macro. Unfortunately for me, I only need the case where it works with macro defined lists, but would be nice to have one version which handles both.
With
%\def\SupportMacroDefinedList{}
commented out (as in the given MWE), you can see that the sorting works fine for the case when the list is not defined in a macro (right hand side of image). There are no spurious spaces and empty lists are handled just fine:For the macro defined list (i.e., with
\def\SupportMacroDefinedList{}
uncommented), this is the best I can do:This is labelled as "almost" working for several reasons, and note that there are different definitions of the list macros for each case. Some of the problems are:
- A trailing
%
was required after the first entry. Without that the sort order changes!! - It does not properly handle empty lists (which the non-macro version above had no problems with).
- Multiple consecutive commas are properly treated as non-list members as in
Zebra,,
, but if there is a space in between the commas as inZebra, ,
then this yields the errorUndefined control sequence. <argument> \@xfor@endmarker
. - Similarly, can't have a line with just a comma (probably same issues as the spaces in multiple consecutive commas).
- This requires a trailing
%
after the last list member. Try taking that out, or adding a space before the%
and making sense of the sort order.
- A trailing
Notes:
- The commas in the output are printed at the end of each list member only to be able to see if there is any spurious spaces.
- According to the comments in How to process a comma separated list?,
\clist_map_inline:nn
is supposed to be able to strip spurious spaces, but does not seem to for me. Instead it treats the macro defined list as a single member list. Also, this same linked question references\clist_map_inline:on
but this yields a compile time error:Undefined control sequence
for me.
References:
Minimum Requirements:
For this to useful for me, I really only need it to handle
Empty lists (only commas, spaces, and/or newlines),
No trailing
,
on the last list member:\newcommand*{\ListMembers}{% Odd, Zebra, Even, Alpha }%
with a trailing
,
on the last member:\newcommand*{\ListMembers}{% Odd, Zebra, Even, Alpha, }%
Something that did not use the expl3
syntax would be great too as then I would at least have have some hope of modifying it if it was ever needed. :-)
Code:
%\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}