我正在制作一个宏,它将接受用户输入的评分方案并将其放入表格环境中。我希望语法尽可能简单,并决定:
\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\\ }}
我花了一段时间才弄清楚为什么这不起作用:#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\\ }}
%{\ProcessList {#1} {\mycommand}}
%\def\mycommand#1,#2{\addtabtoks{#1\\ }}
\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}