%% The LaTeX package csvsimple - version 2.6.0 (2024/01/19) %% csvsimple-l3.sty: Simple LaTeX CSV file processing (LaTeX3) %% %% ------------------------------------------------------------------------------------------- %% Copyright (c) 2008-2024 by Prof. Dr. Dr. Thomas F. Sturm %% ------------------------------------------------------------------------------------------- %% %% This work may be distributed and/or modified under the %% conditions of the LaTeX Project Public License, either version 1.3 %% of this license or (at your option) any later version. %% The latest version of this license is in %% http://www.latex-project.org/lppl.txt %% and version 1.3 or later is part of all distributions of LaTeX %% version 2005/12/01 or later. %% %% This work has the LPPL maintenance status `author-maintained'. %% %% This work consists of all files listed in README.md %% \NeedsTeXFormat{LaTeX2e}[2023-11-01] \ProvidesExplPackage{csvsimple-l3}{2024/01/19}{2.6.0} {LaTeX3 CSV file processing} %---- check package \cs_if_exist:NT \c__csvsim_package_expl_bool { \msg_new:nnn { csvsimple }{ l3 / package-loaded } { Package~'csvsimple-legacy'~seems~already~be~loaded!~ 'csvsimple-l3'~cannot~be~loaded~simultaneously.~ Therefore,~loading~of~'csvsimple-l3'~stops~now.} \msg_warning:nn { csvsimple }{ l3 / package-loaded } \tex_endinput:D } \bool_const:Nn \c__csvsim_package_expl_bool { \c_true_bool } %---- declarations and expl3 variants \bool_new:N \g__csvsim_check_column_count_bool \bool_new:N \g__csvsim_collect_data_bool \bool_new:N \g__csvsim_colnames_detection_bool \bool_new:N \g__csvsim_consume_collected_data_bool \bool_new:N \g__csvsim_head_bool \bool_new:N \g__csvsim_head_to_colnames_bool \bool_new:N \g__csvsim_line_accepted_bool \bool_new:N \g__csvsim_line_firstline_bool \bool_new:N \l__csvsim_respect_and_bool \bool_new:N \l__csvsim_respect_backslash_bool \bool_new:N \l__csvsim_respect_circumflex_bool \bool_new:N \l__csvsim_respect_dollar_bool \bool_new:N \l__csvsim_respect_leftbrace_bool \bool_new:N \l__csvsim_respect_percent_bool \bool_new:N \l__csvsim_respect_rightbrace_bool \bool_new:N \l__csvsim_respect_sharp_bool \bool_new:N \l__csvsim_respect_tab_bool \bool_new:N \l__csvsim_respect_tilde_bool \bool_new:N \l__csvsim_respect_underscore_bool \int_new:N \g__csvsim_col_int \int_new:N \g__csvsim_colmax_int \int_new:N \g_csvsim_columncount_int \int_new:N \g_csvsim_inputline_int \int_new:N \g_csvsim_row_int \int_new:N \l__csvsim_tmpa_int \int_new:N \l__csvsim_tmpb_int \seq_new:N \g__csvsim_colname_seq \seq_new:N \g__csvsim_line_seq \seq_new:N \g__csvsim_range_seq \seq_new:N \l__csvsim_tmpa_seq \str_new:N \g__csvsim_curfilename_str \str_new:N \g__csvsim_filename_str \str_new:N \l__csvsim_csvsorter_command_str \str_new:N \l__csvsim_csvsorter_configpath_str \str_new:N \l__csvsim_csvsorter_log_str \str_new:N \l__csvsim_csvsorter_token_str \str_new:N \l__csvsim_ppfilename_str \str_new:N \l__csvsim_temp_filename_str \str_new:N \l__csvsim_tmpa_str \str_new:N \l__csvsim_tmpb_str \tl_const:Nn \c__csvsim_par_tl { \par } \tl_new:N \g__csvsim_after_table_tl \tl_new:N \g__csvsim_before_table_tl \tl_new:N \g__csvsim_begin_table_center_tl \tl_new:N \g__csvsim_body_tl \tl_new:N \g__csvsim_catcode_tl \tl_new:N \g__csvsim_collect_tl \tl_new:N \g__csvsim_columnnames_tl \tl_new:N \g__csvsim_data_collection_tl \tl_new:N \g__csvsim_end_table_center_tl \tl_new:N \g__csvsim_filter_tl \tl_new:N \g__csvsim_generic_table_options_tl \tl_new:N \g__csvsim_headname_prefix_tl \tl_new:N \g__csvsim_hook_after_filter_tl \tl_new:N \g__csvsim_hook_after_first_line_tl \tl_new:N \g__csvsim_hook_after_head_tl \tl_new:N \g__csvsim_hook_after_line_tl \tl_new:N \g__csvsim_hook_after_reading_tl \tl_new:N \g__csvsim_hook_before_filter_tl \tl_new:N \g__csvsim_hook_before_first_line_tl \tl_new:N \g__csvsim_hook_before_line_tl \tl_new:N \g__csvsim_hook_before_reading_tl \tl_new:N \g__csvsim_hook_columncounterror_tl \tl_new:N \g__csvsim_hook_late_after_first_line_tl \tl_new:N \g__csvsim_hook_late_after_head_tl \tl_new:N \g__csvsim_hook_late_after_last_line_tl \tl_new:N \g__csvsim_hook_late_after_line_application_tl \tl_new:N \g__csvsim_hook_late_after_line_tl \tl_new:N \g__csvsim_hook_table_begin_tl \tl_new:N \g__csvsim_hook_table_end_tl \tl_new:N \g__csvsim_preprocessor_tl \tl_new:N \g__csvsim_separator_tl \tl_new:N \g__csvsim_table_foot_tl \tl_new:N \g__csvsim_table_head_tl \tl_new:N \g__csvsim_tmpa_tl \tl_new:N \l__csvsim_filter_condition_tl \tl_new:N \l__csvsim_tmpa_tl \tl_new:N \l__csvsim_tmpb_tl \group_begin: \char_set_catcode_other:n { 9 } \str_const:Nn \c__csvsim_tab_str { ^^I } \group_end: \regex_const:Nn \c__csvsim_integer_regex {\A\d+\Z} \cs_generate_variant:Nn \bool_gset:Nn { NV } \cs_generate_variant:Nn \seq_gset_split:Nnn { NVV } %---- messages \msg_new:nnnn { csvsimple }{ column-name } { Unknown~column~key~'#1'. } { The~key~'#1'~you~used~in~'column~names'~is~unknown.\\ Therefore,~the~macro~#2 is~not~defined. } \msg_new:nnn { csvsimple }{ empty-head } { File~'#1'~starts~with~an~empty~line~(empty~head)!.} \msg_new:nnn { csvsimple }{ file-error } { File~'#1'~not~existent,~not~readable,~or~empty!} \msg_new:nnn { csvsimple }{ column-wrong-count } { #1~instead~of~#2~columns~for~input~line~#3~of~file~'#4'} \msg_new:nnn { csvsimple }{ sort-info } { Sort~'#1'~by~'#2' } \msg_new:nnn { csvsimple }{ sort-shell-escape } { You~need~to~use~'-shell-escape'~to~run~CSV-Sorter } \msg_new:nnnn { csvsimple }{ sort-error } { Call~of~CSV-Sorter~failed! } { See~log~file~'\l__csvsim_csvsorter_log_str'. } %---- core loop processing \NewHook{csvsimple/csvline} \cs_new_protected_nopar:Npn \__csvsim_read_line: { \group_begin: \g__csvsim_catcode_tl \ior_get:NNTF \g__csvsim_ior \l__csvsim_tmpa_tl { \tl_gset_eq:NN \csvline \l__csvsim_tmpa_tl \int_gincr:N \g_csvsim_inputline_int } { \msg_error:nnx { csvsimple }{ file-error }{ \g__csvsim_curfilename_str } } \group_end: \UseHook{csvsimple/csvline} } \cs_new_protected_nopar:Npn \__csvsim_scan_line: { \int_gzero:N \g__csvsim_col_int \seq_gset_split:NVV \g__csvsim_line_seq \g__csvsim_separator_tl \csvline \seq_map_inline:Nn \g__csvsim_line_seq { \int_gincr:N \g__csvsim_col_int \tl_gset:cn {csvcol \int_to_roman:n \g__csvsim_col_int}{##1} } \int_compare:nNnT \g__csvsim_colmax_int < \g__csvsim_col_int { \int_gset_eq:NN \g__csvsim_colmax_int \g__csvsim_col_int } \int_compare:nNnT \g_csvsim_columncount_int < \c_one_int { \int_gset_eq:NN \g_csvsim_columncount_int \g__csvsim_col_int } } \cs_new_protected_nopar:Npn \__csvsim_process_head_name:n #1 { \tl_set:No \l__csvsim_tmpa_tl {\cs:w csvcol\int_to_roman:n{#1} \cs_end:} \exp_args:NnV \cs_set_nopar:cpn {__csvsim__/\l__csvsim_tmpa_tl} \l__csvsim_tmpa_tl \bool_if:NT \g__csvsim_head_to_colnames_bool { \exp_args:NNe \seq_gput_right:Nn \g__csvsim_colname_seq { \exp_not:o { \cs:w \g__csvsim_headname_prefix_tl \l__csvsim_tmpa_tl \cs_end: } \exp_not:o { \l__csvsim_tmpa_tl } } } } \cs_new_protected_nopar:Npn \__csvsim_read_head: { \__csvsim_read_line: \tl_if_eq:NNTF \csvline \c__csvsim_par_tl { \msg_error:nne { csvsimple }{ empty-head }{ \g__csvsim_filename_str } } { \int_gzero:N \g_csvsim_columncount_int \__csvsim_scan_line: \bool_if:NT \g__csvsim_colnames_detection_bool { \int_step_function:nN \g_csvsim_columncount_int \__csvsim_process_head_name:n } } } \cs_new_protected_nopar:Npn \__csvsim_process_colname:nn #1#2 { \cs_if_exist:cTF {__csvsim__/#1} { \exp_args:NNe \seq_gput_right:Nn \g__csvsim_colname_seq { \exp_not:n { #2 } \exp_not:v { __csvsim__/#1 } } } { \regex_match:NnTF \c__csvsim_integer_regex {#1} { \exp_args:NNe \seq_gput_right:Nn \g__csvsim_colname_seq { \exp_not:n { #2 } \exp_not:o { \cs:w csvcol\int_to_roman:n{#1} \cs_end: } } } { \str_set:Nn \l__csvsim_tmpb_str {#2} \msg_error:nnee { csvsimple }{ column-name }{ #1 }{ \l__csvsim_tmpb_str } } } } \cs_new_protected_nopar:Npn \__csvsim_set_colnames: { \seq_map_inline:Nn \g__csvsim_colname_seq { \tl_gset_eq:NN ##1 } } \cs_new_protected_nopar:Npn \__csvsim_loop: { % preprocess \tl_if_empty:NTF \l__csvsim_preprocessor_tl { \str_gset_eq:NN \g__csvsim_curfilename_str \g__csvsim_filename_str } { \l__csvsim_preprocessor_tl \g__csvsim_filename_str \l__csvsim_ppfilename_str \str_gset_eq:NN \g__csvsim_curfilename_str \l__csvsim_ppfilename_str } % initialize \cs_if_exist:NF \g__csvsim_ior { \ior_new:N \g__csvsim_ior } \__csvsim_setup_catcode_list: \seq_gclear:N \g__csvsim_colname_seq \int_gzero:N \g_csvsim_inputline_int \int_gzero:N \g_csvsim_row_int \int_gset_eq:NN \g__csvsim_colmax_int \c_one_int \bool_if:NT \g__csvsim_collect_data_bool { \__csvsim_collect_data_begin: } % open file \g__csvsim_hook_before_reading_tl \g__csvsim_hook_table_begin_tl \ior_open:Nn \g__csvsim_ior { \g__csvsim_curfilename_str } % read head line \bool_if:NT \g__csvsim_head_bool { \__csvsim_read_head: } \exp_args:NNNV \keyval_parse:NNn \use_none:n \__csvsim_process_colname:nn \g__csvsim_columnnames_tl \bool_if:NT \g__csvsim_head_bool { \g__csvsim_hook_after_head_tl } % read body lines \tl_gset:Nn \g__csvsim_hook_late_after_line_application_tl { \g__csvsim_hook_late_after_first_line_tl \tl_gset_eq:NN \g__csvsim_hook_late_after_line_application_tl \g__csvsim_hook_late_after_line_tl } \bool_gset_true:N \g__csvsim_line_firstline_bool \bool_until_do:nn {\ior_if_eof_p:N \g__csvsim_ior} { \__csvsim_read_line: \tl_if_eq:NNF \csvline \c__csvsim_par_tl { \bool_gset_true:N \g__csvsim_line_accepted_bool \__csvsim_scan_line: \__csvsim_set_colnames: \bool_if:NT \g__csvsim_check_column_count_bool { \int_compare:nNnF \g__csvsim_col_int = \g_csvsim_columncount_int { \bool_gset_false:N \g__csvsim_line_accepted_bool \g__csvsim_hook_columncounterror_tl } } \bool_if:NT \g__csvsim_line_accepted_bool { \g__csvsim_hook_before_filter_tl \g__csvsim_filter_tl \bool_if:NT \g__csvsim_line_accepted_bool { \int_gincr:N \g_csvsim_row_int \__csvsim_check_range: \bool_if:NT \g__csvsim_line_accepted_bool { \bool_if:NTF \g__csvsim_line_firstline_bool { \bool_if:NT \g__csvsim_head_bool { \g__csvsim_hook_late_after_head_tl } \g__csvsim_hook_after_filter_tl \g__csvsim_hook_before_first_line_tl \g__csvsim_body_tl \g__csvsim_hook_after_first_line_tl \bool_gset_false:N \g__csvsim_line_firstline_bool } { \g__csvsim_hook_late_after_line_application_tl \g__csvsim_hook_after_filter_tl \g__csvsim_hook_before_line_tl \g__csvsim_body_tl \g__csvsim_hook_after_line_tl } } } } } } % close file \ior_close:N \g__csvsim_ior % clear macros \int_step_inline:nn \g__csvsim_colmax_int { \tl_set:No \l__csvsim_tmpa_tl {\cs:w csvcol\int_to_roman:n{##1} \cs_end:} \use:e { \exp_not:N\tl_gclear:N \exp_not:V\l__csvsim_tmpa_tl } } \__csvsim_set_colnames: \seq_gclear:N \g__csvsim_colname_seq \bool_if:NF \g__csvsim_line_firstline_bool { \g__csvsim_hook_late_after_last_line_tl } \g__csvsim_hook_table_end_tl \g__csvsim_hook_after_reading_tl \bool_if:NT \g__csvsim_collect_data_bool { \__csvsim_collect_data_end: } } \NewDocumentCommand \csvloop { +m } { \keys_set:nn { csvsim } { default, every~csv, #1} \__csvsim_loop: } \NewDocumentCommand \csvreader { +O{} m m +m } { \keys_set:nn { csvsim } { default, every~csv, #1, file={#2}, column~names={#3} } \tl_gset:Nn \g__csvsim_body_tl {#4} \__csvsim_loop: } %---- auxiliary user macros \NewExpandableDocumentCommand \csvlinetotablerow { } { \seq_use:Nn \g__csvsim_line_seq { & } } \NewExpandableDocumentCommand \thecsvrow { } { \int_use:N \g_csvsim_row_int } \NewExpandableDocumentCommand \thecsvcolumncount { } { \int_use:N \g_csvsim_columncount_int } \NewExpandableDocumentCommand \thecsvinputline { } { \int_use:N \g_csvsim_inputline_int } \NewExpandableDocumentCommand \ifcsvfirstrow { } { \bool_if:NTF \g__csvsim_line_firstline_bool } % deprecated \NewExpandableDocumentCommand \csviffirstrow { } { \bool_if:NTF \g__csvsim_line_firstline_bool } \NewExpandableDocumentCommand \ifcsvoddrow { } { \int_if_odd:nTF {\g_csvsim_row_int} } % deprecated \NewExpandableDocumentCommand \csvifoddrow { } { \int_if_odd:nTF {\g_csvsim_row_int} } %---- String and Number Tests \NewExpandableDocumentCommand \IfCsvsimStrEqualTF { } { \str_if_eq:eeTF } \NewCommandCopy \ifcsvstrcmp \IfCsvsimStrEqualTF \NewExpandableDocumentCommand \ifcsvnotstrcmp { m m +m +m } { \IfCsvsimStrEqualTF{#1}{#2}{#4}{#3} } \NewDocumentCommand \IfCsvsimTlEqualTF { } { \tl_if_eq:eeTF } \NewCommandCopy \ifcsvstrequal \IfCsvsimTlEqualTF \NewDocumentCommand \IfCsvsimTlProtectedEqualTF { m m } { \protected@edef \l__csvsim_tmpa_tl {#1} \protected@edef \l__csvsim_tmpb_tl {#2} \tl_if_eq:NNTF \l__csvsim_tmpa_tl \l__csvsim_tmpb_tl } \NewCommandCopy \ifcsvprostrequal \IfCsvsimTlProtectedEqualTF \NewExpandableDocumentCommand \IfCsvsimFpCompareTF { m } { \fp_compare:nTF {#1} } \NewCommandCopy \ifcsvfpcmp \IfCsvsimFpCompareTF \NewExpandableDocumentCommand \IfCsvsimIntCompareTF { m } { \int_compare:nTF {#1} } \NewCommandCopy \ifcsvintcmp \IfCsvsimIntCompareTF %---- filename functions \cs_new_protected_nopar:Npn \__csvsim_set_temp_filename:nnn #1#2#3 { \str_set:Nn \l__csvsim_temp_filename_str {#2#3} \str_if_empty:NTF \l__csvsim_temp_filename_str { \str_set:Nn \l__csvsim_temp_filename_str {#1} } { \str_set:Nn \l__csvsim_tmpa_str {#1} \str_if_empty:NF \l__csvsim_tmpa_str { \str_compare:eNeF { \str_item:Nn \l__csvsim_tmpa_str {-1} } = { / } { \str_put_right:Nn \l__csvsim_tmpa_str {/} } \str_concat:NNN \l__csvsim_temp_filename_str \l__csvsim_tmpa_str \l__csvsim_temp_filename_str } } } \cs_new_protected_nopar:Npn \__csvsim_set_temp_filename:n #1 { \file_parse_full_name_apply:nN { #1 } \__csvsim_set_temp_filename:nnn } \cs_new_protected_nopar:Npn \__csvsim_set_filename:Nn #1#2 { \__csvsim_set_temp_filename:n { #2 } \str_set_eq:NN #1 \l__csvsim_temp_filename_str } \cs_new_protected_nopar:Npn \__csvsim_gset_filename:Nn #1#2 { \__csvsim_set_temp_filename:n { #2 } \str_gset_eq:NN #1 \l__csvsim_temp_filename_str } %---- keys \NewDocumentCommand \csvset { +m } { \keys_set:nn { csvsim } { #1 } } \NewDocumentCommand \csvstyle { m +m } { \keys_define:nn { csvsim } { #1 .meta:n = { #2 } } } \NewDocumentCommand \csvnames { m m } { \keys_define:nn { csvsim } { #1 .meta:n = { column~names={#2} } } } \keys_define:nn { csvsim } { file .code:n = \__csvsim_gset_filename:Nn \g__csvsim_filename_str {#1}, column~names~reset .code:n = \tl_gclear:N \g__csvsim_columnnames_tl, column~names .code:n = { \tl_if_empty:NTF \g__csvsim_columnnames_tl { \tl_gset:Nn \g__csvsim_columnnames_tl {#1} } { \tl_gput_right:Nn \g__csvsim_columnnames_tl {,#1} } }, command .tl_gset:N = \g__csvsim_body_tl, check~column~count .bool_gset:N = \g__csvsim_check_column_count_bool, on~column~count~error .tl_gset:N = \g__csvsim_hook_columncounterror_tl, head .bool_gset:N = \g__csvsim_head_bool, head~to~column~names~prefix .tl_gset:N = \g__csvsim_headname_prefix_tl, head~to~column~names .bool_gset:N = \g__csvsim_head_to_colnames_bool, column~names~detection .bool_gset:N = \g__csvsim_colnames_detection_bool, column~count .int_gset:N = \g_csvsim_columncount_int, separator .choice:, separator/comma .code:n = { \tl_gset:Nn \g__csvsim_separator_tl {,} }, separator/semicolon .code:n = { \tl_gset:Nn \g__csvsim_separator_tl {;} }, separator/pipe .code:n = { \tl_gset:Nn \g__csvsim_separator_tl {|} }, separator/tab .code:n = { \tl_gset:NV \g__csvsim_separator_tl \c__csvsim_tab_str \csvset{respect~tab} }, separator/space .code:n = { \tl_gset:Nn \g__csvsim_separator_tl {~} }, every~csv .meta:n = {}, no~head .meta:n = { head=false }, no~check~column~count .meta:n = { check~column~count=false }, warn~on~column~count~error .meta:n = { on~column~count~error= { \msg_warning:nneeee { csvsimple }{ column-wrong-count } { \int_use:N\g__csvsim_col_int } { \int_use:N\g_csvsim_columncount_int } { \int_use:N\g_csvsim_inputline_int } { \g__csvsim_filename_str } }}, } %---- hooks \keys_define:nn { csvsim } { before~reading .tl_gset:N = \g__csvsim_hook_before_reading_tl, after~head .tl_gset:N = \g__csvsim_hook_after_head_tl, before~filter .tl_gset:N = \g__csvsim_hook_before_filter_tl, after~filter .tl_gset:N = \g__csvsim_hook_after_filter_tl, late~after~head .tl_gset:N = \g__csvsim_hook_late_after_head_tl, late~after~first~line .tl_gset:N = \g__csvsim_hook_late_after_first_line_tl, late~after~last~line .tl_gset:N = \g__csvsim_hook_late_after_last_line_tl, before~first~line .tl_gset:N = \g__csvsim_hook_before_first_line_tl, after~first~line .tl_gset:N = \g__csvsim_hook_after_first_line_tl, after~reading .tl_gset:N = \g__csvsim_hook_after_reading_tl, late~after~line .code:n = { \tl_gset:Nn \g__csvsim_hook_late_after_line_tl {#1} \tl_gset_eq:NN \g__csvsim_hook_late_after_first_line_tl \g__csvsim_hook_late_after_line_tl \tl_gset_eq:NN \g__csvsim_hook_late_after_last_line_tl \g__csvsim_hook_late_after_line_tl }, before~line .code:n = { \tl_gset:Nn \g__csvsim_hook_before_line_tl {#1} \tl_gset_eq:NN \g__csvsim_hook_before_first_line_tl \g__csvsim_hook_before_line_tl }, after~line .code:n = { \tl_gset:Nn \g__csvsim_hook_after_line_tl {#1} \tl_gset_eq:NN \g__csvsim_hook_after_first_line_tl \g__csvsim_hook_after_line_tl }, } %---- filter \cs_new_protected_nopar:Npn \__csvsim_set_filter:n #1 { \tl_gset:Nn \g__csvsim_filter_tl { #1 } } \cs_new_protected_nopar:Npn \__csvsim_set_filter_bool:n #1 { \tl_set:Nn \l__csvsim_filter_condition_tl { #1 } \tl_gset:Nn \g__csvsim_filter_tl { \bool_gset:NV \g__csvsim_line_accepted_bool \l__csvsim_filter_condition_tl } } \NewDocumentCommand \csvfilteraccept { } { \__csvsim_set_filter:n { \bool_gset_true:N \g__csvsim_line_accepted_bool } } \NewDocumentCommand \csvfilterreject { } { \__csvsim_set_filter:n { \bool_gset_false:N \g__csvsim_line_accepted_bool } } \keys_define:nn { csvsim } { no~filter .code:n = { \csvfilteraccept }, filter~reject~all .code:n = { \csvfilterreject }, filter~accept~all .code:n = { \csvfilteraccept }, full~filter. tl_gset:N = \g__csvsim_hook_before_filter_tl, filter~test .code:n = { \__csvsim_set_filter:n { #1 { \bool_gset_true:N \g__csvsim_line_accepted_bool } { \bool_gset_false:N \g__csvsim_line_accepted_bool } } }, filter~bool .code:n = { \__csvsim_set_filter_bool:n { #1 } }, filter~fp .code:n = { \__csvsim_set_filter_bool:n { \fp_compare_p:n {#1} } }, filter~strcmp .code:n = { \__csvsim_set_filter_bool:n { \str_if_eq_p:ee #1 } }, filter~not~strcmp .code:n = { \__csvsim_set_filter_bool:n { !\str_if_eq_p:ee #1 } }, and~filter~bool .code:n = { \tl_put_right:Nn \l__csvsim_filter_condition_tl { && #1 } }, and~filter~fp .code:n = { \tl_put_right:Nn \l__csvsim_filter_condition_tl { && \fp_compare_p:n {#1} } }, and~filter~strcmp .code:n = { \tl_put_right:Nn \l__csvsim_filter_condition_tl { && \str_if_eq_p:ee #1 } }, and~filter~not~strcmp .code:n = { \tl_put_right:Nn \l__csvsim_filter_condition_tl { && !\str_if_eq_p:ee #1 } }, or~filter~bool .code:n = { \tl_put_right:Nn \l__csvsim_filter_condition_tl { || #1 } }, or~filter~fp .code:n = { \tl_put_right:Nn \l__csvsim_filter_condition_tl { || \fp_compare_p:n {#1} } }, or~filter~strcmp .code:n = { \tl_put_right:Nn \l__csvsim_filter_condition_tl { || \str_if_eq_p:ee #1 } }, or~filter~not~strcmp .code:n = { \tl_put_right:Nn \l__csvsim_filter_condition_tl { || !\str_if_eq_p:ee #1 } }, } \NewDocumentCommand \csvfilterbool { m m } { \keys_define:nn { csvsim } { #1 .meta:n = { filter~bool={#2} } } } % ifthen \keys_define:nn { csvsim } { filter~ifthen .code:n = { \__csvsim_set_filter:n { \ifthenelse{#1} { \bool_gset_true:N \g__csvsim_line_accepted_bool } { \bool_gset_false:N \g__csvsim_line_accepted_bool } } }, filter~equal .meta:n = { filter~ifthen=\equal #1 }, filter~not~equal .meta:n = { filter~ifthen=\not\equal #1 }, } % etoolbox \keys_define:nn { csvsim } { filter~expr .code:n = { \__csvsim_set_filter:n { \ifboolexpr{#1} { \bool_gset_true:N \g__csvsim_line_accepted_bool } { \bool_gset_false:N \g__csvsim_line_accepted_bool } } }, } %---- range \cs_new_protected_nopar:Npn \__csvsim_add_range:n #1 { \tl_if_in:nnTF {#1}{-} { \seq_set_split:Nnn \l__csvsim_tmpa_seq {-} {#1} \seq_pop_left:NN \l__csvsim_tmpa_seq \l__csvsim_tmpa_tl \seq_pop_left:NN \l__csvsim_tmpa_seq \l__csvsim_tmpb_tl \tl_if_empty:NTF \l__csvsim_tmpa_tl { \int_set_eq:NN \l__csvsim_tmpa_int \c_one_int } { \int_set:Nn \l__csvsim_tmpa_int { \l__csvsim_tmpa_tl } } \tl_if_empty:NTF \l__csvsim_tmpb_tl { \int_set_eq:NN \l__csvsim_tmpb_int \c_max_int } { \int_set:Nn \l__csvsim_tmpb_int { \l__csvsim_tmpb_tl } } } { \tl_if_in:nnTF {#1}{+} { \seq_set_split:Nnn \l__csvsim_tmpa_seq {+} {#1} \seq_pop_left:NN \l__csvsim_tmpa_seq \l__csvsim_tmpa_tl \seq_pop_left:NN \l__csvsim_tmpa_seq \l__csvsim_tmpb_tl \tl_if_empty:NTF \l__csvsim_tmpa_tl { \int_set:Nn \l__csvsim_tmpa_int { 1 } } { \int_set:Nn \l__csvsim_tmpa_int { \l__csvsim_tmpa_tl } } \tl_if_empty:NTF \l__csvsim_tmpb_tl { \int_set_eq:NN \l__csvsim_tmpb_int \l__csvsim_tmpa_int } { \int_set:Nn \l__csvsim_tmpb_int { \l__csvsim_tmpa_int + \l__csvsim_tmpb_tl - 1 } } } { \int_set:Nn \l__csvsim_tmpa_int {#1} \int_set_eq:NN \l__csvsim_tmpb_int \l__csvsim_tmpa_int } } \seq_gput_right:Ne \g__csvsim_range_seq {{\int_use:N \l__csvsim_tmpa_int}{\int_use:N \l__csvsim_tmpb_int}} } \cs_new_protected_nopar:Npn \__csvsim_set_range:n #1 { \seq_gclear:N \g__csvsim_range_seq \keyval_parse:NNn \__csvsim_add_range:n \use_none:nn { #1 } } \cs_generate_variant:Nn \__csvsim_set_range:n { e } \keys_define:nn { csvsim } { range .code:n = { \__csvsim_set_range:e {#1} }, } \prg_new_conditional:Npnn \__csvsim_if_in_range:nn #1#2 { p , T } { \if_int_compare:w #1 > \g_csvsim_row_int \prg_return_false: \else: \if_int_compare:w #2 < \g_csvsim_row_int \prg_return_false: \else: \prg_return_true: \fi: \fi: } \cs_new_protected_nopar:Npn \__csvsim_check_range: { \seq_if_empty:NF \g__csvsim_range_seq { \bool_gset_false:N \g__csvsim_line_accepted_bool \seq_map_inline:Nn \g__csvsim_range_seq { \__csvsim_if_in_range:nnT ##1 { \bool_gset_true:N \g__csvsim_line_accepted_bool \seq_map_break: } } } } %---- data collection \cs_new_protected_nopar:Npn \__csvsim_gset_tl_to_collect:N #1 { \tl_gset:Ne #1 { \exp_not:N \tl_build_gput_right:Nn \exp_not:N \g__csvsim_collect_tl { \exp_not:o { #1 } } } } \cs_new_protected_nopar:Npn \__csvsim_gset_tl_to_collect_expanded:N #1 { \tl_gset:Ne #1 { \exp_not:N \tl_build_gput_right:Ne \exp_not:N \g__csvsim_collect_tl { \exp_not:o { #1 } } } } \cs_new_protected_nopar:Npn \__csvsim_collect_data_begin: { \tl_build_gbegin:N \g__csvsim_collect_tl \__csvsim_gset_tl_to_collect:N \g__csvsim_hook_after_head_tl \__csvsim_gset_tl_to_collect:N \g__csvsim_hook_after_first_line_tl \__csvsim_gset_tl_to_collect:N \g__csvsim_hook_after_line_tl \__csvsim_gset_tl_to_collect:N \g__csvsim_hook_before_first_line_tl \__csvsim_gset_tl_to_collect:N \g__csvsim_hook_before_line_tl \__csvsim_gset_tl_to_collect:N \g__csvsim_hook_late_after_first_line_tl \__csvsim_gset_tl_to_collect:N \g__csvsim_hook_late_after_head_tl \__csvsim_gset_tl_to_collect:N \g__csvsim_hook_late_after_last_line_tl \__csvsim_gset_tl_to_collect:N \g__csvsim_hook_late_after_line_tl \__csvsim_gset_tl_to_collect_expanded:N \g__csvsim_body_tl } \cs_new_protected_nopar:Npn \__csvsim_collect_data_end: { \tl_build_gend:N \g__csvsim_collect_tl \bool_if:NTF \g__csvsim_consume_collected_data_bool { \g__csvsim_collect_tl \exp_args:NV \tl_gclear:N \g__csvsim_data_collection_tl } { \exp_args:NV \tl_gset_eq:NN \g__csvsim_data_collection_tl \g__csvsim_collect_tl } \tl_gclear:N \g__csvsim_collect_tl } \cs_set_eq:NN \csvexpval \exp_not:o \cs_set_eq:NN \csvexpnot \exp_not:N \NewDocumentCommand{ \csvcollectn }{ +m } { \tl_build_gput_right:Nn \g__csvsim_collect_tl {#1} } \NewDocumentCommand{ \csvcollecte }{ +m } { \tl_build_gput_right:Ne \g__csvsim_collect_tl {#1} } % alias for \csvcollecte \NewDocumentCommand{ \csvcollectx }{ +m } { \tl_build_gput_right:Ne \g__csvsim_collect_tl {#1} } \NewDocumentCommand{ \csvcollectV }{ m } { \tl_build_gput_right:Ne \g__csvsim_collect_tl { \exp_not:o { #1 } } } \keys_define:nn { csvsim } { collect~data .bool_gset:N = \g__csvsim_collect_data_bool, data~collection .tl_gset:N = \g__csvsim_data_collection_tl, consume~collected~data .bool_gset:N = \g__csvsim_consume_collected_data_bool, } \keys_set:nn { csvsim } { data~collection = \csvdatacollection, } %---- catcodes \cs_new_protected_nopar:Npn \__csvsim_setup_catcode_list: { \tl_gclear:N \g__csvsim_catcode_tl \bool_if:NT \l__csvsim_respect_tab_bool { \tl_gput_right:Nn \g__csvsim_catcode_tl { \char_set_catcode_other:n { 9 } } } \bool_if:NT \l__csvsim_respect_percent_bool { \tl_gput_right:Nn \g__csvsim_catcode_tl { \char_set_catcode_other:n { 37 } } } \bool_if:NT \l__csvsim_respect_sharp_bool { \tl_gput_right:Nn \g__csvsim_catcode_tl { \char_set_catcode_other:n { 35 } } } \bool_if:NT \l__csvsim_respect_dollar_bool { \tl_gput_right:Nn \g__csvsim_catcode_tl { \char_set_catcode_other:n { 36 } } } \bool_if:NT \l__csvsim_respect_and_bool { \tl_gput_right:Nn \g__csvsim_catcode_tl { \char_set_catcode_other:n { 38 } } } \bool_if:NT \l__csvsim_respect_backslash_bool { \tl_gput_right:Nn \g__csvsim_catcode_tl { \char_set_catcode_other:n { 92 } } } \bool_if:NT \l__csvsim_respect_underscore_bool { \tl_gput_right:Nn \g__csvsim_catcode_tl { \char_set_catcode_other:n { 95 } } } \bool_if:NT \l__csvsim_respect_tilde_bool { \tl_gput_right:Nn \g__csvsim_catcode_tl { \char_set_catcode_other:n { 126 } } } \bool_if:NT \l__csvsim_respect_circumflex_bool { \tl_gput_right:Nn \g__csvsim_catcode_tl { \char_set_catcode_other:n { 94 } } } \bool_if:NT \l__csvsim_respect_leftbrace_bool { \tl_gput_right:Nn \g__csvsim_catcode_tl { \char_set_catcode_other:n { 123 } } } \bool_if:NT \l__csvsim_respect_rightbrace_bool { \tl_gput_right:Nn \g__csvsim_catcode_tl { \char_set_catcode_other:n { 125 } } } } \keys_define:nn { csvsim } { respect~tab .bool_set:N = \l__csvsim_respect_tab_bool, respect~percent .bool_set:N = \l__csvsim_respect_percent_bool, respect~sharp .bool_set:N = \l__csvsim_respect_sharp_bool, respect~dollar .bool_set:N = \l__csvsim_respect_dollar_bool, respect~and .bool_set:N = \l__csvsim_respect_and_bool, respect~backslash .bool_set:N = \l__csvsim_respect_backslash_bool, respect~underscore .bool_set:N = \l__csvsim_respect_underscore_bool, respect~tilde .bool_set:N = \l__csvsim_respect_tilde_bool, respect~circumflex .bool_set:N = \l__csvsim_respect_circumflex_bool, respect~leftbrace .bool_set:N = \l__csvsim_respect_leftbrace_bool, respect~rightbrace .bool_set:N = \l__csvsim_respect_rightbrace_bool, respect~all .meta:n = { respect~tab, respect~percent, respect~sharp, respect~dollar, respect~and, respect~backslash, respect~underscore, respect~tilde, respect~circumflex, respect~leftbrace, respect~rightbrace }, respect~none .meta:n = { respect~tab=false, respect~percent=false, respect~sharp=false, respect~dollar=false, respect~and=false, respect~backslash=false, respect~underscore=false, respect~tilde=false, respect~circumflex=false, respect~leftbrace=false, respect~rightbrace=false }, } %---- tables \cs_new_protected_nopar:Npn \__csvsim_key_table:nn #1#2 { \tl_gset:Nn \g__csvsim_hook_table_begin_tl {#1} \tl_gset:Nn \g__csvsim_hook_table_end_tl {#2} } \keys_define:nn { csvsim } { before~table .tl_gset:N = \g__csvsim_before_table_tl, after~table .tl_gset:N = \g__csvsim_after_table_tl, table~head .tl_gset:N = \g__csvsim_table_head_tl, table~foot .tl_gset:N = \g__csvsim_table_foot_tl, generic~table~options .tl_gset:N = \g__csvsim_generic_table_options_tl, table~centered .choice:, table~centered .default:n = true, table~centered/true .code:n = { \tl_gset:Nn \g__csvsim_begin_table_center_tl {\begin{center}} \tl_gset:Nn \g__csvsim_end_table_center_tl {\end{center}} }, table~centered/false .code:n = { \tl_gclear:N \g__csvsim_begin_table_center_tl \tl_gclear:N \g__csvsim_end_table_center_tl }, _table_ .code:n = \__csvsim_key_table:nn #1, no~table .meta:n = { _table_ = {}{}, generic~table~options = , table~centered = false, }, generic~table .meta:n = { _table_ = { \g__csvsim_begin_table_center_tl \g__csvsim_before_table_tl \tl_gset:Nn \g__csvsim_tmpa_tl {\begin{#1}} \tl_gput_right:NV \g__csvsim_tmpa_tl \g__csvsim_generic_table_options_tl \g__csvsim_tmpa_tl \g__csvsim_table_head_tl } { \g__csvsim_table_foot_tl \end{#1} \g__csvsim_after_table_tl \g__csvsim_end_table_center_tl }, late~after~line = \\ }, generic~collected~table .meta:n = { collect~data, consume~collected~data = true, _table_ = { \tl_build_gput_right:Ne \g__csvsim_collect_tl { \exp_not:o { \g__csvsim_begin_table_center_tl } \exp_not:o { \g__csvsim_before_table_tl } \exp_not:n { \begin{#1} } \exp_not:o { \g__csvsim_generic_table_options_tl } \exp_not:o { \g__csvsim_table_head_tl } } } { \tl_build_gput_right:Ne \g__csvsim_collect_tl { \exp_not:o { \g__csvsim_table_foot_tl } \exp_not:n { \end{#1} } \exp_not:o { \g__csvsim_after_table_tl } \exp_not:o { \g__csvsim_end_table_center_tl } } }, late~after~line = \\, }, } \keys_define:nn { csvsim } { tabular .meta:n = { generic~table = tabular, generic~table~options = {{#1}}, }, centered~tabular .meta:n = { tabular = {#1}, table~centered }, longtable .meta:n = { generic~table = longtable, generic~table~options = {{#1}}, }, tabbing .meta:n = { generic~table = tabbing, generic~table~options =, late~after~last~line = }, centered tabbing .meta:n = { tabbing, table~centered }, tabularray .meta:n = { generic~collected~table = tblr, generic~table~options = {{#1}}, }, centered~tabularray .meta:n = { tabularray = {#1}, table~centered }, long~tabularray .meta:n = { generic~collected~table = longtblr, generic~table~options = {{#1}}, }, } \keys_define:nn { csvsim } { _autotab_ .meta:n = { file = #1, late~after~line = \\, command = \csvlinetotablerow }, _autotabular_ .meta:n = { _autotab_ = #1, late~after~last~line = \g__csvsim_table_foot_tl \end{tabular} \g__csvsim_after_table_tl, }, autotabular .meta:n = { _autotabular_ = #1, head, column~names~detection = false, after~head = \g__csvsim_before_table_tl \begin{tabular}{|*{\int_use:N\g_csvsim_columncount_int}{l|}} \g__csvsim_table_head_tl, table~head = \hline\csvlinetotablerow\\\hline, table~foot = \\\hline, }, autotabular* .meta:n = { _autotabular_ = #1, no~head, before~first~line = \g__csvsim_before_table_tl \begin{tabular}{|*{\int_use:N\g_csvsim_columncount_int}{l|}} \g__csvsim_table_head_tl, table~head = \hline, table~foot = \\\hline, }, autobooktabular .meta:n = { _autotabular_ = #1, head, column~names~detection = false, after~head = \g__csvsim_before_table_tl \begin{tabular}{*{\int_use:N\g_csvsim_columncount_int}{l}} \g__csvsim_table_head_tl, table~head = \toprule\csvlinetotablerow\\\midrule, table~foot = \\\bottomrule, }, autobooktabular* .meta:n = { _autotabular_ = #1, no~head, before~first~line = \g__csvsim_before_table_tl \begin{tabular}{*{\int_use:N\g_csvsim_columncount_int}{l}} \g__csvsim_table_head_tl, table~head = \toprule, table~foot = \\\bottomrule, }, _autolongtable_ .meta:n = { _autotab_ = #1, late~after~last~line = \end{longtable} \g__csvsim_after_table_tl, }, autolongtable .meta:n = { _autolongtable_ = #1, head, column~names~detection = false, after~head = \g__csvsim_before_table_tl \begin{longtable}{|*{\int_use:N\g_csvsim_columncount_int}{l|}} \g__csvsim_table_head_tl, table~head = \hline\csvlinetotablerow\\\hline\endhead \hline\endfoot, }, autolongtable* .meta:n = { _autolongtable_ = #1, no~head, before~first~line = \g__csvsim_before_table_tl \begin{longtable}{|*{\int_use:N\g_csvsim_columncount_int}{l|}} \g__csvsim_table_head_tl, table~head = \hline\endhead \hline\endfoot, }, autobooklongtable .meta:n = { _autolongtable_ = #1, head, column~names~detection = false, after~head = \g__csvsim_before_table_tl \begin{longtable}{*{\int_use:N\g_csvsim_columncount_int}{l}} \g__csvsim_table_head_tl, table~head = \toprule\csvlinetotablerow\\\midrule\endhead \bottomrule\endfoot, }, autobooklongtable* .meta:n = { _autolongtable_ = #1, no~head, before~first~line = \g__csvsim_before_table_tl \begin{longtable}{*{\int_use:N\g_csvsim_columncount_int}{l}} \g__csvsim_table_head_tl, table~head = \toprule\endhead \bottomrule\endfoot, }, _autotabularray_ .meta:n = { file = {#1}, command = \csvlinetotablerow, no~head, generic~collected~table = tblr, }, autotabularray .meta:n = { _autotabularray_ = {#1}, generic~table~options = { { row{1} = {font=\bfseries,preto=\MakeUppercase}, hline{1,Z} = {0.08em}, hline{2} = {0.05em}, } } }, autotabularray* .meta:n = { _autotabularray_ = {#1}, generic~table~options = { { hline{1,Z} = {0.08em}, } } }, _autolongtabularray_ .meta:n = { file = {#1}, command = \csvlinetotablerow, no~head, generic~collected~table = longtblr, }, autolongtabularray .meta:n = { _autolongtabularray_ = {#1}, generic~table~options = { { row{1} = {font=\bfseries,preto=\MakeUppercase}, hline{1,Z} = {0.08em}, hline{2} = {0.05em}, } } }, autolongtabularray* .meta:n = { _autolongtabularray_ = {#1}, generic~table~options = { { hline{1,Z} = {0.08em}, } } }, } \NewDocumentCommand \csvautotabular { s +O{} m } { \IfBooleanTF {#1} { \keys_set:nn { csvsim } { default, every~csv, autotabular*={#3}, #2} } { \keys_set:nn { csvsim } { default, every~csv, autotabular={#3}, #2} } \__csvsim_loop: } \NewDocumentCommand \csvautolongtable { s +O{} m } { \IfBooleanTF {#1} { \keys_set:nn { csvsim } { default, every~csv, autolongtable*={#3}, #2} } { \keys_set:nn { csvsim } { default, every~csv, autolongtable={#3}, #2} } \__csvsim_loop: } \NewDocumentCommand \csvautobooktabular { s +O{} m } { \IfBooleanTF {#1} { \keys_set:nn { csvsim } { default, every~csv, autobooktabular*={#3}, #2} } { \keys_set:nn { csvsim } { default, every~csv, autobooktabular={#3}, #2} } \__csvsim_loop: } \NewDocumentCommand \csvautobooklongtable { s +O{} m } { \IfBooleanTF {#1} { \keys_set:nn { csvsim } { default, every~csv, autobooklongtable*={#3}, #2} } { \keys_set:nn { csvsim } { default, every~csv, autobooklongtable={#3}, #2} } \__csvsim_loop: } \NewDocumentCommand \csvautotabularray { s +O{} m +o +o } { \keys_set:nn { csvsim } { default, every~csv } \IfBooleanTF {#1} { \keys_set:nn { csvsim } { autotabularray*={#3}, #2} } { \keys_set:nn { csvsim } { autotabularray={#3}, #2} } \IfValueT {#4} { \IfValueTF {#5} { \keys_set:nn { csvsim } { generic~table~options = { [ #4 ]{ #5 } } } } { \keys_set:nn { csvsim } { generic~table~options = { { #4 } } } } } \__csvsim_loop: } \NewDocumentCommand \csvautolongtabularray { s +O{} m +o +o } { \keys_set:nn { csvsim } { default, every~csv } \IfBooleanTF {#1} { \keys_set:nn { csvsim } { autolongtabularray*={#3}, #2} } { \keys_set:nn { csvsim } { autolongtabularray={#3}, #2} } \IfValueT {#4} { \IfValueTF {#5} { \keys_set:nn { csvsim } { generic~table~options = { [ #4 ]{ #5 } } } } { \keys_set:nn { csvsim } { generic~table~options = { { #4 } } } } } \__csvsim_loop: } %---- sorting \cs_new_protected_nopar:Npn \__csvsim_key_new_sorting_rule:nn #1#2 { \keys_define:nn { csvsim } { sort~by~#1 .meta:n = { sort~by={#2} }, } } \NewDocumentCommand \csvsortingrule { } { \__csvsim_key_new_sorting_rule:nn } \keys_define:nn { csvsim } { preprocessor .tl_set:N = \l__csvsim_preprocessor_tl, preprocessed~file .code:n = \__csvsim_set_filename:Nn \l__csvsim_ppfilename_str {#1}, csvsorter~command .code:n = \__csvsim_set_filename:Nn \l__csvsim_csvsorter_command_str {#1}, csvsorter~configpath .code:n = \__csvsim_set_filename:Nn\l__csvsim_csvsorter_configpath_str {#1}, csvsorter~log .code:n = \__csvsim_set_filename:Nn \l__csvsim_csvsorter_log_str {#1}, csvsorter~token .code:n = \__csvsim_set_filename:Nn \l__csvsim_csvsorter_token_str {#1}, no~preprocessing .meta:n = { preprocessor= }, sort~by .meta:n = { preprocessor= { \__csvsim_processor_csvsorter:nnn {#1} } }, new~sorting~rule .code:n = \__csvsim_key_new_sorting_rule:nn #1, new~sorting~rule .value_required:n = true , } \keys_set:nn { csvsim } { preprocessed~file = \c_sys_jobname_str _sorted._csv, csvsorter~command = csvsorter, csvsorter~configpath = ., csvsorter~log = csvsorter.log, csvsorter~token = \c_sys_jobname_str.csvtoken, } \cs_new_protected_nopar:Npn \__csvsim_processor_csvsorter:nnn #1#2#3 { \sys_if_shell_unrestricted:TF { \__csvsim_set_temp_filename:n { #1 } \msg_note:nnee { csvsimple }{ sort-info }{ #2 }{ \l__csvsim_temp_filename_str } \cs_if_exist:NF \g__csvsim_iow { \iow_new:N \g__csvsim_iow } \iow_open:Nn \g__csvsim_iow { \l__csvsim_csvsorter_token_str } \iow_now:Nn \g__csvsim_iow { \ExplSyntaxOn \msg_error:nn { csvsimple }{ sort-error } \ExplSyntaxOff } \iow_close:N \g__csvsim_iow \sys_shell_now:e { "\l__csvsim_csvsorter_command_str" \c_space_tl -c~ "\l__csvsim_csvsorter_configpath_str/\l__csvsim_temp_filename_str" \c_space_tl -l~ "\l__csvsim_csvsorter_log_str" \c_space_tl -t~ "\l__csvsim_csvsorter_token_str" \c_space_tl -i~ "#2" \c_space_tl -o~ "#3" \c_space_tl -q~1 } \file_input:n { \l__csvsim_csvsorter_token_str } } { \msg_error:nn { csvsimple }{ sort-shell-escape } } } %---- default \keys_define:nn { csvsim } { % default for reset default .meta:n = { file = unknown.csv, no~preprocessing, command = \csvline, column~names~reset, head, column~names~detection, check~column~count, head~to~column~names~prefix = , head~to~column~names = false, collect~data = false, consume~collected~data = false, column~count = 0, on~column~count~error =, range =, no~filter, before~filter =, after~filter =, before~line =, after~line =, late~after~line =, after~head =, late~after~head =, before~reading =, after~reading =, before~table =, after~table =, table~head =, table~foot =, no~table, separator = comma, respect~none, }, } \keys_set:nn { csvsim } { default }