后续行动这个答案,如何扩展代码第二种方法将每行值列存储在列表或数组中并能够对其进行索引?
换句话说,我需要另一个宏(连同\getValue
),以以下方式存储同一行(具有行名的第一列除外)的所有列的值:
\getRow{<row name located in the first column>}{<list/array name of the whole row value columns>}
例如,考虑以下文件
\begin{filecontents*}{test.csv}
First Parameter , 7 , 9 , ,
Third Parameter , 5 , 10 , ,
Fifth Parameter , 3 , 6 , , 44
\end{filecontents*}
44
我可以先将 的行存储Fifth Parameter
在 中,\myList
从而获得 的值\getRow{Fifth Parameter}{\myList}
。然后\myList[4]
将获得 的值44
。3
可以通过\myList
或访问行的第一个值(例如)\myList[1]
。
答案1
\getRow
我在“第二种方法”中添加了一个命令现有代码,正如您所问的。其语法是:
\getRow[*] {\macro} {key} {label}
星号形式执行全局分配\宏(否则,该任务是本地的)。
这标签必须识别您之前使用过读取的文件
\ReadCSV
(它对应于 的第一个强制参数\ReadCSV
)。这钥匙用于从该文件中选择特定的行(与 和 含义相同
\getValue
)\CSVItem
。
接到这样的电话后\getRow
,\宏在仅扩展上下文中可以安全使用(例如,内部\edef
、\write
来自\num
等siunitx
):它使用 e-TeX 原语包装所需的值\unexpanded
,就像\tl_item:nn
一样。
支持负索引并从末尾开始计数:
\macro[-1]
扩展到存储在行的最后一项\宏;\macro[-2]
扩展到其前身;ETC。
作为特殊情况,\macro[non-empty]
扩展为存储在行中的非空项目数\宏。
\begin{filecontents*}{test.csv}
Third Parameter , 7 , 9 ,
First Parameter , 5 , {foo, bar} ,
Second Parameter , 3 , 6 , 44
\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:nw { 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: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 } }
}
\ExplSyntaxOff
\begin{document}
% Use default column for the key (1). The second empty optional argument (list
% of value columns) means we want to autodetect the value columns; then, the
% first column is for keys and all other columns are used as value columns.
\ReadCSV{mydata}{test.csv}
\getValue\rdPar{Second Parameter}{mydata}
\rdPar % 3
\getValue\rdPar[2]{Second Parameter}{mydata}
\rdPar % 6
\getValue\rdPar[3]{Second Parameter}{mydata}
\rdPar % 44
\getValue\rdPar{Third Parameter}{mydata}
\rdPar % 7
\edef\rdPar{\CSVItem{First Parameter}{mydata}}%
\rdPar % 5
\edef\rdPar{\CSVItem{First Parameter}[2]{mydata}}%
\rdPar % foo, bar
\edef\rdPar{\CSVItem{First Parameter}[3]{mydata}}%
\ifx\rdPar\empty
\textlangle empty\textrangle
\else
\rdPar
\fi
\getRow{\RowA}{First Parameter}{mydata}
\getRow{\RowB}{Second Parameter}{mydata}
\getRow{\RowC}{Third Parameter}{mydata}
``\RowA[1]'', ``\RowA[2]'', ``\RowA[3]'' ($\RowA[non-empty]$ non-empty)\par
``\RowB[1]'', ``\RowB[2]'', ``\RowB[3]'' ($\RowB[non-empty]$ non-empty)\par
``\RowC[1]'', ``\RowC[2]'', ``\RowC[3]'' ($\RowC[non-empty]$ non-empty)
{% Enter a group and perform a global assignment
\getRow*{\globallyDefined}{First Parameter}{mydata}%
}% Leave the group
``\globallyDefined[1]'', ``\globallyDefined[2]'', ``\globallyDefined[3]''
\edef\zzz{abc'\RowA[2]'def}
%\show\zzz % prints \zzz=macro:->abc'foo, bar'def.
\edef\zzz{\RowA[non-empty]}
%\show\zzz % prints \zzz=macro:->2.
\end{document}