Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

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@menu}{,}
\DeclareListParser{\tw@do@list@count}{,}

\NewDocumentCommand{\setmenuseperator}{v}{%
   \let\tw@do@menu\relax%
   \let\tw@do@list@count\relax%
   \DeclareListParser{\tw@do@menu}{#1}%
   \DeclareListParser{\tw@do@list@count}{#1}%
}

\newcommand{\menu}[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@do@menu{#1}%
   \tw@postcode%
}

\makeatother

\setlength{\parskip}{1cm}
\begin{document}
\menu{1,2,3,4}

\menu{Single Element}

\menu{A,B,C,D,E}

\setmenuseperator{/}
\menu{C:/Nutzer und Einstellungen/Desktop/Test}

% doesn’t work yet
%\setmenuseperator{\}
%\menu{C:\Nutzer und Einstellungen\Desktop\Test} 
\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.

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

2 Answers

up vote 9 down vote accepted

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,}%
  }%
}
\newcommand\menu[2][,]{%
  \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}%
}
\cptrobustify\menu
\edef\cpt@parserlist{\cpt@parserlist\@backslashchar}
\makeatother
% Tests:
\begin{document}
\parindent-40pt
\menu{1,2,3,4}
\par\medskip
\menu{Single Element}
\par\medskip
\menu{A,B,C,D,E}
\par\medskip
\menu[/]{C:/Nutzer und Einstellungen/Desktop/Test}
\par\medskip
\menu[bslash]{C:\Nutzer und Einstellungen\Desktop\Test}
\end{document}

enter image description here

share|improve this answer
    
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
1  
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
show 5 more comments
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\seq_new:N \l_tobi_menu_seq
\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 \\ }

\NewDocumentCommand{\setmenuseparator}{ m }
  {
   \tl_set:Nn \l_tobi_sep_tl { #1 }
  }
\NewDocumentCommand{\menu}{ s m }
  {
   \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 }
     \exp_args:NV \tobi_menu_process:n \l_tmpa_tl
     \group_end:
    }
    {
     \tobi_menu_process:n { #2 }
    }
  }
\cs_new:Npn \tobi_menu_process:n #1

   \exp_args:NNV \seq_set_split:Nnn \l_tobi_menu_seq \l_tobi_sep_tl { #1 }
   \tobi_premenu:
   \prg_case_int:nnn { \seq_length:N \l_tobi_menu_seq }
     {
      { 0 } { EMPTY }
      { 1 } { \tobi_singlemenu:n { \seq_map_function:NN \l_tobi_menu_seq \use:n } }
     }
     {
      \seq_pop_left:NN \l_tobi_menu_seq \l_tmpa_tl
      \seq_pop_right:NN \l_tobi_menu_seq \l_tmpb_tl
      \tobi_firstmenu:n { \l_tmpa_tl}
      \seq_map_inline:Nn \l_tobi_menu_seq { \tobi_midmenu:n { ##1 } }
      \tobi_lastmenu:n { \l_tmpb_tl }
     }
   \tobi_postmenu:
  }
\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}
\menu{1,2,3,4}

\menu{Single Element}

\menu{A,B,C,D,E}

\setmenuseparator{/}
\menu{C:/Nutzer und Einstellungen/Desktop/Test}

\menu*{C:\Nutzer und Einstellungen\Desktop\Test} 
\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
\seq_new:N \l_tobi_menu_seq
\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 \\ }

\NewDocumentCommand{\setmenuseparator}{ m }
 {
  \tl_set:Nn \l_tobi_sep_tl { #1 }
 }
\NewDocumentCommand{\menu}{ s m }
 {
  \IfBooleanTF{#1}
   {
    \tobi_menu_process_rescan:n { #2 }
   }
   {
    \tobi_menu_process:n { #2 }
   }
 }

\cs_new:Npn \tobi_menu_process:n #1
 {
  \seq_set_split:NVn \l_tobi_menu_seq \l_tobi_sep_tl { #1 }
   \tobi_premenu:
   \int_case:nnn { \seq_count:N \l_tobi_menu_seq }
     {
      { 0 } { EMPTY }
      { 1 } { \tobi_singlemenu:n { \seq_map_function:NN \l_tobi_menu_seq \use:n } }
     }
     {
      \seq_pop_left:NN \l_tobi_menu_seq \l_tmpa_tl
      \seq_pop_right:NN \l_tobi_menu_seq \l_tmpb_tl
      \tobi_firstmenu:n { \l_tmpa_tl}
      \seq_map_inline:Nn \l_tobi_menu_seq { \tobi_midmenu:n { ##1 } }
      \tobi_lastmenu:n { \l_tmpb_tl }
     }
   \tobi_postmenu:
  }
\cs_new_protected:Npn \tobi_menu_process_rescan:n #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 \\ } { #1 }
  \tobi_menu_process:V \l_tmpa_tl
  \group_end:
 }
\cs_generate_variant:Nn \seq_set_split:Nnn {NV}
\cs_generate_variant:Nn \tobi_menu_process:n {V}

\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}
\menu{1,2,3,4}

\menu{Single Element}

\menu{A,B,C,D,E}

\setmenuseparator{/}
\menu{C:/Nutzer und Einstellungen/Desktop/Test}

\menu*{C:\Nutzer und Einstellungen\Desktop\Test} 
\end{document}
share|improve this answer
    
Thanks, but is this really easier? I don’t use Windows but others may like it :-/ –  Tobi Feb 18 '12 at 15:33
1  
@Tobi: If you are a little bit familiar with the new syntax it is easier and of course clearer –  Marco Daniel Feb 18 '12 at 16:02
    
Here's a way (with some limitations) –  egreg Feb 18 '12 at 16:04
    
@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
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.