There are many ways of tackling this problem: which you choose depends on your particular requirements.
Taking the example in the question, the reason for the apparent failure with \clist_map_inline:nn
is that expl3
is very careful not to expand anything 'by accident'. Thus when the argument grabbed is a macro containing a comma-separated list, the code never sees the commas: for all you know this could be a list of one item only which just happens to be a macro itself containing a further list! There is also a difference in expl3
between functions which deal with 'stored' and 'inline' comma lists. Essentially, the idea is that a 'stored' list will already have been sanitized to remove spaces and empty items. So what you need to do is use the inline list function and expand your input once:
\NewDocumentCommand \SortCommaSeparatedList { m }
{ \exp_args:No \clist_map_function:nN {#1} \SortItem }
This will work with both forms of your input as your 'inline' list only contains unexpandable tokens. In general you cannot assume that, so I would say that \SortCommaSeparatedList
should be described as accepting either a macro containing a list, or as accepting a list, but not both.
To avoid expl3
, perhaps the easiest way would be to use LaTeX2e's \@for
along with some space stripping code, again altering as little as possible:
\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
This again expands the argument once, and this time we do a more awkward loop over every item. The space-trimming code is exactly that in expl3
, but written in a more 'traditional' form. (You could write the loop more efficiently here by using that from expl3
, but that seems like more effort for very little real gain.)
You could go further in a few ways. First, if you are willing to stick with expl3
then you could avoid loading xstring
and do the comparisons using \tl_if_empty:nTF
and so forth. There is also an experimental sorting module which would do the entire job for you! On the other hand, as expl3
requires the \pdfstrcmp
primitive you could use that for the sort, although that will be slightly complicated as it works purely on character codes. Finally of course you could use LuaTeX, and do the sort in Lua (I guess that you want some reasonably general, so that is probably out).
etoolbox
's list processing capabilities are straight forward:
\documentclass{article}
\usepackage{etoolbox}% http://ctan.org/pkg/etoolbox
\newcommand{\ppath}[2][$\;\triangleright\;$]{%
\def\nextitem{\def\nextitem{#1}}% Separator
\renewcommand*{\do}[1]{\nextitem\textsf{##1}}% How to process each item
\docsvlist{#2}% Process list
}
\begin{document}
A decent file path is \ppath{File,New,Document}.
\end{document}
The separator \nextitem
is defined to do nothing during its first use. \do
defines how each item is processed, while \docsvlist
processes a comma-separated list. See Cunning (La)TeX tricks for a short discussion on the use of \nextitem
.
Best Answer
A solution which goes through all elements:
Make sure comma isn't active, otherwise this won't work. For example, the following fails. Also, make sure there are no spurious spaces in your lists.