6

I'd like to build a tabular from a command with keys/values, values being themselves a set of keys/values (or a list).
If the key is not defined, no entry added in the tabular.

For now here is what I coded.

\documentclass{memoir}
\usepackage{xparse}

\ExplSyntaxOn
\keys_define:nn { evaluation }
{
    cc .tl_set:N = \l__evaluation_cc,
    cc .initial:n = 0,
    ds .tl_set:N = \l__evaluation_ds,
    ds .initial:n = 0,
    dm .tl_set:N = \l__evaluation_dm,
    dm .initial:n = 0,
    tp .tl_set:N = \l__evaluation_tp,
    tp .initial:n = 0,
    projet .tl_set:N = \l__evaluation_projet,
    projet .initial:n = 0,
}

\NewDocumentCommand{\Evaluation}{O{}}
{
    \group_begin:
    \keys_set:nn {evaluation}{#1}
    \begin{tabular}{*{4}{c}}
        Type & Number & Exam & home work\\
        CC & \\ % only if cc is a used key
        DS & \\ % only if ds is a used key
        $\vdots$\\
    \end{tabular}
    \group_end:
}
\ExplSyntaxOff

\begin{document}
    \Evaluation[cc={n=1;e=2;h=3}, projet={h=8}]
\end{document}
  • How to manage the two levels of key/value (or any other way to do that)?
  • How to test if the key is defined?

Many thanks!

6
  • If unknown keys should be ignored, you can just use \keys_set_known:nn instead of \keys_set:nn. You could handle second level keys either by setting up a subdirectory in the key tree and using the .meta:nn handler, or by using .code:n and nesting a \keys_set_known:nn inside that .code:n (roughly the same), or parse the nested list inside of .code:n with one of \prop_set_from_keyval:Nn or \keyval_parse:nnn.
    – Skillmon
    Commented Mar 25, 2021 at 23:31
  • Please note that your variables aren't named according to the convention. Since they are token lists, they should have a trailing _tl in their name, so it should be \l__evaluation_cc_tl for example.
    – Skillmon
    Commented Mar 26, 2021 at 0:11
  • I guess you also have variable entries in the first column: shouldn't them be the students' names? What about projet?
    – egreg
    Commented Mar 26, 2021 at 14:02
  • There will not be students in this table: only the evaluations for a teaching module, among DS (supervised assignment), CC (continuous assessment), TP (practical work), project, DM (home work)
    – NBur
    Commented Mar 26, 2021 at 20:26
  • 1
    @NBur nothing to apologize for, I made my comment so that you can learn not to mock you or anything!
    – Skillmon
    Commented Mar 27, 2021 at 9:58

3 Answers 3

3

Using expl3 and the l3keys module like in your question, you could use a prop for the parsing of the individual rows. Since except for \keyval_parse:nnn the key=value system in expl3 doesn't work fully expandable, we have to build a temporary prop for the key=value parsing.

We can check whether a key was used at all by setting a special marker as the default value, and then can test against that marker inside of \__nbur_evaluation_parse_row:nn.

\documentclass{memoir}

\usepackage{xparse}

\ExplSyntaxOn
\keys_define:nn { evaluation }
{
  cc .tl_set:N = \l__nbur_evaluation_cc_tl,
  cc .initial:n = \q_no_value,
  ds .tl_set:N = \l__nbur_evaluation_ds_tl,
  ds .initial:n = \q_no_value,
  project .tl_set:N = \l__nbur_evaluation_projet_tl,
  project .initial:n = 0,
}
\prop_new:N \l__nbur_evaluation_row_prop
\cs_new_protected:Npn \__nbur_evaluation_parse_row:nn #1 #2
  {
    \quark_if_no_value:nF {#2}
      {
        \prop_set_from_keyval:Nn \l__nbur_evaluation_row_prop {#2}
        \use:x
          {
            \exp_not:n {#1}
              & \prop_item:Nn \l__nbur_evaluation_row_prop { n }
              & \prop_item:Nn \l__nbur_evaluation_row_prop { e }
              & \prop_item:Nn \l__nbur_evaluation_row_prop { h }
          }
        \\
      }
  }
\cs_generate_variant:Nn \__nbur_evaluation_parse_row:nn { nV }

\NewDocumentCommand{\Evaluation}{O{}}
{
    \group_begin:
      \keys_set_known:nn {evaluation}{#1}
      \begin{tabular}{*{4}{c}}
          Type & Number & Exam & home work\\
          \__nbur_evaluation_parse_row:nV {CC} \l__nbur_evaluation_cc_tl
          \__nbur_evaluation_parse_row:nV {DS} \l__nbur_evaluation_ds_tl
          $\vdots$\\
      \end{tabular}
    \group_end:
}
\ExplSyntaxOff

\begin{document}
\Evaluation[cc={n=1,e=2,h=3,j=error}, projet={h=8}]
\end{document}

enter image description here

2
  • Great! Just one more thing: when calling \Evaliation only with cc and project, how not to display the other types of evaluations like DS or DM in the table? Is it possible to add a test on the first key level?
    – NBur
    Commented Mar 26, 2021 at 20:37
  • @NBur I made the edit to check whether keys at the first level were used.
    – Skillmon
    Commented Mar 27, 2021 at 10:08
4

If you want to split your key=value list into the rows of your table, one would expect a set of possible second level keys that are common for all your top level keys.

The following uses expkv with the two extensions expkv-def and expkv-cs for this. The top level is a typical key=value interface defined with \ekvdefinekeys. The data key will store the value and act as a Boolean switch at the same time. If the key was used, the defined macro will expand to its first argument followed by the value provided to the key. If the key wasn't used, the defined macro will expand to its second argument only. We can use this to provide the values to our macro parsing the second layer, which is called \evaluation@row in this example.

\evaluation@row is defined with \ekvcSplit, it'll accept the keys n, e, and h (all with an empty default value), and expand to #1&#2&#3, #1 being the first key defined in the previous list, #2 to the second, and so on. The neat thing which makes this practical for table cells is that expkv-cs's key=value interface remains fully expandable.

\documentclass{memoir}

\usepackage{xparse}
\usepackage{expkv-cs,expkv-def}

\makeatletter
% define the top level keys
\ekvdefinekeys{evaluation}
  {%
    data cc = \evaluation@cc,
    data ds = \evaluation@ds,
    store project = \evaluation@project
  }
\ekvsetdef\evaluation@kv{evaluation}
% define the keys at second level and split them into $n$ & $e$ & $h$
\ekvcSplit\evaluation@row{n=,e=,h=}{#1&#2&#3}
\NewDocumentCommand\Evaluation{O{}}
  {%
    \begingroup
      \evaluation@kv{#1}%
      \begin{tabular}{*{4}{c}}
        Type & Number & Exam & home work\\
        CC & \evaluation@cc\evaluation@row{&&} \\
        DS & \evaluation@ds\evaluation@row{&&} \\
        $\vdots$\\
      \end{tabular}
    \endgroup
  }
% silently ignore unknown keys in the top level
\ekvdefunknown{evaluation}{}
\ekvdefunknownNoVal{evaluation}{}
% silently ignore unknown keys in the second level
\ekvdefunknown{\string\evaluation@row}{}
\ekvdefunknownNoVal{\string\evaluation@row}{}
\makeatother

\begin{document}
\Evaluation[cc={n=1,e=2,h=3,j=error}, projet={h=8}]
\end{document}

enter image description here


With a few adjustments, we can use the possibilities of the data key type to also omit the unspecified rows in this variant. The necessary adjustments will be to use \ekvcSplitAndForward instead of \ekvcSplit, as the next step will have to grab an additional argument (the name of the row). The second argument of the data-key defined macros has to be \@gobble, which will just remove the name of the row.

\documentclass{memoir}

\usepackage{xparse}
\usepackage{expkv-cs,expkv-def}

\makeatletter
% define the top level keys
\ekvdefinekeys{evaluation}
  {%
    data cc = \evaluation@cc,
    data ds = \evaluation@ds,
    store project = \evaluation@project
  }
\ekvsetdef\evaluation@kv{evaluation}
% define the keys at second level and split them into arguments for
% `\evaluation@row@`. You can specify default values for `n`, `e`, and `h` after
% the equals signs (currently empty).
\ekvcSplitAndForward\evaluation@row\evaluation@row@{n=,e=,h=}
\newcommand\evaluation@row@[4]{#4&#1&#2&#3\\}
\NewDocumentCommand\Evaluation{O{}}
  {%
    \begingroup
      \evaluation@kv{#1}%
      \begin{tabular}{*{4}{c}}
        Type & Number & Exam & home work\\
        \evaluation@cc\evaluation@row\@gobble{CC}%
        \evaluation@ds\evaluation@row\@gobble{DS}%
        $\vdots$\\
      \end{tabular}
    \endgroup
  }
% ignore unknown keys in the top level
\ekvdefunknown{evaluation}{}
\ekvdefunknownNoVal{evaluation}{}
% ignore unknown keys in the second level
\ekvdefunknown{\string\evaluation@row}{}
\ekvdefunknownNoVal{\string\evaluation@row}{}
\makeatother

\begin{document}
\Evaluation[cc={n=1,e=2,h=3,j=error}, projet={h=8}]
\end{document}

enter image description here

4

With a change of syntax (no semicolons), you can evaluate the keys and store the given values in a property list.

The values will be printed only if the main key has been set, by checking whether the corresponding property list is empty or not. Values of n, e or h not appearing will be printed as 0.

\documentclass{memoir}
\usepackage{booktabs}
%\usepackage{xparse} % not needed for LaTeX 2020-10-01 or later

\ExplSyntaxOn
\prop_new:N \l__evaluation_cc_prop
\prop_new:N \l__evaluation_ds_prop
\prop_new:N \l__evaluation_dm_prop
\prop_new:N \l__evaluation_tp_prop
\prop_new:N \l__evaluation_projet_prop
\keys_define:nn { evaluation }
 {
  cc     .code:n = \prop_set_from_keyval:Nn \l__evaluation_cc_prop { #1 },
  ds     .code:n = \prop_set_from_keyval:Nn \l__evaluation_ds_prop { #1 },
  dm     .code:n = \prop_set_from_keyval:Nn \l__evaluation_dm_prop { #1 },
  tp     .code:n = \prop_set_from_keyval:Nn \l__evaluation_tp_prop { #1 },
  projet .code:n = \prop_set_from_keyval:Nn \l__evaluation_projet_prop { #1 },
 }

\NewDocumentCommand{\Evaluation}{m}
 {
  \group_begin:
  \keys_set:nn {evaluation}{#1}
  \begin{tabular}{*{4}{c}}
  \toprule
  Type & Number & Exam & Homework\\
  \midrule
  \__evaluation_table_row:n { cc }
  \__evaluation_table_row:n { ds }
  \__evaluation_table_row:n { dm }
  \__evaluation_table_row:n { tp }
  \__evaluation_table_row:n { projet }
  \bottomrule
  \end{tabular}
  \group_end:
}

\cs_new:Nn \__evaluation_table_row:n
 {
  \prop_if_empty:cF { l__evaluation_#1_prop }
   {
    \str_uppercase:n { #1 } &
    \__evaluation_item:nn { #1 } { n } &
    \__evaluation_item:nn { #1 } { e } &
    \__evaluation_item:nn { #1 } { h } \\
   }
 }
\cs_new:Nn \__evaluation_item:nn
 {
  \prop_if_in:cnTF { l__evaluation_#1_prop } { #2 }
   { \prop_item:cn { l__evaluation_#1_prop } { #2 } }
   { 0 }
 }

\ExplSyntaxOff

\begin{document}

\Evaluation{cc={n=1,e=2,h=3}, projet={h=8}}

\end{document}

enter image description here

The method can also be used in different contexts and for bigger property lists there can be performance issues with \prop_item:Nn. Following Skillmon's advice (see comments), here's an alternative implementation using the faster \prop_get:NnNTF function.

\documentclass{memoir}
\usepackage{booktabs}
%\usepackage{xparse} % not needed for LaTeX 2020-10-01 or later

\ExplSyntaxOn
\prop_new:N \l__evaluation_cc_prop
\prop_new:N \l__evaluation_ds_prop
\prop_new:N \l__evaluation_dm_prop
\prop_new:N \l__evaluation_tp_prop
\prop_new:N \l__evaluation_projet_prop
\keys_define:nn { evaluation }
 {
  cc     .code:n = \prop_set_from_keyval:Nn \l__evaluation_cc_prop { #1 },
  ds     .code:n = \prop_set_from_keyval:Nn \l__evaluation_ds_prop { #1 },
  dm     .code:n = \prop_set_from_keyval:Nn \l__evaluation_dm_prop { #1 },
  tp     .code:n = \prop_set_from_keyval:Nn \l__evaluation_tp_prop { #1 },
  projet .code:n = \prop_set_from_keyval:Nn \l__evaluation_projet_prop { #1 },
 }

\NewDocumentCommand{\Evaluation}{m}
 {
  \group_begin:
  \keys_set:nn {evaluation}{#1}
  \begin{tabular}{*{4}{c}}
  \toprule
  Type & Number & Exam & Homework\\
  \midrule
  \__evaluation_table_row:n { cc }
  \__evaluation_table_row:n { ds }
  \__evaluation_table_row:n { dm }
  \__evaluation_table_row:n { tp }
  \__evaluation_table_row:n { projet }
  \bottomrule
  \end{tabular}
  \group_end:
}

\tl_new:N \l__evaluation_item_tl

\cs_new_protected:Nn \__evaluation_table_row:n
 {
  \prop_if_empty:cF { l__evaluation_#1_prop }
   {
    \str_uppercase:n { #1 } &
    \__evaluation_item:nn { #1 } { n } &
    \__evaluation_item:nn { #1 } { e } &
    \__evaluation_item:nn { #1 } { h } \\
   }
 }
\cs_new_protected:Nn \__evaluation_item:nn
 {
  \prop_get:cnNTF { l__evaluation_#1_prop } { #2 } \l__evaluation_item_tl
   { \tl_use:N \l__evaluation_item_tl } % the key exists
   { 0 } % no such key stored
 }

\ExplSyntaxOff

\begin{document}

\Evaluation{cc={n=1,e=2,h=3}, projet={h=8}}

\end{document}
3
  • Performance wise, as you don't have to fetch the property list elements expandably in this variant, wouldn't it make more sense to use \prop_get:NnNTF in \__evaluation_item:nn?
    – Skillmon
    Commented Mar 27, 2021 at 10:22
  • @Skillmon Possibly, but I don't think it's so much a gain for such small cases. With a huge property list it would definitely be better.
    – egreg
    Commented Mar 27, 2021 at 10:37
  • @Skillmon Anyway, I added the alternative implementation. Thanks for pointing it out.
    – egreg
    Commented Mar 27, 2021 at 10:45

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .