2

Following up this great answer of reading a CSV file and saving it, I need to have the following:

  • A compilation-halting error is required when I try to index into a non-existent row or column since now it undergoes endless compilation when this happens.

  • I would like to get a warning in my latex editor when the macro handled by \getValue has an empty value (i.e. the accessed cell in the CSV file is empty).

  • I would like both warning and error show the respective file name and code line when build option -file-line-error is enabled.

  • The same conditions should apply to \getRow by having a warning when retrieving a totally empty row, and a compilation-halting error when trying to access non-existent CSV data or row (e.g. Parameter here).

enter image description here

\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}

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

% 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 } { \IfBooleanTF {#1} { \tl_gset:Nx } { \tl_set:Nx } #2 { \diaa_csv_item:nnn {#4} {#3} {#5} } }

% key, value column, label \NewExpandableDocumentCommand \CSVItem { m O{1} m } { \diaa_csv_item:nnn {#1} {#2} {#3} }

\cs_generate_variant:Nn \tl_item:nn { f }

% key, value column, label \cs_new:Npn \diaa_csv_item:nnn #1 #2 #3 { \tl_item:fn { \prop_item:cn { g__diaa_csv_#3_prop } {#1} } {#2} }

% star → global assignment, macro, key, label \NewDocumentCommand \getRow { s m m m } { \prop_get:cnN { g__diaa_csv_#4_prop } {#3} \l_tmpa_tl \IfBooleanTF {#1} { \cs_gset_nopar:Npx } { \cs_set_nopar:Npx } #2 [ ##1 ] { \exp_not:N \str_if_eq:nnTF {##1} { non-empty } { \exp_not:N __diaa_nb_nonempty_items_in_row:nn { 0 } \exp_not:V \l_tmpa_tl \exp_not:n { \q_recursion_tail \q_recursion_stop } } { \exp_not:N \tl_item:nn { \exp_not:V \l_tmpa_tl } {##1} } } }

\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} } } \ExplSyntaxOff

\parindent0pt

\begin{document}

\ReadCSV{mydata}{test.csv}

\verb|\getValue\myValue{Second Parameter}{NOTmydata}|\ A \textbf{compilation-halting error} should show up stating that "NOTmydata" is undefined CSV data.\

\verb|\getValue\myValue{Non-existent Parameter}{mydata}|\ A \textbf{compilation-halting error} should show up stating that "Non-existent Parameter" is undefined CSV parameter.\

\verb|\getValue\myValue{Empty Parameter}{mydata}|\ A \textbf{Warning} should show up stating that the value stored in \verb|\myValue| is empty.

\end{document}

Diaa
  • 9,599
  • The warning is tricky because \diaa_csv_item:nnn is fully expandable, and you can't have expandable warnings (only errors). You can have the warning for \getValue but not for \CSVItem. – Phelype Oleinik Dec 19 '20 at 19:48
  • @PhelypeOleinik I only need the warning/error for both \getValue and \getRow (Sorry for not mentioning it in the question). I don't use \CSVItem. – Diaa Dec 19 '20 at 21:42

1 Answers1

5

Since you need warnings and error messages formatted the same way, and you don't need expandability, the code can be rewritten non-expandably, which allows you to use \msg_error:nn(nnnn) always (rather than \msg_expandable_error:nn(nnnn)). (the old answer, with expandable \diaa_csv_item:nnn is below, for reference.)

I changed a bit the syntax of the output of \getRow. When you do

\getRow [*] \<row macro> {<row ID>} {<CSV ID>}

the \<row macro> is defined with the syntax:

\<row macro> \<return> [<col number>]

such that \<row macro> saves the item from row <row ID>, column <col number>, from the <CSV ID> into \<return>. The previous code was expandable, so \<row macro> [<col number>] would expand directly to the return value, but now that it may throw a (non-expandable) error, the \<row macro> is protected and returns the item in \<return>, which can then be used.

All the errors and warnings you requested should be there. If the CSV database was not defined, the error csv-undefined is raised. If the CSV exists, but does not have the requested key, key-undefined is raised. Both these errors are checked in \__diaa_csv_item_exist:nnN(T). These two tests are common for \getValue and \getRow.

With \getValue, if the column requested is zero or larger than the number of columns in the CSV, error out-of-range is raised. Then, if a valid item is requested, it is checked for being empty. If it is, the warning item-empty is issued.

With \getRow, if all items in a row are empty, the row-empty warning is issued, and the \<row macro> is defined to raise the improper-row error if used. Otherwise, \<row macro> is defined such that it does the same checks specific to \getValue: checks if the column number is within range, then checks if the item returned is not empty.

Here's the code:

\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}

\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} } \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 } } }

\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}

\verb|\getValue\myValue{Second Parameter}{NOTmydata}|\ A \textbf{compilation-halting error} should show up stating that "NOTmydata" is undefined CSV data.\ \getValue\myValue{Second Parameter}{NOTmydata}\ \getRow\myValue{Second Parameter}{NOTmydata}; \myValue\myValuee[1]\

\verb|\getValue\myValue{Non-existent Parameter}{mydata}|\ A \textbf{compilation-halting error} should show up stating that "Non-existent Parameter" is undefined CSV parameter.\ \getValue\myValue{Non-existent Parameter}{mydata}\ \getRow\myValue{Non-existent Parameter}{mydata}; \myValue\myValuee[1]\

\verb|\getValue\myValue[4]{Second Parameter}{mydata}|\ An \textbf{error} because index 4 is out of range.\ \getValue\myValue[4]{Second Parameter}{mydata}\ \getRow\myValue{Second Parameter}{mydata}; \myValue\myValuee[4]\

\verb|\getValue\myValue{Empty Parameter}{mydata}|\ A \textbf{Warning} should show up stating that the value stored in \verb|\myValue| is empty. \getValue\myValue{Empty Parameter}{mydata}\ \getRow\myValue{Empty Parameter}{mydata}; \myValue\myValuee[1]\

\end{document}


Previous answer, for reference:

The function \diaa_csv_item:nnn that fetches data from the stored CSV is expandable, so you can't have warnings, only expandable errors, which are themselves very limited. An expandable error will have the form:

! Undefined control sequence.
<argument> \::error 
                    ! diaa: CSV database `NOTmydata' undefined!
l.161 ...alue\myValue{Second Parameter}{NOTmydata}

that is, a low-level Undefined control sequence error from TeX (on \::error being the undefined control sequence), and one line of text following it. The message can't be too long or TeX will truncate the message.

I implemented the warning as an after-the-fact check on the return value of \getValue. Since that function is not expandable, you can have a warning. \CSVItem can't have a warning: either an expandable error or nothing. I left a version that errors with an empty item commented in the code.

Note that in the case of checking if the element of a retrieved row (that is, using \getRow\myRow... then \myRow[<index>] returning an empty value), the macro \__diaa_check_empty:nn is used. This macro is not protected, so it will expand in an expansion-only context, however it is not fully expandable because it contains a \msg_warning:nnn (and as I said before, you can't have an expandable warning), so it might break unexpectedly in expansion contexts if the retrieved item is empty. If it is not, everything works normally.

Other than that, I used \prop_if_exist:cTF to check if the CSV was defined, \prop_if_in:cnTF to check if the requested item exists in the CSV, and \tl_count:n to check if the column requested is within the range.

\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}

\ExplSyntaxOn % Step 1: reading the file \tl_new:N \l__diaa_tmpa_tl \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

% 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__diaa_tmpa_tl

        \ior_close:N \l__diaa_csv_ior
        \seq_set_split:NnV \l_tmpa_seq { , } \l__diaa_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__diaa_tmpa_tl \seq_map_inline:Nn \l__diaa_csv_tmp_seq { \tl_put_right:Nx \l__diaa_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__diaa_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 } { \IfBooleanTF {#1} { \tl_gset:Nx } { \tl_set:Nx } #2 { \diaa_csv_item:nnn {#4} {#3} {#5} } \tl_if_empty:NT #2 { \msg_warning:nnnnn { diaa } { item-empty } {#3} {#4} {#5} } }

% key, value column, label \NewExpandableDocumentCommand \CSVItem { m O{1} m } { \diaa_csv_item:nnn {#1} {#2} {#3} }

% Version with error if empty % \NewExpandableDocumentCommand \CSVItem { m O{1} m } % { % \exp_args:Nf __diaa_check_empty_item:nnnn % { \diaa_csv_item:nnn {#1} {#2} {#3} } {#2} {#1} {#3} % } % \cs_new:Npn __diaa_check_empty_item:nnnn #1 #2 #3 #4 % { % \tl_if_empty:nTF {#1} % { \msg_expandable_error:nnnnn { diaa } { item-empty } {#2} {#3} {#4} } % {#1} % }

\cs_generate_variant:Nn \tl_item:nn { f }

% key, value column, label \cs_new:Npn \diaa_csv_item:nnn #1 #2 #3 { \prop_if_exist:cTF { g__diaa_csv_#3_prop } { \prop_if_in:cnTF { g__diaa_csv_#3_prop } {#1} { \exp_args:NNf __diaa_check_column_range:Nnn \use_i:nn { \prop_item:cn { g__diaa_csv_#3_prop } {#1} } {#2} } { \msg_expandable_error:nnnn { diaa } { key-undefined } {#1} {#3} } } { \msg_expandable_error:nnn { diaa } { csv-undefined } {#3} } }

\cs_new:Npn __diaa_check_column_range:Nnn #1 #2 #3 { \bool_lazy_or:nnTF { \int_compare_p:nNn {#3} = { 0 } } { \int_compare_p:nNn { \tl_count:n {#2} } < { \int_abs:n {#3} } } { \msg_expandable_error:nnn { diaa } { out-of-range } {#3} } { \exp_args:Nf #1 { \tl_item:nn {#2} {#3} } {#3} } }

\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 } { empty-row-item } { Empty~item~#1~\msg_line_context:! } \msg_new:nnn { diaa } { row-empty } { Row~#1'~in~CSV~`#2'~is~empty! }

\cs_new: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 } { \prop_if_exist:cTF { g__diaa_csv_#4_prop } { \prop_get:cnNTF { g__diaa_csv_#4_prop } {#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} } \IfBooleanTF {#1} { \cs_gset_nopar:Npx } { \cs_set_nopar:Npx } #2 [ ##1 ] { \exp_not:N \str_if_eq:nnTF {##1} { non-empty } { \exp_not:N __diaa_nb_nonempty_items_in_row:nn { 0 } \exp_not:V \l__diaa_tmpa_tl \exp_not:n { \q_recursion_tail \q_recursion_stop } } { \exp_not:N __diaa_check_column_range:Nnn \exp_not:N __diaa_check_empty:nn { \exp_not:V \l__diaa_tmpa_tl } {##1} } } } { \msg_error:nnnn { diaa } { key-undefined } {#3} {#4} } } { \msg_error:nnn { diaa } { csv-undefined } {#4} } }

\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 } } } \ExplSyntaxOff

\parindent0pt

\begin{document}

\ReadCSV{mydata}{test.csv}

\verb|\getValue\myValue{Second Parameter}{NOTmydata}|\ A \textbf{compilation-halting error} should show up stating that "NOTmydata" is undefined CSV data.\ \getValue\myValue{Second Parameter}{NOTmydata}\

\verb|\getValue\myValue{Non-existent Parameter}{mydata}|\ A \textbf{compilation-halting error} should show up stating that "Non-existent Parameter" is undefined CSV parameter.\ \getValue\myValue{Non-existent Parameter}{mydata}\

\verb|\getValue\myValue[4]{Second Parameter}{mydata}|\ An \textbf{error} because index 4 is out of range.\ \getValue\myValue[4]{Second Parameter}{mydata}\

\verb|\getValue\myValue{Empty Parameter}{mydata}|\ A \textbf{Warning} should show up stating that the value stored in \verb|\myValue| is empty. \getValue\myValue{Empty Parameter}{mydata}\

\getRow\myRow{Empty Parameter}{mydata} \myRow[1]

\CSVItem{Second Parameter}[4]{mydata}

\end{document}

  • Unfortunately, I am away from PC to experiment your answer, however, I forgot to mention that I need the same warnings/errors apply to \getRow as well. So, I would be grateful if you could consider this. I am sorry for not adding it in the question. Thanks in advance. – Diaa Dec 19 '20 at 21:36
  • 1
    @Diaa No need to be sorry :) I added the errors to \getRow – Phelype Oleinik Dec 19 '20 at 22:02
  • For \getRow, this \getRow\myRow{Empty Parameter}{mydata} doesn't make any warnings to pop up in my texstudio as \getValue does. So, can you please make \getRow behave the same? – Diaa Dec 19 '20 at 23:04
  • Also, for \getValue, I get this error message in red color ./readcsv_.tex:179: Undefined control sequence. <argument> \::error ! diaa: CSV database 'gmydata' undefined! l.179 \getValue\myRow{Empty Parameter}{gmydata} unlike that of \getRow that is in green color in my editor ./readcsv_.tex:179: Package diaa Error: CSV database 'gmydata' undefined! Type <return> to continue. l.179 \getRow\myRow{Empty Parameter}{gmydata}. So, can I make the warning and error of \getRow visually the same as that of \getValue? – Diaa Dec 19 '20 at 23:05
  • @Diaa Right, I misunderstood how \getRow works. Sorry about that. You have to get the row with \getRow\myRow{Empty Parameter}{mydata}, but that won't produce a warning on an empty item because you didn't specify an item. When you do \myRow[<index>], then the warning is (with my latest edit) issued if the item is empty. – Phelype Oleinik Dec 20 '20 at 01:10
  • @Diaa Regarding the different errors: one happens in an expansion-only context (red), so it is formatted differently from the other (normal, green) error, and your text editor thinks it has to colour it differently (not much can be done from TeX). If you really want both errors to be formatted in the same way you can 1) use an expandable error all times (that is, replace all \msg_error: by \msg_expandable_error:), then all will be red in your editor, or 2) sacrifice expandability and use a normal error all times. If you want the latter, say it and I will do tomorrow (too late now :) – Phelype Oleinik Dec 20 '20 at 01:15
  • I wish I could understand what you mean by expandability and its sacrifice to know the pros and cons of each approach; I am just a normal user but if you are patient to show what each approach means or outputs, I would be happy to learn:). Anyway, at least for \getRow\myRow{Empty Parameter}{mydata}, I would like the warning to popped up in my editor messages as done with \getValue\myValue{Empty Parameter}{mydata} here https://i.ibb.co/9TJYXk3/image.png. – Diaa Dec 20 '20 at 08:44