将多个参数从 ProcessList (xparse) 传递到宏。结果将在表格中使用

将多个参数从 ProcessList (xparse) 传递到宏。结果将在表格中使用

我正在制作一个宏,它将接受用户输入的评分方案并将其放入表格环境中。我希望语法尽可能简单,并决定:

\tabulate{ eval1,%1 ; eval2,%2 ; ... ; evaln,%n}

作为输入方案,例如

\tabulate{Midterm 1,15\% ; Midterm 2,20\%}

之前我问过关于从宏构建表格的问题,我知道我应该(需要?)使用 toks 来构建表格的行。我的第一次尝试如下:

\documentclass{article}
\usepackage{xparse}

\makeatletter
\newtoks\@tabtoks
\newcommand\addtabtoks[1]{\@tabtoks\expandafter{\the\@tabtoks#1}}
\newcommand*\resettabtoks{\@tabtoks{}}
\newcommand*\printtabtoks{\the\@tabtoks}
\makeatother

\DeclareDocumentCommand \tabulate%
{ > { \SplitList { ; } } m }%
{\ProcessList {#1} {\mycommand}}
\def\mycommand#1,#2{\addtabtoks{#1&#2\\ }}

我花了一段时间才弄清楚为什么这不起作用:#1 作为单个标记从 ProcessList 传递到 mycommand,不能作为单个标记读取?问题:我对上述原因不起作用的理解是否合理正确?如果不是,有人可以澄清一下吗?基于这种模糊的理解,我想出了一个可行的解决方案,但偏离了我想要的输入方案(这是几乎可行的示例):

\documentclass{article}
\usepackage{xparse}

\makeatletter
\newtoks\@tabtoks
\newcommand\addtabtoks[1]{\@tabtoks\expandafter{\the\@tabtoks#1}}
\newcommand*\resettabtoks{\@tabtoks{}}
\newcommand*\printtabtoks{\the\@tabtoks}
\makeatother

\DeclareDocumentCommand \tabulate%
{ > { \SplitList { ; } } m }%
{\ProcessList {#1} {\mycommand}}
\newcommand{\mycommand}[1]{
\def\temp{#1}
\expandafter\mycommandTwo\temp
}
\def\mycommandTwo#1,#2{\addtabtoks{#1&#2\\ }}

%{\ProcessList {#1} {\mycommand}}
%\def\mycommand#1,#2{\addtabtoks{#1&#2\\ }}


\begin{document}

\resettabtoks
\tabulate{Midterm 1,{15\%};Midterm 2,{20\%};Midterm 3,{25\%}}

\begin{tabular}{cc}
    \printtabtoks
\end{tabular}

\end{document}

再次,基于我的模糊理解,我将 ProcessList 中的参数存储在一个宏中,该宏会展开,因此可以被 mycommandTwo 读取为单独的标记。根据我是否正确理解了初始问题,这可能不是正确的解释。我的问题是,如果不像上面那样对百分比进行分组,即,{15\%}而不是仅仅这样,15\%我就得不到我想要的输出。我不确定为什么会这样。有人能帮助我理解为什么需要对百分比进行分组和/或对我目前的情况提出可能的修复/改进建议吗?我之前的尝试包括解析 csv eval,%,在每个条目前面加上&,然后删除&结果列表中的第一个,但我无法让它工作。提前致谢!

编辑 1:现在想想,我更愿意让用户甚至不需要输入之前的\%尽管我怀疑这需要比我掌握的更多的知识。

答案1

在做tabular事情的时候,TeX 看到的那一刻&非常关键,最好提前收集数据并一次性呈现给 TeX。

以下是使用 expl3 语法的方法:

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\tabulate}{ O{cc} m }
 {
  \tl_clear:N \l_scott_body_tl
  \seq_set_split:Nnn \l_scott_body_seq { ; } { #2 }
  \seq_map_inline:Nn \l_scott_body_seq { \scott_do_line:n { ##1 } }
  \begin{tabular}{#1}
  \tl_use:N \l_scott_body_tl
  \end{tabular}
 }
\cs_new:Npn \scott_do_line:n #1
  {
   \tl_clear:N \l_scott_row_tl
   \clist_map_inline:nn { #1 } { \tl_put_right:Nn \l_scott_row_tl { & ##1 } }
   \tl_put_right:Nn \l_scott_row_tl { \\ }
   \tl_set:Nx \l_scott_row_tl { \tl_tail:N \l_scott_row_tl }
   \tl_put_right:NV \l_scott_body_tl \l_scott_row_tl
  }
\seq_new:N \l_scott_body_seq
\tl_new:N \l_scott_body_tl
\tl_new:N \l_scott_row_tl
\ExplSyntaxOff
\begin{document}

\tabulate{Midterm 1,{15\%};Midterm 2,{20\%};Midterm 3,{25\%}}

\tabulate[cr]{Midterm 1,{15\%};Midterm 2,{20\%};Midterm 3,{5\%}}

\end{document}

有人可能会这样做\ProcessList,至少是为了获取行:

\ExplSyntaxOn
\NewDocumentCommand{\tabulate}{ O{cc} >{ \SplitList { ; } } m }
 {
  \tl_clear:N \l_scott_body_tl
  \ProcessList { #2 } { \scott_do_line:n }
  \begin{tabular}{#1}
  \tl_use:N \l_scott_body_tl
  \end{tabular}
 }
\cs_new:Npn \scott_do_line:n #1
  {
   \tl_clear:N \l_scott_row_tl
   \clist_map_inline:nn { #1 } { \tl_put_right:Nn \l_scott_row_tl { & ##1 } }
   \tl_put_right:Nn \l_scott_row_tl { \\ }
   \tl_set:Nx \l_scott_row_tl { \tl_tail:N \l_scott_row_tl }
   \tl_put_right:NV \l_scott_body_tl \l_scott_row_tl
  }
\tl_new:N \l_scott_body_tl
\tl_new:N \l_scott_row_tl
\ExplSyntaxOff

这本质上与以前相同:\SplitList将参数拆分为一个序列,然后\ProcessList对该序列进行映射(这是在第一个版本中直接完成的)。

每行都作为 进行处理clist;每项前面都有&,一行存储在标记列表中,第一行&被删除。然后,每行都添加到“主体”标记列表中,然后将其传递到环境中tabular


如果你真的想要%直接允许,那么你可以使用一个常见的技巧:

\newcommand{\tabulate}{\begingroup\catcode`\%=12 \innertabulate}

然后\innertabulate\tabulate之前一样定义,但\endgroup在末尾添加。

答案2

\SplitList此命令中的

\DeclareDocumentCommand \tabulate { > { \SplitList { ; } } m }%
  {\ProcessList {#1} {\mycommand}}
\tabulate{Midterm 1,15;Midterm 2,20;Midterm 3,25}

将给出{Midterm 1,15}{Midterm 2,20}{Midterm 3,25}所以#1从宏的角度来看单个项目确实是单个标记。

您的尝试还有另一个问题。请考虑以下命令:

\def\test#1,#2{1: #1; 2: #2; that's it! }
\test Midterm,15\%

会给你

1:期中考试;2:1;就这样!5%

仅显示逗号后的第一个标记。这就是为什么您必须在 周围添加括号15\%

为了看到整个事物,你需要另一个标记:

\def\test#1,#2\stop{1: #1; 2: #2; that's it! }
\test Midterm,15\%\stop

恕我直言,最简单的做法是使用expl3映射功能:

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\tl_new:N \l_tab_interior_tl
% this maps through the token list created by \SplitList and will apply
% \add_to_tab:w to every item in a way that it doesn't have to contain the
% comman and the secoind argument:
\DeclareDocumentCommand \tabulate { > { \SplitList { ; } } m }
  { \tl_map_inline:nn { #1 } { \add_to_tab:w ##1,, \q_stop } }

% this adds every entry from above in the form #1 & #2\% \\ to the token list
% that builds the table; it works similar to the \def\test above:
\cs_new:Npn \add_to_tab:w #1,#2,#3 \q_stop
  {
    \tl_put_right:Nn \l_tab_interior_tl
      { #1 & \tl_if_blank:nF { #2 } { #2 \% } \\ }
  }

% this prints the actual table material:
\cs_new_nopar:Npn \writetable { \tl_use:N \l_tab_interior_tl }

% this clears the tabular material; this functionality could of course be added
% to the \writetable command if you're sure you're not going to use it twice:
\cs_new_nopar:Npn \cleartable { \tl_clear:N \l_tab_interior_tl }
\ExplSyntaxOff

\begin{document}

\tabulate{Midterm 1,15;Midterm 2,20;Midterm 3,25}

\begin{tabular}{cc}
 \writetable
\end{tabular}

\cleartable
\tabulate{Midterm 1;Midterm 2,20;Midterm 3}

\begin{tabular}{cc}
 \writetable
\end{tabular}

\end{document}

相关内容