1

Following up this answer and the ensuing discussion, \getRow is expected to check the existence and emptiness of the target row of a CSV data, then save that row columns to another macro.

In other words, the following code snippet of \getRow doesn't process any data of the target row after a successful check for the emptiness and existence of that row.

% star → global assignment, macro, key, label
\NewDocumentCommand \getRow { s m m m }
{
    \IfBooleanTF {#1}
    { \__diaa_get_row:NNnn \cs_gset_protected:Npx }
    { \__diaa_get_row:NNnn \cs_set_protected:Npx }
    #2 {#3} {#4}
}
\cs_new_protected:Npn \__diaa_get_row:NNnn #1 #2 #3 #4
{
    #1 #2 ##1 [ ##2 ]
    { \exp_not:N \msg_expandable_error:nnn { diaa } { improper-row } { \cs_to_str:N #2 } }
    \__diaa_csv_item_exist:nnNT {#4} {#3} \l__diaa_tmpa_tl
    {
        \bool_set_true:N \l__diaa_empty_item_bool
        \tl_map_inline:Nn \l__diaa_tmpa_tl
        {
            \tl_if_empty:nF {##1}
            { \bool_set_false:N \l__diaa_empty_item_bool }
        }
        \bool_if:NTF \l__diaa_empty_item_bool
        { \msg_warning:nnnn { diaa } { row-empty } {#3} {#4} }
        #1 #2 ##1 [ ##2 ]
        {
            \exp_not:N \__diaa_get_column:nnN
            { \exp_not:V \l__diaa_tmpa_tl } {##2} ##1
        }
    }
}

\cs_new_protected:Npn __diaa_get_column:nnN #1 #2 #3 { \str_if_eq:nnTF {#2} { non-empty } { __diaa_nb_nonempty_items_in_row:nn { 0 } #1 \q_recursion_tail \q_recursion_stop } { __diaa_check_column_range:nn {#1} {#2} } }

\cs_new:Npn __diaa_nb_nonempty_items_in_row:nn #1#2 { \quark_if_recursion_tail_stop_do:nn {#2} { \int_eval:n {#1} } \tl_if_empty:nTF {#2} { __diaa_nb_nonempty_items_in_row:nn {#1} } { __diaa_nb_nonempty_items_in_row:nn { #1 + 1 } } }

\msg_new:nnn { diaa } { csv-undefined } { CSV~database~#1'~undefined! } \msg_new:nnn { diaa } { key-undefined } { CSV~#2'~has~no~key~#1'! } \msg_new:nnn { diaa } { out-of-range } { Index~#1~out~of~range! } \msg_new:nnn { diaa } { item-empty } { Item~#1~from~#2'~in~CSV~#3'~is~empty! } \msg_new:nnn { diaa } { row-empty } { Row~#1'~in~CSV~`#2'~is~empty! } \msg_new:nnn { diaa } { empty-row-item } { Empty~item~#1~\msg_line_context:! } \msg_new:nnn { diaa } { improper-row } { Improper~row~macro~\iow_char:N \#1~\msg_line_context:.\ The~\iow_char:N \getRow~command~did~not~succeed. }

So, how to make \getRow process the row data after passing the checks for emptiness and existence for the following full MWE?

P.S. I included the full expl3 code since I don't know if there are dependencies somewhere or not


\begin{filecontents*}{test.csv}
    Third Parameter , 7 , 9 ,
    First Parameter , 5 , {foo, bar} ,
    Second Parameter , 3 , 6 , 44
    Empty Parameter , , ,
\end{filecontents*}

\documentclass{article} \usepackage{xparse,siunitx,xfp}

\ExplSyntaxOn % Step 1: reading the file \ior_new:N \l__diaa_csv_ior \bool_new:N \l__diaa_csv_str_bool \bool_new:N \l__diaa_empty_item_bool \seq_new:N \l__diaa_csv_tmp_seq \tl_new:N \l__diaa_tmpa_tl

% str mode (bool/star), key column, label, value columns, file \NewDocumentCommand \ReadCSV { s O{1} m O{} m } { \IfBooleanTF {#1} { \bool_set_true:N \l__diaa_csv_str_bool } { \bool_set_false:N \l__diaa_csv_str_bool } \diaa_csv_read:nnnn {#3} {#2} {#4} {#5} }

% label, key column, value columns, file \cs_new_protected:Npn \diaa_csv_read:nnnn #1 #2 #3 #4 { \tl_if_blank:nTF {#3} % Detect number of columns and use 2 to last { \ior_open:NnTF \l__diaa_csv_ior {#4} { \bool_if:NTF \l__diaa_csv_str_bool { \ior_str_get:NN } { \ior_get:NN } \l__diaa_csv_ior \l_tmpa_tl \ior_close:N \l__diaa_csv_ior \seq_set_split:NnV \l_tmpa_seq { , } \l_tmpa_tl \seq_clear:N \l__diaa_csv_tmp_seq \int_step_inline:nnn { 2 } { \seq_count:N \l_tmpa_seq } { \seq_put_right:Nn \l__diaa_csv_tmp_seq {##1} } } { \msg_expandable_error:nnn { diaa } { file-not-found } {#4} } } { \seq_set_split:Nnn \l__diaa_csv_tmp_seq { , } {#3} } % explicit columns \ior_open:NnTF \l__diaa_csv_ior {#4} { \prop_new:c { g__diaa_csv_#1_prop } __diaa_csv_read:nn {#1} {#2} \ior_close:N \l__diaa_csv_ior } { \msg_expandable_error:nnn { diaa } { file-not-found } {#4} } }

\msg_new:nnn { diaa } { file-not-found } { File~`#1'~not~found. }

\cs_generate_variant:Nn \prop_put:Nnn { cxV }

% label, key column \cs_new_protected:Npn __diaa_csv_read:nn #1 #2 { \bool_if:NTF \l__diaa_csv_str_bool { \ior_str_map_inline:Nn } { \ior_map_inline:Nn } \l__diaa_csv_ior { \seq_set_split:Nnn \l_tmpa_seq { , } {##1} % split one CSV row \tl_clear:N \l_tmpa_tl \seq_map_inline:Nn \l__diaa_csv_tmp_seq { \tl_put_right:Nx \l_tmpa_tl { { \seq_item:Nn \l_tmpa_seq {####1} } } } \prop_put:cxV { g__diaa_csv_#1_prop } { \seq_item:Nn \l_tmpa_seq {#2} } \l_tmpa_tl } }

% Step 2: getting the values % star → global assignment, macro or tl var, value column, key, label \NewDocumentCommand \getValue { s m O{1} m m } { \tl_clear:N \l__diaa_tmpa_tl \diaa_csv_item:nnnN {#4} {#3} {#5} \l__diaa_tmpa_tl \IfBooleanTF {#1} { \tl_gset_eq:NN } { \tl_set_eq:NN } #2 \l__diaa_tmpa_tl \tl_if_empty:NT #2 { \msg_warning:nnnnn { diaa } { item-empty } {#3} {#4} {#5} } }

% key, value column, label \cs_new_protected:Npn \diaa_csv_item:nnnN #1 #2 #3 #4 { __diaa_csv_item_exist:nnNT {#3} {#1} #4 { \exp_args:NV __diaa_check_column_range:nn #4 {#2} } } \cs_new_protected:Npn __diaa_check_column_range:nn #1 #2 { \bool_lazy_or:nnTF { \int_compare_p:nNn {#2} = { 0 } } { \int_compare_p:nNn { \tl_count:n {#1} } < { \int_abs:n {#2} } } { \msg_expandable_error:nnn { diaa } { out-of-range } {#2} } { \tl_set:Nx \l__diaa_tmpa_tl { \tl_item:nn {#1} {#2} } } }

\prg_new_protected_conditional:Npnn __diaa_csv_item_exist:nnN #1 #2 #3 { T } { \prop_if_exist:cTF { g__diaa_csv_#1_prop } { \prop_get:cnNTF { g__diaa_csv_#1_prop } {#2} #3 { \prg_return_true: } { \msg_expandable_error:nnnn { diaa } { key-undefined } {#2} {#1} \prg_return_false: } } { \msg_expandable_error:nnn { diaa } { csv-undefined } {#1} \prg_return_false: } }

\cs_new_protected:Npn __diaa_check_empty:nn #1 #2 { \tl_if_empty:nT {#1} { \msg_warning:nnn { diaa } { empty-row-item } {#2} } #1 }

% star → global assignment, macro, key, label \NewDocumentCommand \getRow { s m m m } { \IfBooleanTF {#1} { __diaa_get_row:NNnn \cs_gset_protected:Npx } { __diaa_get_row:NNnn \cs_set_protected:Npx } #2 {#3} {#4} } \cs_new_protected:Npn __diaa_get_row:NNnn #1 #2 #3 #4 { #1 #2 ##1 [ ##2 ] { \exp_not:N \msg_expandable_error:nnn { diaa } { improper-row } { \cs_to_str:N #2 } } __diaa_csv_item_exist:nnNT {#4} {#3} \l__diaa_tmpa_tl { \bool_set_true:N \l__diaa_empty_item_bool \tl_map_inline:Nn \l__diaa_tmpa_tl { \tl_if_empty:nF {##1} { \bool_set_false:N \l__diaa_empty_item_bool } } \bool_if:NTF \l__diaa_empty_item_bool { \msg_warning:nnnn { diaa } { row-empty } {#3} {#4} } #1 #2 ##1 [ ##2 ] { \exp_not:N __diaa_get_column:nnN { \exp_not:V \l__diaa_tmpa_tl } {##2} ##1 } } }

\cs_new_protected:Npn __diaa_get_column:nnN #1 #2 #3 { \str_if_eq:nnTF {#2} { non-empty } { __diaa_nb_nonempty_items_in_row:nn { 0 } #1 \q_recursion_tail \q_recursion_stop } { __diaa_check_column_range:nn {#1} {#2} } }

\cs_new:Npn __diaa_nb_nonempty_items_in_row:nn #1#2 { \quark_if_recursion_tail_stop_do:nn {#2} { \int_eval:n {#1} } \tl_if_empty:nTF {#2} { __diaa_nb_nonempty_items_in_row:nn {#1} } { __diaa_nb_nonempty_items_in_row:nn { #1 + 1 } } }

\msg_new:nnn { diaa } { csv-undefined } { CSV~database~#1'~undefined! } \msg_new:nnn { diaa } { key-undefined } { CSV~#2'~has~no~key~#1'! } \msg_new:nnn { diaa } { out-of-range } { Index~#1~out~of~range! } \msg_new:nnn { diaa } { item-empty } { Item~#1~from~#2'~in~CSV~#3'~is~empty! } \msg_new:nnn { diaa } { row-empty } { Row~#1'~in~CSV~`#2'~is~empty! } \msg_new:nnn { diaa } { empty-row-item } { Empty~item~#1~\msg_line_context:! } \msg_new:nnn { diaa } { improper-row } { Improper~row~macro~\iow_char:N \#1~\msg_line_context:.\ The~\iow_char:N \getRow~command~did~not~succeed. } \ExplSyntaxOff

\parindent0pt

\begin{document}

\ReadCSV{mydata}{test.csv}

\getRow\myRow{Second Parameter}{mydata}
\num{\myRow[2]}\\
\num{\myRow[1]}\\
\edef\Value{\fpeval{\myRow[2]-\myRow[1]}}
\num[\Value]

\end{document}

Diaa
  • 9,599
  • 1
    why did you destroy all the nice indentations in your full example? It makes it quite hard to read the code. – Ulrike Fischer Dec 26 '20 at 17:22
  • @UlrikeFischer I overlooked it. Please, check my updated one. – Diaa Dec 26 '20 at 17:25
  • 1
    @Diaa In the answer your question links to, Phelype changed the way \getRow works in order to satisfy your requirements regarding error messages. I don't have the time to read these changes, but it is very clear that you are using the old syntax in your example here. The syntax in the answer you linked to requires three steps every time: 1) Using \getRow\somemacro{<row ID>}{<CSV ID>}. 2) Calling \somemacro\othermacro[<index>]. 3) Using \othermacro (step 1 assigns to \somemacro, step 2 to \othermacro). – frougon Dec 26 '20 at 20:28
  • @frougon In my question, I am using the last code suggested by Phelype here https://chat.stackexchange.com/transcript/message/56517000#56517000 to format the error messages in a particular way. I updated my question to include it. – Diaa Dec 26 '20 at 20:33
  • @Diaa What I'm saying is that \getRow\myRow{Second Parameter}{mydata} followed by \myRow[2] does not conform to\getRow's usage as explained at the beginning of Phelype's answer, which your question refers to. See how \<row macro> is used in Phelype's answer. – frougon Dec 26 '20 at 20:39
  • @frougon I am feeling my silliness right now. I think this question will be deleted in a couple of minutes :/ – Diaa Dec 26 '20 at 20:44
  • Don't be too harsh to yourself, take care. :-) – frougon Dec 26 '20 at 20:45
  • @frougon In case you are still around, am I doing it wrong: \getRow\myValue{Second Parameter}{mydata} \myValue\myValuee[2]? I followed the same syntax and no output. – Diaa Dec 26 '20 at 20:49
  • 1
    Then you need to use \myValuee. This is the macro containing the result. Reread my 3 steps above... – frougon Dec 26 '20 at 20:50
  • @frougon After considering your clear steps, I got the error of Undefined control sequence.l.202 \myValue for the following: \begin{document} \ReadCSV{mydata}{test.csv} \getRow\myRow{Second Parameter}{mydata} \myRow\myValue[2]; \myValue \end{document} – Diaa Dec 26 '20 at 20:57
  • I'm afraid this is due to a bug in the answer your code is based on. \__diaa_get_column:nnN never assigns to its third argument. If you insert \tl_set_eq:NN #3 \l__diaa_tmpa_tl after \__diaa_check_column_range:nn {#1} {#2} in the definition of \__diaa_get_column:nnN, it works for this example. – frougon Dec 26 '20 at 21:12
  • The non-empty case would also need a similar fix, or else to be used in a clunky way: \getRow\myRow{Second Parameter}{mydata} followed by \myRow\unused[non-empty] where \unused is, well, unused). – frougon Dec 26 '20 at 21:17
  • @frougon I know you are busy but I would like to know is it possible to make \getRow work as in your old code so that I can keep the current error/warning messages of this answer, but also use it in a straightforward way like \num{\myRow[2]} in stead of the extra steps in addition to losing the compactness of using the values in the list without having to extract every single one? – Diaa Dec 26 '20 at 21:23
  • In order to fix what I just mentioned to keep the API non-clunky, I suggest \cs_new_protected:Npn \__diaa_get_column:nnN #1 #2 #3 { \str_if_eq:nnTF {#2} { non-empty } { \tl_set:Nx #3 { \__diaa_nb_nonempty_items_in_row:nn { 0 } #1 \q_recursion_tail \q_recursion_stop } } { \__diaa_check_column_range:nn {#1} {#2} \tl_set_eq:NN #3 \l__diaa_tmpa_tl } }. To reply to your last question: sorry, but Phelype changed the API to do something you asked regarding the error messages. – frougon Dec 26 '20 at 21:25
  • The old, simpler API worked in expansion-only contexts but some things can't be done there, like those you asked regarding error messages. You can use the final result (step 3) of Phelype's API inside \num{...}, but not intermediate results (from steps 1 and 2). – frougon Dec 26 '20 at 21:26
  • @frougon I appreciate your consideration, effort and time you put in this discussion. I have to choose which approach is more needed than the other. The choice is mine. :) – Diaa Dec 26 '20 at 21:31

1 Answers1

2

Here is a fix for \__diaa_get_column:nnN (which didn't assign to its third argument with the original code), along with an example using the results inside siunitx's \num macro.

For your convenience, I also added a \getNbNonEmpty macro to find the number of non-empty items in a row and store it in a macro:

\getNbNonEmpty [*] \⟨result⟩ {⟨key⟩} {⟨CSV file label⟩}

This macro checks if the ⟨CSV file label⟩ is valid and if the ⟨key⟩ identifies an existing row. If not, you'll get the same error messages as for \getRow. However, \getNbNonEmpty doesn't warn if the row is empty because I believe this wouldn't be very helpful here; this would be easy to change if you wish, though. As for \getValue and \getRow, the star form performs a global assignment to \⟨result⟩.

% Based on Phelype Oleinik's answer at
% <https://tex.stackexchange.com/a/575687/73317>
\begin{filecontents*}{test.csv}
Third Parameter  , 7 , 9          ,
First Parameter  , 5 , {foo, bar} ,
Second Parameter , 3 , 6          , 44
Empty Parameter  ,   ,            ,
\end{filecontents*}

\documentclass{article} \usepackage{xparse} \usepackage{siunitx} % for the test using \num

\ExplSyntaxOn % Step 1: reading the file \ior_new:N \l__diaa_csv_ior \bool_new:N \l__diaa_csv_str_bool \bool_new:N \l__diaa_empty_item_bool \seq_new:N \l__diaa_csv_tmp_seq \tl_new:N \l__diaa_tmpa_tl

% str mode (bool/star), key column, label, value columns, file \NewDocumentCommand \ReadCSV { s O{1} m O{} m } { \IfBooleanTF {#1} { \bool_set_true:N \l__diaa_csv_str_bool } { \bool_set_false:N \l__diaa_csv_str_bool } \diaa_csv_read:nnnn {#3} {#2} {#4} {#5} }

% label, key column, value columns, file \cs_new_protected:Npn \diaa_csv_read:nnnn #1 #2 #3 #4 { \tl_if_blank:nTF {#3} % Detect number of columns and use 2 to last { \ior_open:NnTF \l__diaa_csv_ior {#4} { \bool_if:NTF \l__diaa_csv_str_bool { \ior_str_get:NN } { \ior_get:NN } \l__diaa_csv_ior \l_tmpa_tl \ior_close:N \l__diaa_csv_ior \seq_set_split:NnV \l_tmpa_seq { , } \l_tmpa_tl \seq_clear:N \l__diaa_csv_tmp_seq \int_step_inline:nnn { 2 } { \seq_count:N \l_tmpa_seq } { \seq_put_right:Nn \l__diaa_csv_tmp_seq {##1} } } { \msg_error:nnn { diaa } { file-not-found } {#4} } } { \seq_set_split:Nnn \l__diaa_csv_tmp_seq { , } {#3} } % explicit columns \ior_open:NnTF \l__diaa_csv_ior {#4} { \prop_new:c { g__diaa_csv_#1_prop } __diaa_csv_read:nn {#1} {#2} \ior_close:N \l__diaa_csv_ior } { \msg_error:nnn { diaa } { file-not-found } {#4} } }

\msg_new:nnn { diaa } { file-not-found } { File~`#1'~not~found. }

\cs_generate_variant:Nn \prop_put:Nnn { cxV }

% label, key column \cs_new_protected:Npn __diaa_csv_read:nn #1 #2 { \bool_if:NTF \l__diaa_csv_str_bool { \ior_str_map_inline:Nn } { \ior_map_inline:Nn } \l__diaa_csv_ior { \seq_set_split:Nnn \l_tmpa_seq { , } {##1} % split one CSV row \tl_clear:N \l_tmpa_tl \seq_map_inline:Nn \l__diaa_csv_tmp_seq { \tl_put_right:Nx \l_tmpa_tl { { \seq_item:Nn \l_tmpa_seq {####1} } } } \prop_put:cxV { g__diaa_csv_#1_prop } { \seq_item:Nn \l_tmpa_seq {#2} } \l_tmpa_tl } }

% Step 2: getting the values % star → global assignment, macro or tl var, value column, key, label \NewDocumentCommand \getValue { s m O{1} m m } { \tl_clear:N \l__diaa_tmpa_tl \diaa_csv_item:nnnN {#4} {#3} {#5} \l__diaa_tmpa_tl \IfBooleanTF {#1} { \tl_gset_eq:NN } { \tl_set_eq:NN } #2 \l__diaa_tmpa_tl \tl_if_empty:NT #2 { \msg_warning:nnnnn { diaa } { item-empty } {#3} {#4} {#5} } }

% key, value column, label \cs_new_protected:Npn \diaa_csv_item:nnnN #1 #2 #3 #4 { __diaa_csv_item_exist:nnNT {#3} {#1} #4 { \exp_args:NV __diaa_check_column_range:nn #4 {#2} } } \cs_new_protected:Npn __diaa_check_column_range:nn #1 #2 { \bool_lazy_or:nnTF { \int_compare_p:nNn {#2} = { 0 } } { \int_compare_p:nNn { \tl_count:n {#1} } < { \int_abs:n {#2} } } { \msg_error:nnn { diaa } { out-of-range } {#2} } { \tl_set:Nx \l__diaa_tmpa_tl { \tl_item:nn {#1} {#2} } } }

\prg_new_protected_conditional:Npnn __diaa_csv_item_exist:nnN #1 #2 #3 { T } { \prop_if_exist:cTF { g__diaa_csv_#1_prop } { \prop_get:cnNTF { g__diaa_csv_#1_prop } {#2} #3 { \prg_return_true: } { \msg_error:nnnn { diaa } { key-undefined } {#2} {#1} \prg_return_false: } } { \msg_error:nnn { diaa } { csv-undefined } {#1} \prg_return_false: } }

\cs_new_protected:Npn __diaa_check_empty:nn #1 #2 { \tl_if_empty:nT {#1} { \msg_warning:nnn { diaa } { empty-row-item } {#2} } #1 }

% star → global assignment, macro, key, label \NewDocumentCommand \getRow { s m m m } { \IfBooleanTF {#1} { __diaa_get_row:NNnn \cs_gset_protected:Npx } { __diaa_get_row:NNnn \cs_set_protected:Npx } #2 {#3} {#4} }

% #1: \cs_set_protected:Npx or \cs_gset_protected:Npx % #2: macro for the result % #3: key % #4: label \cs_new_protected:Npn __diaa_get_row:NNnn #1 #2 #3 #4 { #1 #2 ##1 [ ##2 ] { \msg_error:nnx { diaa } { improper-row } { \cs_to_str:N #2 } } __diaa_csv_item_exist:nnNT {#4} {#3} \l__diaa_tmpa_tl { \bool_set_true:N \l__diaa_empty_item_bool \tl_map_inline:Nn \l__diaa_tmpa_tl { \tl_if_empty:nF {##1} { \bool_set_false:N \l__diaa_empty_item_bool } } \bool_if:NT \l__diaa_empty_item_bool { \msg_warning:nnnn { diaa } { row-empty } {#3} {#4} } #1 #2 ##1 [ ##2 ] { \exp_not:N __diaa_get_column:nnN { \exp_not:V \l__diaa_tmpa_tl } {##2} ##1 } } }

% #1: token list {1st item}{2nd item} ... {nth item} % #2: index (integer expression) or 'non-empty' % #3: macro for the result \cs_new_protected:Npn __diaa_get_column:nnN #1 #2 #3 { \str_if_eq:nnTF {#2} { non-empty } { \tl_set:Nx #3 { __diaa_nb_nonempty_items_in_row:nw { 0 } #1 \q_recursion_tail \q_recursion_stop } } { __diaa_check_column_range:nn {#1} {#2} \tl_set_eq:NN #3 \l__diaa_tmpa_tl } }

\cs_new:Npn __diaa_nb_nonempty_items_in_row:nw #1#2 { \quark_if_recursion_tail_stop_do:nn {#2} { \int_eval:n {#1} } \tl_if_empty:nTF {#2} { __diaa_nb_nonempty_items_in_row:nw {#1} } { __diaa_nb_nonempty_items_in_row:nw { #1 + 1 } } }

% star → global assignment, macro, key, label \NewDocumentCommand \getNbNonEmpty { s m m m } { \IfBooleanTF {#1} { __diaa_get_nb_nonempty_items:NNnn \cs_gset_nopar:Npx } { __diaa_get_nb_nonempty_items:NNnn \cs_set_nopar:Npx } #2 {#3} {#4} }

% #1: \cs_set_nopar:Npx or \cs_gset_nopar:Npx % #2: macro % #3: key % #4: label \cs_new_protected:Npn __diaa_get_nb_nonempty_items:NNnn #1 #2 #3 #4 { #1 #2 { \msg_error:nnx { diaa } { improper-non-empty-macro } { \cs_to_str:N #2 } }

\__diaa_csv_item_exist:nnNT {#4} {#3} \l__diaa_tmpa_tl
  {
    #1 #2
      {
        \exp_last_unbraced:Nno \__diaa_nb_nonempty_items_in_row:nw { 0 }
          \l__diaa_tmpa_tl \q_recursion_tail \q_recursion_stop
      }
  }

}

\msg_new:nnn { diaa } { csv-undefined } { CSV~database~#1'~undefined! } \msg_new:nnn { diaa } { key-undefined } { CSV~#2'~has~no~key~#1'! } \msg_new:nnn { diaa } { out-of-range } { Index~#1~out~of~range! } \msg_new:nnn { diaa } { item-empty } { Item~#1~from~#2'~in~CSV~#3'~is~empty! } \msg_new:nnn { diaa } { row-empty } { Row~#1'~in~CSV~`#2'~is~empty! } \msg_new:nnn { diaa } { empty-row-item } { Empty~item~#1~\msg_line_context:! } \msg_new:nnn { diaa } { improper-row } { Improper~row~macro~\iow_char:N \#1~\msg_line_context:.\ The~\iow_char:N \getRow~command~did~not~succeed. } \msg_new:nnn { diaa } { improper-non-empty-macro } { Improper~non-empty~macro~\iow_char:N \#1~\msg_line_context:.\ The~\iow_char:N \getNbNonEmpty~command~did~not~succeed. } \ExplSyntaxOff

\parindent0pt

\begin{document}

\ReadCSV{mydata}{test.csv}

% Get the second item of the row identified by Second Parameter \getRow\myRow{Second Parameter}{mydata} \myRow\myValue[2] % Using the result: \num{\myValue}

% Get the number of non-empty items in the same row \myRow\nbNonEmpty[non-empty] % Using the result: \num{\nbNonEmpty}

% Same thing in two steps (no need for the \getRow here) \getNbNonEmpty\nbNonEmpty{Second Parameter}{mydata} % Using the result: \num{\nbNonEmpty}

% Ditto for the row identified by 'Third Parameter': \getNbNonEmpty\nbNonEmpty{Third Parameter}{mydata} % \show\nbNonEmpty % \nbNonEmpty=macro:->2.

\end{document}

enter image description here

frougon
  • 24,283
  • 1
  • 32
  • 55
  • Thanks for your priceless help. Have a nice night :) – Diaa Dec 26 '20 at 21:42
  • Glad to hear it helped. Good night to you too. :-) – frougon Dec 26 '20 at 21:42
  • I have some thought but I would like to check its validity first. Suppose I have two commands for the row operation: The first one (e.g. \checkRow) checks its emptiness and existence. The second one (\getRow) works like your old code. I put the two commands after each other in single third command. So, the third command (e.g. \getcheckedRow) will work exactly like your old \getRow but with checks before parsing the data. – Diaa Dec 26 '20 at 22:28
  • Does something like this make sense and is doable or is it non-orthodox approach? – Diaa Dec 26 '20 at 22:29
  • With my code, after \getRow\rowmacro{key}{label}, it was possible to use \rowmacro[index] in an expansion-only context (such as inside the argument of \num). But you asked Phelype to have \rowmacro[index] print a warning in some cases (as opposed to an error), and this can't be done in expansion-only contexts. You need to choose... – frougon Dec 26 '20 at 23:09
  • Your code is working flawlessly, but I am questioning the validity of having something like \NewDocumentCommand \getCheckedRow { s m m m ) { \checkRow { #1 #2 #3 #4 } % the code of Phylepe to be executed by this \getRow { #1 #2 #3 #4 } % your code is executed by this } – Diaa Dec 26 '20 at 23:16
  • So, 1- the four arguments of the parent command \getCheckedRow will be populated to the two children commands \checkRow and \getRow. 2- the execution of the parent command means the first child command will be executed and after its successful execution, the second child one will be executed. – Diaa Dec 26 '20 at 23:17
  • The indexing must be done outside expansion-only contexts if you want it to be able to trigger warnings. I've added a second code with a \getCheckedItem command that might do what you want (note that it can't be used in expansion-only contexts—but the macro you give it as first argument for storing the result can). – frougon Dec 26 '20 at 23:39
  • Many thanks for your patience. My simple eyes :) tell me that your newly added command works exactly like \getValue of Phylepe, since his also checks before assigning values. and can work in expansion-only context. \ReadCSV{mydata}{test.csv} \getCheckedItem\myValue{First Parameter}{mydata}[1] \num{\myValue} \getValue\myValue[1]{First Parameter}{mydata} \num{\myValue} – Diaa Dec 27 '20 at 00:04
  • Yes, I think you're right, I just forgot about this \getValue. :) \getValue is a bit more powerful than \getCheckedItem because the star form allows it to perform a global assignment. So, I'll remove the second example. You can find it in the history if you want. – frougon Dec 27 '20 at 00:17
  • I renamed the \getRow of Phylepe to \checkRow and added your old definition of \getRow. My naivety told me that \NewDocumentCommand \getCheckedRow { s m m m } { \checkRow{#1}{#2}{#3}{#4} \getRow{#1}{#2}{#3}{#4} } should work but it doesn't :D – Diaa Dec 27 '20 at 00:17
  • I don't know why most of the people here don't like answering my questions of this kind XD. It is not my fault to not have more tikz questions to draw enough attention :) – Diaa Dec 27 '20 at 00:26
  • 1
    Hello, Diaa. For your convenience, I added a \getNbNonEmpty macro to find the number of non-empty items in a row and store it in a macro. This performs the checks but spares you one step as compared to doing the same using \getRow (this is possible because one doesn't need to index into the row when looking for the number of non-empty items in a row). Please see the latest edit for details. – frougon Dec 27 '20 at 11:37
  • Many thanks for your help. I will experiment a bit then ask a new question about nesting commands. Have a nice day :) – Diaa Dec 27 '20 at 14:42
  • In case you are in a good mood :) I have something weird I couldn't understand: in the old first method of Phylepe here, \getRow is allegedly expandable which can be tested by \getRow\myRow{Second Parameter}{mydata} \myRow[2]. However, when using \num{\myRow[2]}, siunitx complains about the non-numeric input and exits with error. How is this even possible? – Diaa Dec 27 '20 at 18:17
  • 1
    Err... you linked to the (TTBOMK) (n-1)th version of the code where \getRow writes to a “row macro” which is not expandable. See the sentence “The previous code was expandable, so (...) but now that it may throw a (non-expandable) error (...).” This is because Phelype was aksed to output warnings when indexing in the row finds certain things. Your test should work with my earlier version of the code. – frougon Dec 27 '20 at 18:26
  • If you still use code from this question, take the latest edit because I just fixed a bug that was in the question code... – frougon Feb 23 '22 at 14:54
  • If it wouldn't bother you, could you please give me an approval that the whole compiled code (here https://pastebin.com/V5LtXd6w) from many of your and Phelype answers are perfectly compatible without any possible sinkholes? – Diaa Feb 23 '22 at 21:05
  • I'm sorry, I did what I could to help in the previous days because of the notification (which is a trap) and my great reluctance of leaving someone without help, but now I can't do more—especially a long review of code, some of which is not mine. I've already done more than I should have, and am paying now. Doctors didn't do their work in more than 20 years, and now I can't help other people as I've always done as long as my health allowed it. The code you posted has indentation completely changed for my parts, and other parts are not from me. – frougon Feb 24 '22 at 07:54
  • In particular, using \msg_expandable_error:nnn as it is done in the big \getRow (not mine) doesn't make sense. \msg_error:nnn would be more advised. That being said, it is quite possible that the whole works reasonably well; I simply can't afford to read it carefully as would be required to have some decent level of confidence. Sorry. – frougon Feb 24 '22 at 07:55
  • If you want not to be dependant anymore on other people, read the TeXbook, practice, then read the expl3 docs and practice at the same time. That is my best long term advice; that's how I learnt. interface3.pdf can be read in an “as needed” way—no need to read it all in one go. – frougon Feb 24 '22 at 08:03
  • I sincerely apologize for that much disturbance. Kind regards! – Diaa Feb 24 '22 at 11:37