In LaTeX3, the preferred way to get user-level functions is xparse
and its \NewDocumentCommand
function.
If the list was not a comma-separated list, then you would have to do something like
\ExplSyntaxOn
\NewDocumentCommand{\PrintAnswerList}{>{\SplitList;}m}
{ \tl_map_inline:nn {#1} { \PrintAnswer {##1.ans} } }
\ExplSyntaxOff
\PrintAnswerList { file01 ; file 02 ; file03 }
Then \PrintAnswer
would be performed outside the scope of the expl syntax.
Here, your life is both slightly easier because you have a comma-separated list, and slightly harder because it is not given directly, but is given hidden inside a macro, \inputfiles
. In the code below, I used \clist_map_inline:on
, which expands its clist argument once before performing the second argument for each item. Since this particular variant is not available in the kernel, we need to provide it, with
\clist_generate_variant:Nn \clist_map_inline:nn { o }
All in all, you can do for instance (I changed \PrintAnswer
too)
\documentclass{book}
...
\usepackage{expl3,xparse}
\ExplSyntaxOn
\NewDocumentCommand { \PrintAnswer } { m }
{
\file_if_exist:nTF {#1}
{
\iow_term:n {****~Printing~#1}
\file_input:n {#1}
\refstepcounter {subsection}
}
{ \iow_term:n {****~#1~not~found~****} }
}
\cs_generate_variant:Nn \clist_map_inline:nn {o}
\NewDocumentCommand { \PrintAnswerList } { m }
{ \clist_map_inline:on {#1} { \PrintAnswer {##1.ans} } }
\ExplSyntaxOff
\begin{document}
\PrintAnswerList{\inputfiles}
\end{document}
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).
Best Answer
This seems to work, with no packages. EDITED to solve the upper/lower-case problem.
EDIT: Resolved problem when a comparison ran out of letters prior to resolving the order, for example,
wash, washer
.See ADDENDUM for handling (after a fashion) diacritics.
ADDENDUM
Here's a version that can handle diacritics, in the sense that they do not break the algorithm. This was accomplished by changing the
\edef
s in the above algorithm to appropriately expanded\def
s.However, diacritics here will always precede all non-diacritic letters in the sort. While maybe not the ideal behavior, it may still be useful.
The algorithm was based on my
\bubblesort
macro here: Using LaTeX to compact a list of numbers