检查某行 CSV 数据是否为空以及是否存在后未保存

检查某行 CSV 数据是否为空以及是否存在后未保存

后续行动这个答案随后的讨论\getRow预计会检查 CSV 数据的目标行是否存在,然后将该行的列保存到另一个宏中。

换句话说,下面的代码片段\getRow在成功检查目标行是否为空和存在后,不会处理该行的任何数据。

% 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.
}

\getRow那么,在通过以下完整 MWE 的空性和存在性检查后,如何处理行数据?

PS 我包含了完整的expl3代码,因为我不知道是否存在依赖关系


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

答案1

\__diaa_get_column:nnN这里是对(原始代码中没有分配给它的第三个参数)的修复,以及使用siunitx宏中结果的示例\num

为了方便起见,我还添加了一个\getNbNonEmpty宏来查找一行中非空项目的数量并将其存储在宏中:

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

此宏检查 ⟨CSV 文件标签⟩ 是否有效,以及 ⟨key⟩ 是否标识现有行。如果无效,您将收到与 相同的错误消息\getRow。但是,\getNbNonEmpty如果行是空的,则不会发出警告,因为我认为这在这里不会很有帮助;不过,如果您愿意,这很容易更改。至于 和\getValue\getRow星号形式对 执行全局赋值\⟨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}

在此处输入图片描述

相关内容