改进列表解析命令

改进列表解析命令

这个问题引出了新方案的一部分:
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>bslashbackslash。请参阅下面的示例。

\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}

在此处输入图片描述

相关内容