# Improve list parsing command

This question led to part of a new package:
menukeys

I build a command to iterate over a list with changable seperators and it works like expected but I wonder if there is a faster or more elegant way to do it?

\documentclass{article}
\makeatletter
\RequirePackage{etoolbox}
\RequirePackage{xparse}

\def\tw@first@element#1{\fbox{\strut#1 (first)}}
\def\tw@middle@element#1{\fbox{\strut#1 (mid)}}
\def\tw@last@element#1{\fbox{\strut#1 (last)}}
\def\tw@single@element#1{\fbox{\strut#1 (single)}}

\def\tw@seperator{\fbox{\strut sep}}
\def\tw@precode{\fbox{\strut pre}}
\def\tw@postcode{\fbox{\strut post}}

\newcounter{tw@list@i}
\newcounter{tw@list@last}

\def\tw@input@seperator{,}

\DeclareListParser{\tw@do@list@count}{,}

\let\tw@do@list@count\relax%
\DeclareListParser{\tw@do@list@count}{#1}%
}

\setcounter{tw@list@i}{1}%
\setcounter{tw@list@last}{0}%
\renewcommand*{\do}[1]{\stepcounter{tw@list@last}}%
\tw@do@list@count{#1}%
\tw@precode%
\renewcommand*{\do}[1]{%
\ifnumequal{1}{\value{tw@list@last}}{%
\tw@single@element{##1}%
}{%
\ifnumequal{1}{\value{tw@list@i}}{%
\tw@first@element{##1}%
}{%
\ifnumequal{\value{tw@list@i}}{\value{tw@list@last}}{%
\tw@seperator\tw@last@element{##1}%
}{%
\tw@seperator\tw@middle@element{##1}%
}%
}%
}%
\stepcounter{tw@list@i}%
}%
\tw@postcode%
}

\makeatother

\setlength{\parskip}{1cm}
\begin{document}

% doesn’t work yet
\end{document}


The requirements for the \menu command are the following

• Special treatment for single item lists
• Treat first and last list item other than the mid ones
• Insert a separator only between the elements
• add some code before and after the list

## Extra

As you can imagine such commands should be used to format paths automatically, so it would be great if a \ could be recognized as list separator.

-
+1 for taking the effort to turn this into a -- well-documented -- package. Good work! –  Daniel May 23 '12 at 16:58

The catoptions package has exactly the type of list processor you need. Call \menu using the syntax

\menu[<list separator>]{<list>}


The <list separator> can be anything you like, but if it is backslash (ie, \), then specify it (<list separator>) as either bslash or backslash. See examples below.

\documentclass{article}
\makeatletter
\usepackage{catoptions}[2011/12/17]

% One command (\tobiprint) replaces 7 commands by Tobi:
\robust@def*\tobiprint#1#2{\fbox{\strut#2\ifblankTF{#1}{}{~(#1)}}}

% We want to test if the <list separator> is 'bslash' or 'backslash'. For the
% test to be valid, we have to separate 'bslash' and 'backslash'. We ordinarily
% would have used comma (,) to do the splitting, but comma is also an admissible
% value for <list separator>. In fact, any character is a valid value for
% <list separator>. Therefore, the splitter has to be a character that the user
% is unlikely to submit as the <list separator>. One such character is character
% number 1 (^^A). Hence the comma in the definition of \testlistsep is actually
% character ^^A.
% Secondly, <list separator> may have been specified by the user with spurious
% leading and trailing spaces. Users do this to prettify their code. That is the
% reason for calling \cpttrimspaces in \testlistsep.
\begingroup
\lccode\,=1
\lowercase{\endgroup
\robust@def*\testlistsep#1{%
\xifinsetTF{,\cpttrimspaces{#1},}{,bslash,backslash,directory,location,}%
}%
}
\tobiprint{}{pre}%
% \indrisloop accepts any user-specified operator. Let us call yours \tobido:
\def\tobido##1{%
\iflastindris
\ifnum\indrisnr=\@ne
\tobiprint{single}{##1}%
\else
\tobiprint{}{sep}\tobiprint{last}{##1}%
\fi
\else
\ifnum\indrisnr=\@ne
\tobiprint{first}{##1}%
\else
\tobiprint{}{sep}\tobiprint{middle}{##1}%
\fi
\fi
}%
\testlistsep{#1}{%
\edef\alist{\detokenize{#2}}\edef\@tempa{\@backslashchar}%
}{%
\edef\alist{\unexpanded{#2}}\edef\@tempa{\detokenize{#1}}%
}%
\cptexpanded{\indrisloop*[\@tempa]}\alist\tobido
\tobiprint{}{post}%
}
\edef\cpt@parserlist{\cpt@parserlist\@backslashchar}
\makeatother
% Tests:
\begin{document}
\parindent-40pt
\par\medskip
\par\medskip
\par\medskip
\par\medskip
\end{document}


-
Thank you. Looks very nice and I prefer the 2e solution against @egregs L3 one since I can’T read it. Could you please explain your code a little more detailed (especially the lines 5–9)? –  Tobi Feb 19 '12 at 11:14
@Tobi: I have added explanatory notes as requested. –  Ahmed Musa Feb 19 '12 at 18:47
Why is it faster? –  Marc van Dongen Feb 19 '12 at 18:49
@MarcvanDongen: Please I don't understand your question, if it was meant for me. –  Ahmed Musa Feb 19 '12 at 21:52
Thank you. But what does the \begin-/\endgroup construct do; I wonder wh it works since they are unbalanced with the braces. And why are you using \lowercase? –  Tobi Feb 19 '12 at 23:38
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\tl_new:N \l_tobi_sep_tl
\tl_set:Nn \l_tobi_sep_tl { , } % default
\tl_const:Nx \c_tobi_backslash_tl { \cs_to_str:N \\ }

{
\tl_set:Nn \l_tobi_sep_tl { #1 }
}
{
\IfBooleanTF{#1}
{
\group_begin:
\tl_set_eq:NN \l_tobi_sep_tl \c_tobi_backslash_tl
\tl_set_rescan:Nnn \l_tmpa_tl {\char_set_catcode_other:N \\ } { #2 }
\group_end:
}
{
}
}

\exp_args:NNV \seq_set_split:Nnn \l_tobi_menu_seq \l_tobi_sep_tl { #1 }
{
{ 0 } { EMPTY }
}
{
}
}
\cs_new:Npn \tobi_premenu: { \fbox{\strut pre} }
\cs_new:Npn \tobi_postmenu: { \fbox{\strut post} }
\cs_new:Npn \tobi_firstmenu:n #1 { \fbox{\strut #1~(first)} }
\cs_new:Npn \tobi_midmenu:n #1 { \fbox{\strut #1~(mid)} }
\cs_new:Npn \tobi_lastmenu:n #1 { \fbox{\strut #1~(last)} }
\cs_new:Npn \tobi_singlemenu:n #1 { \fbox{\strut #1~(single)} }
\ExplSyntaxOff

\setlength{\parskip}{1cm}
\begin{document}

\end{document}


You have only to give sensible definitions to the six functions \tobi_Xmenu.

EDIT: I've added support also for \ as separator; it should be called as \menu* (and doesn't need a previous \setmenuseparator).

## Important change

Due to the changes made to expl3 in Summer 2012, here's a new (cleaned up) version of the code above.

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\tl_new:N \l_tobi_sep_tl
\tl_set:Nn \l_tobi_sep_tl { , } % default
\tl_const:Nx \c_tobi_backslash_tl { \cs_to_str:N \\ }

{
\tl_set:Nn \l_tobi_sep_tl { #1 }
}
{
\IfBooleanTF{#1}
{
}
{
}
}

{
\seq_set_split:NVn \l_tobi_menu_seq \l_tobi_sep_tl { #1 }
{
{ 0 } { EMPTY }
}
{
}
}
{
\group_begin:
\tl_set_eq:NN \l_tobi_sep_tl \c_tobi_backslash_tl
\tl_set_rescan:Nnn \l_tmpa_tl {\char_set_catcode_other:N \\ } { #1 }
\group_end:
}
\cs_generate_variant:Nn \seq_set_split:Nnn {NV}

\cs_new_protected:Npn \tobi_premenu: { \fbox{\strut pre} }
\cs_new_protected:Npn \tobi_postmenu: { \fbox{\strut post} }
\cs_new_protected:Npn \tobi_firstmenu:n #1 { \fbox{\strut #1~(first)} }
\cs_new_protected:Npn \tobi_midmenu:n #1 { \fbox{\strut #1~(mid)} }
\cs_new_protected:Npn \tobi_lastmenu:n #1 { \fbox{\strut #1~(last)} }
\cs_new_protected:Npn \tobi_singlemenu:n #1 { \fbox{\strut #1~(single)} }
\ExplSyntaxOff

\setlength{\parskip}{1cm}
\begin{document}


@egreg I'd be more comfortable with both arguments being grabbed by \menu` in the first instance. –  Joseph Wright Feb 18 '12 at 16:28