xparse:如何在检索空的或不存在的 CSV 数据时有条件地获取警告或错误?

xparse:如何在检索空的或不存在的 CSV 数据时有条件地获取警告或错误?

后续行动这个很好的答案读取 CSV 文件并保存,我需要以下内容:

  • A编译停止错误当我尝试索引不存在的行或列时是必需的,因为当发生这种情况时它会经历无休止的编译。

  • 我想获得警告在我的 Latex 编辑器中,当处理的宏\getValue具有空值时(即,CSV 文件中访问的单元格为空)。

  • 我希望警告和错误都显示相应的文件名代码行当构建选项-file-line-error被启用时。

  • 同样的条件也适用于\getRow检索时发出警告完全空行,以及編輯中止尝试访问不存在的 CSV 数据或行时出错(例如Parameter此处)。

在此处输入图片描述

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

答案1

由于您需要以相同方式格式化的警告和错误消息,并且不需要可扩展性,因此可以以不可扩展的方式重写代码,这允许您\msg_error:nn(nnnn)始终使用(而不是\msg_expandable_error:nn(nnnn))。(下面是可扩展的旧答案\diaa_csv_item:nnn,仅供参考。)

我稍微改变了一下 的输出语法\getRow。当你这样做时

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

\<row macro>定义语法如下:

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

这样,将行、列\<row macro>中的项目保存到中。以前的代码是可扩展的,因此会直接扩展到返回值,但现在它可能会抛出(不可扩展的)错误,因此受到保护并返回中的项目,然后可以使用该项目。<row ID><col number><CSV ID>\<return>\<row macro> [<col number>]\<row macro>\<return>

您请求的所有错误和警告都应该在那里。如果未定义 CSV 数据库,则会csv-undefined引发错误。如果 CSV 存在,但没有请求的密钥,key-undefined则会引发。这两个错误都在 中签入。这两个测试对于和\__diaa_csv_item_exist:nnN(T)很常见。\getValue\getRow

使用 时\getValue,如果请求的列为零或大于 CSV 中的列数,out-of-range则会出现错误。然后,如果请求了有效项目,则检查其是否为空。如果是,则item-empty发出警告。

使用 时\getRow,如果一行中的所有项都为空,row-empty则会发出警告,并且 被\<row macro>定义为improper-row在使用时引发错误。否则,\<row macro>被定义为执行与 相同的特定检查\getValue:检查列号是否在范围内,然后检查返回的项是否不为空。

代码如下:

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

以前的答案,供参考:

从存储的 CSV 中提取数据的函数\diaa_csv_item:nnn是可扩展的,因此您不会收到警告,只会收到可扩展的错误,而这些错误本身非常有限。可扩展错误的形式如下:

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

Undefined control sequence即TeX 的低级错误(\::error未定义的控制序列),以及其后的一行文本。消息不能太长,否则 TeX 会截断该消息。

我实现了警告作为对返回值的事后检查\getValue。由于该函数不可扩展,因此可能会出现警告。 \CSVItem不能出现警告:要么是可扩展错误,要么什么都没有。我留下了一个版本,其中有一个空项在代码中被注释掉了。

\getRow\myRow...请注意,在检查检索到的行的元素是否为空值(即使用然后\myRow[<index>]返回空值)的情况下,\__diaa_check_empty:nn将使用宏。此宏不受保护,因此它将在仅扩展的上下文中扩展,但是不是因为它包含一个\msg_warning:nnn(正如我之前所说,你不能有可扩展警告),所以如果检索到的项目为空,它可能会在扩展上下文中意外中断。如果不是,则一切正常。

除此之外,我还用来\prop_if_exist:cTF检查 CSV 是否已定义,\prop_if_in:cnTF检查请求的项目是否存在于 CSV 中,以及\tl_count:n检查请求的列是否在范围内。

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

相关内容