这个问题引出了新方案的一部分:
menukeys
我构建了一个命令来迭代具有可变分隔符的列表,并且它按预期工作,但我想知道是否有更快或更优雅的方法来做到这一点?
\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}
该命令的要求\menu
如下
- 单项清单的特殊处理
- 处理除中间项之外的第一个和最后一个列表项
- 仅在元素之间插入分隔符
- 在列表前后添加一些代码
额外的
您可以想象,这样的命令应该用于自动格式化路径,因此如果\
可以将 a 识别为列表分隔符,那就太好了。
答案1
这期权包中正好有您需要的列表处理器类型。\menu
使用语法调用
\menu[<list separator>]{<list>}
可以<list separator>
是任何你喜欢的字符,但如果它是反斜杠(即\
),则将其指定<list separator>
为bslash
或backslash
。请参阅下面的示例。
\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}
答案2
\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}
您只需对这六个功能给出合理的定义\tobi_Xmenu
。
编辑:我还添加了对\
分隔符的支持;它应该被称为\menu*
(并且不需要先前的\setmenuseparator
)。
重要变更
由于所做的更改expl3
,这里是上述代码的新版本(清理后的版本)。
\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:nnF { \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}
答案3
我们仅使用 TeX 基元即可处理此问题。代码更紧凑,我们不需要任何 LaTeX 包:
\def\menubox#1{\vbox{\hrule\hbox{ #1 \vrule height12pt depth5pt}\hrule}}
\def\menu{\hbox\bgroup\catcode`\\=12 \menuA}
\def\setmenusep#1{\expandafter\setmenusepA\string#1\end}
\def\setmenusepA#1#2\end{%
\def\menuA##1{\def\tmp{}\hbox{\vrule\menubox{pre}}\menuB##1#1\end#1}
\def\menuB##1#1##2#1{%
\ifx\end##2\ifx\tmp\empty \def\tmp{single}\else \def\tmp{last}\fi\fi
\ifx\tmp\empty\def\tmp{first}\fi
\menubox{##1 (\tmp)}%
\def\tmp{middle}%
\ifx\end##2\menubox{post}\let\next=\egroup
\else\menubox{sep}\def\next{\menuB##2#1}\fi
\next
}}
\setmenusep ,
% TEST:
\menu{1,2,3,4}
\medskip
\menu{Single Element}
\medskip
\setmenusep /
\menu{C:/Nutzer und Einstellungen/Desktop/Test}
\medskip
\setmenusep \\
\menu{C:\Nutzer und Einstellungen\Desktop\Test}
\bye
答案4
这是一种listofitems
方法。我还实现了一种方法,也可以用作\thebackslash
分隔符;但是,a\
不能是字符串的最后一个字符。(我认为其他解决方案在这种情况下也不起作用)。分隔符可以是字符串。如果要!
在分隔符中使用,则需要说明\setMenuseparator[+]{!}
其中+
或任何其他选定的标记不是主要参数的一部分。
\documentclass{article}
\makeatletter
\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@separator{\fbox{\strut sep}}
\def\tw@precode{\fbox{\strut pre}}
\def\tw@postcode{\fbox{\strut post}}
\usepackage{listofitems}
\newcommand\Menu[1]{%
\edef\tmp{\detokenize{#1}}%
\greadlist*\Menulist{\tmp}%
\tw@precode%
\ifnum\listlen\Menulist[]=1\relax%
\tw@single@element{\Menulist[1]}%
\else%
\foreachitem\i\in\Menulist{%
\ifnum\icnt=1\relax
\tw@first@element{\i}%
\else%
\tw@separator%
\ifnum\icnt=\listlen\Menulist[]\relax
\tw@last@element{\i}%
\else%
\tw@middle@element{\i}%
\fi%
\fi%
}%
\fi%
\tw@postcode%
}
\edef\tmp{\detokenize{\ }}
\def\tmpA#1#2\relax{#1}
\edef\thebackslash{\expandafter\tmpA\tmp\relax}
\newcommand\setMenuseparator[2][!]{%
\ifx\thebackslash#2%
\expandafter\setsepchar\expandafter{\thebackslash}%
\else%
\setsepchar[#1]{#2}%
\fi%
}
\setMenuseparator{,}
\makeatother
\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}
\setMenuseparator{\thebackslash}
\Menu{C:\Nutzer und Einstellungen\Desktop\Test}
\end{document}