# Complex parsing of arguments in new environments

I am trying to build an invoice generation system in LaTeX which will be easy and maintainable. Right now we are using longtable and a preprocessing layer. I would like to change this to a .sty and a set of macros so that templates can be more heavily separated from the data entered into them.

What I would like is an API like:

\begin{invoicetable}{r|llrr|r}{runningnumber,description,qty,sellprice,linetotal}
\line{runningnumber=1,partnumber=A-12232,description={Test part, some data},qty=4,sellprice=14.99,linetotal=59.96}
\end{invoicetable}


This would be a macro for something like (pretty formatting added here for clarity):

\begin{longtable}{r|llrr|r}
\# & Partnumber & Description          & Qty & Sellprice & linetotal \\
1 & A-12232    & Test part, some data &   4 &     14.99 &     59.96 \\
\end{longtable}


A few reasons for doing this is that it would allow me to drop columns in the environment declaration and have relevant keys ignored in the line items, and it would free up key ordering in the addition of lines themselves (partnumber=A-12232,qty=4 would be the same regardless of ordering).

At this point I have a pretty good idea of how the \line macro would work in that case (I may name it \invoiceline) and if I have trouble I can ask more questions there, but my immediate question is with the argument to the \begin macro. I need to be able to figure out roughly how to internally take the comma separated values in, break them up into an ordered list and a counter, something like a counter of \invoice@cols as a counter, and \invoice@col@current as another one. I figure there would need to be a macro for each col, like \invoice@col1 returning the name of the column.

-

Here is a solution involving xtring:

\documentclass{article}
\usepackage{xstring,longtable}
\makeatletter
\def\line#1{%
\global\let\tab@keys@i\tab@keys
\gdef\line@content{#1,}%
\line@i
}

\def\line@i{%
\StrCut\tab@keys@i,\current@key\tab@keys@i
\global\let\tab@keys@i\tab@keys@i
\IfSubStr\line@content\current@key
{\StrBehind\line@content\current@key[\current@key]%
\StrBetween\current@key=,%
}
\relax
\ifx\@empty\tab@keys@i\\%
\else&\expandafter\line@i
\fi
}

\newenvironment{invoicetable}[2]%
{\edef\tab@keys{#2,}%
\expandarg\noexploregroups
\longtable{#1}%
}
\endlongtable

\makeatother
\begin{document}
\begin{invoicetable}{r|llrr|r}{runningnumber,description,qty,sellprice,linetotal}
\line{runningnumber=1,partnumber=A-12232,description={Test part, some data},qty=4,sellprice=14.99,linetotal=59.96}
\end{invoicetable}
\end{document}

-
If I'm not mistaken, this requires that the keys in \line (by the way, \line is already a LaTeX command) are in the same order as in the argument. – egreg Feb 23 '13 at 13:44
No, try \line{qty=4,linetotal=59.96,description={Test part, some data},sellprice=14.99,partnumber=A-12232,runningnumber=1} and the rtesult is the same. If \line is already defined, juste replace it by anything else, for example \myline and use \newcommand, which is safer than \def. – unbonpetit Feb 23 '13 at 14:07
I think this is the approach I will go with for now. I plan to change \line to \invoiceline – Chris Travers Mar 3 '13 at 2:48

Here's an implementation with LaTeX3 macros.

\documentclass{article}
\usepackage{longtable}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentEnvironment{invoicetable}{mm} % #1 = columns, #2 = column names
{
\travers_invoicetable:n { #2 }
}
{
\begin{longtable}{#1}
\hline
\l_travers_table_tl
\end{longtable}
}

{
}

\NewDocumentCommand{\tline}{m}
{
\travers_makeline:Nn \l_travers_table_tl { #1 }
}

\tl_new:N \l_travers_table_tl
\tl_new:N \l__travers_linetemp_tl
\tl_new:N \l_travers_lastcol_tl
\seq_new:N \l_travers_colnames_seq

%%% what to do with unknown keys
\keys_define:nn { travers/invoice }
{
unknown .code:n =
}

% absorb the list of column names and define the keys
\cs_new_protected:Npn \travers_invoicetable:n #1
{
\seq_set_split:Nnn \l_travers_colnames_seq { , } { #1 }
\seq_map_inline:Nn \l_travers_colnames_seq
{
\keys_define:nn { travers/invoice }
{
##1 .tl_set:c = { l__travers_name_##1_tl }
}
}
% detach the last column
\seq_pop_right:NN \l_travers_colnames_seq \l_travers_lastcol_tl
}

% each line processes the key-value pairs and adds to the table
\cs_new_protected:Npn \travers_makeline:Nn #1 #2
{
\tl_clear:N \l__travers_linetemp_tl
\keys_set:nn { travers/invoice } { #2 }
\seq_map_inline:Nn \l_travers_colnames_seq
{
\tl_put_right:Nv \l__travers_linetemp_tl { l__travers_name_##1_tl }
\tl_put_right:Nn \l__travers_linetemp_tl { & }
}
\tl_put_right:Nv \l__travers_linetemp_tl { l__travers_name_\l_travers_lastcol_tl _tl }
\tl_put_right:Nn \l__travers_linetemp_tl { \\ }
\tl_put_right:NV #1 \l__travers_linetemp_tl
}

\cs_generate_variant:Nn \tl_put_right:Nn { Nv }

\ExplSyntaxOff

\begin{document}
\begin{invoicetable}{r|llr|r}{
runningnumber,
description,
qty,
sellprice,
linetotal
}
runningnumber=\#,
partnumber=Part number,
description=Description,
qty=Qty,
sellprice=Price,
linetotal=Total
}
\tline{
runningnumber=1,
partnumber=A-12232,
description={Test part, some data},
qty=4,
sellprice=14.99,
linetotal=59.96
}
\end{invoicetable}

\begin{invoicetable}{r|llrr|r}{
runningnumber,
partnumber,
description,
qty,
sellprice,
linetotal
}
runningnumber=\#,
partnumber=Part number,
description=Description,
qty=Qty,
sellprice=Price,
linetotal=Total
}
\tline{
runningnumber=1,
partnumber=A-12232,
description={Test part, some data},
qty=4,
sellprice=14.99,
linetotal=59.96
}
\end{invoicetable}

\begin{invoicetable}{r}{
linetotal
}
runningnumber=\#,
partnumber=Part number,
description=Description,
qty=Qty,
sellprice=Price,
linetotal=Total
}
\tline{
runningnumber=1,
partnumber=A-12232,
description={Test part, some data},
qty=4,
sellprice=14.99,
linetotal=59.96
}
\end{invoicetable}

\end{document}


-
I am not sure I can count on LaTeX3 for this. How hard is it to get this to work with LaTeX-2E? – Chris Travers Feb 23 '13 at 12:06
@ChrisTravers You just need a fairly recent TeX distribution, which contains the expl3 bundle. – egreg Feb 23 '13 at 12:57