% Ce fichier contient le code de l'extension "spreadtab" % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % \def\STname {spreadtab} % \def\STver {0.6} % % % \def\STdate {2025/02/27} % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % Author : Christian Tellechea % % Status : Maintained % % Maintainer : Christian Tellechea % % Email : unbonpetit@netc.fr % % Package URL: https://www.ctan.org/pkg/spreadtab % % Copyright : Christian Tellechea 2009-2025 % % Licence : Released under the LaTeX Project Public License v1.3c % % or later, see http://www.latex-project.org/lppl.txt % % Files : 1) spreadtab.sty % % 2) spreadtab-fr.tex % % 3) spreadtab-fr.pdf % % 4) spreadtab-en.tex % % 5) spreadtab-en.pdf % % 6) README % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \ProvidesPackage{spreadtab}[\STdate\space v\STver\space Spreadsheet features for table environments (CT)] \NeedsTeXFormat{LaTeX2e}[2022/06/01] \RequirePackage{xstring,simplekv} \expandafter\edef\csname ST_restorecatcode\endcsname{\catcode\number`\_=\the\catcode`\_\relax} \catcode`\_11 %------------------------------- %----- Macros généralistes ----- %------------------------------- \def\ST_letcs#1#2{\expandafter\let\expandafter#1\csname#2\endcsname} \def\ST_antefi#1\fi{\fi#1} \def\ST_id#1{#1} \def\ST_gobone#1{} \def\ST_gobtwo#1#2{} \def\ST_first_tonil#1#2\_nil{#1} \def\ST_remain_tonil#1#2\_nil{#2} \def\ST_gob_to_nil#1\_nil{} \def\ST_csdef#1{\expandafter\def\csname#1\endcsname} \long\def\ST_exec_first\fi\ST_exec_second#1#2{\fi#1} \long\def\ST_exec_second#1#2{#2} \def\ST_remove_to_nnil#1\_nnil{} \def\ST_cslet#1{\expandafter\let\csname#1\endcsname} \long\def\ST_swaparg#1#2{#2{#1}} \long\def\ST_e_second#1#2{\expandafter\ST_swaparg\expandafter{#2}{#1}} \long\def\ST_ee_second#1#2{\expandafter\expandafter\expandafter\ST_swaparg\expandafter\expandafter\expandafter{#2}{#1}} \def\ST_exp_two_args#1#2#3{\ST_e_second{\ST_e_second{#1}{#2}}{#3}} \def\ST_add_tomacro#1#2{\expandafter\def\expandafter#1\expandafter{#1#2}} \def\ST_eadd_tomacro#1#2{\expandafter\ST_add_tomacro\expandafter#1\expandafter{#2}} \def\ST_xadd_tomacro#1#2{\ST_eadd_tomacro#1{\expanded{#2}}} \long\def\ST_ifx#1{\ifx#1\ST_exec_first\fi\ST_exec_second} \long\def\ST_ifempty#1{\if\expandafter\relax\detokenize{#1}\relax\ST_exec_first\fi\ST_exec_second} \def\ST_ifnum#1{\ifnum#1\ST_exec_first\fi\ST_exec_second} \def\ST_if#1{\if#1\ST_exec_first\fi\ST_exec_second} \def\ST_ifcsname#1{\ifcsname#1\endcsname\ST_exec_first\fi\ST_exec_second} \ST_letcs\ST_ifnextchar{@ifnextchar} \ST_letcs\ST_ifstar{@ifstar} \def\ST_quark{\ST_quark}% quark de spreadtab \def\ST_grab_int#1{% partage #1 en une partie entière et une aprtie restante \ST_e_second\ST_grab_int_a{\expanded{#1}}% } \def\ST_grab_int_a#1#2#3{% #1=argument #2=macro recevant l'entier #3=macro recevant le reste \ST_ifempty{#1} {% \let#2\empty \let#3\empty } {% \ST_if_first_is{#1}{-} {% \expandafter\ST_grab_int_b\ST_gobone#1\_nil-#2#3% } {% \ST_if_first_is{#1}{+} {\expandafter\ST_grab_int_b\ST_gobone} {\ST_grab_int_b}% #1\_nil+#2#3% }% }% } \def\ST_grab_int_b#1\_nil#2{% #2=signe \afterassignment\ST_grab_int_c \ST_intpart_cnt#20#1\relax } \def\ST_grab_int_c#1\relax#2#3{% #2=macro recevant l'entier #3=macro recevant le reste \edef#2{\number\ST_intpart_cnt}% \def#3{#1}% } \def\ST_if_integer#1#2#3{% \ST_ifempty{#1} {#3} {% \ST_grab_int{#1}\ST_temp_int\ST_afterinteger \ST_ifx{\ST_afterinteger\empty}{#2}{#3}% }% } %------------------------------------------------- %----- liste des différentes macro-fonctions ----- %------------------------------------------------- \def\ST_functions_with_num_arg{% liste des fonctions dont l'argument est numérique id,ifeq,ifgt,iflt,numtofrshortdate,numtoengshortdate,numtofrlongdate,gcd,lcm,% numtoenglongdate,numtofrmonth,numtoengmonth,numtofrday,numtoengday% } \def\ST_functions_with_text_arg{% liste des fonctions dont l'argument est un texte frshortdatetonum,engshortdatetonum,englongdatetonum,frlongdatetonum,scitodec,tag,row,col,cell,value% } \def\ST_functions_no_calc_arg{% liste des fonctions dont l'argument ne doit pas être calculé ifeq,ifgt,iflt,gcd,lcm,value% } \def\ST_functions_with_assign_argument{% liste des fonctions dont l'argument est une variable -> il ne faut donc pas aller chercher des références dans l'argument tag,row,col,cell,value% } \def\ST_functions_with_textresult{% liste des fonctions dont le résultat est un texte numtofrshortdate,numtoengshortdate,numtofrlongdate,numtoenglongdate,% numtofrmonth,numtoengmonth,numtofrday,numtoengday% } \def\ST_functions_with_range_arg{% liste des fonctions admettant des plages de cellules sum,sumprod% } \edef\ST_functions_list{% liste totale des fonctions \ST_functions_with_range_arg,\ST_functions_with_num_arg,\ST_functions_with_text_arg,% } %------------------------------- %----- interface avec l3fp ----- %------------------------------- \def\ST_fpeval#1#2{\edef#1{\fpeval{#2}}} \ST_letcs\ST_fp_compare{fp_compare:nNnTF}% TODO à supprimer \def\ST_ifgt#1#2{\ST_fp_compare{#1}>{#2}}% TODO à supprimer \def\ST_iflt#1#2{\ST_fp_compare{#1}<{#2}}% TODO à supprimer \def\ST_ifeq#1#2{\ST_fp_compare{#1}={#2}}% TODO à supprimer %----------------------- %----- allocations ----- %----------------------- \newcount\ST_cnt \newcount\ST_col_cnt \newcount\ST_col_cnt_a \newcount\ST_row_cnt \newcount\ST_row_cnt_a \newcount\ST_intpart_cnt \newif\ifST_hiddencol \newif\ifST_if_show_debug_tab \ST_if_show_debug_tabtrue \newif\ifST_colortblloaded \newif\ifST_gcd %----------------------------- %----- Messages d'erreur ----- %----------------------------- \def\ST_emit_message#1{% \ifboolKV[\STname]{messages} {\message{#1}} {}% } \def\STseedoc_a{Please, read the manual.} \def\ST_circular_reference{% \ST_coord_toref\ST_coord \ST_calc_dep_tree \PackageError\STname{% Circular reference found in cell \ST_coord!^^J Here is its dependant cells: \ST_dep_tree }\STseedoc_a } \def\ST_undefined_cell{% \ST_coord_toref\ST_temp_callcell \ST_coord_toref\ST_coord \PackageError\STname{% Undefined reference!^^J Cell \ST_temp_callcell\space contains a reference to an undefined cell: \ST_coord% }\STseedoc_a } \def\ST_zerocodecell_cell{% \ST_coord_toref\ST_temp_callcell \ST_coord_toref\ST_coord \PackageError\STname{% A reference to a non-numeric or empty cell is not allowed!^^J Cell \ST_temp_callcell\space contains a reference to an empty or text cell: \ST_coord% }\STseedoc_a } \def\ST_multicol_cell{% \ST_coord_toref\ST_temp_callcell \ST_coord_toref\ST_coord \PackageError\STname{% Cell \ST_temp_callcell\space contains a reference to a merged \string\multicolumn\space cell: \ST_coord }\STseedoc_a } \def\ST_illegal_relativeref{% \edef\ST_temp_callcell{\ST_current_colnumber,\ST_current_rownumber}% \ST_coord_toref\ST_temp_callcell \PackageError\STname{% Illegal relative reference found in cell \ST_temp_callcell!% }\STseedoc_a } \def\ST_illegal_ref{% \PackageError\STname{% Illegal reference in "save list"!% }\STseedoc_a} \def\ST_unmatch_matrixdim{% \edef\ST_temp_callcell{\ST_current_colnumber,\ST_current_rownumber}% \ST_coord_toref\ST_temp_callcell \PackageError\STname{% Somprod dimension of matrix do not match in cell \ST_temp_callcell! }\STseedoc_a } \def\ST_invalid_date{% \edef\ST_temp_callcell{\ST_current_colnumber,\ST_current_rownumber}% \ST_coord_toref\ST_temp_callcell \PackageError\STname{% Invalid date in cell \ST_temp_callcell.% }\STseedoc_a } \def\ST_invalid_range{% \edef\ST_temp_callcell{\ST_current_colnumber,\ST_current_rownumber}% \ST_coord_toref\ST_temp_callcell \PackageError\STname{% Invalid range in cell \ST_temp_callcell.% }\STseedoc_a } \def\ST_invalid_copy{% \edef\ST_temp_callcell{\ST_current_colnumber,\ST_current_rownumber}% \ST_coord_toref\ST_temp_callcell \PackageError\STname{% Numeric field marker found, \string\STcopy\space forbidden in cell \ST_temp_callcell.% }\STseedoc_a } \def\ST_unknown_tag{% \edef\ST_temp_callcell{\ST_current_colnumber,\ST_current_rownumber}% \ST_coord_toref\ST_temp_callcell \PackageError\STname{% Undefined tag in cell \ST_temp_callcell.% }\STseedoc_a } \def\ST_illegal_copy{% \PackageError\STname{% Illegal reference in copied formula!% }\STseedoc_a } %--------------------------------- %----- macros de coordonnées ----- %--------------------------------- \def\ST_calc_dep_tree{% transforme la chaine de cellules "(1,3)(4,5)(3,2)(1,3)" rencontré en référence circulaire en "A3-D5-C2-A3" \let\ST_dep_tree\empty \ST_calc_dep_tree_a } \def\ST_calc_dep_tree_a{% \ST_mid\ST_dependance_tree()\ST_currentref \ST_right\ST_dependance_tree)\ST_dependance_tree \ST_coord_toref\ST_currentref \ST_eadd_tomacro\ST_dep_tree\ST_currentref \ST_ifx{\empty\ST_dependance_tree} {} {% \ST_add_tomacro\ST_dep_tree-% \ST_calc_dep_tree_a }% } \def\ST_coord_toref#1{% transforme la séquence de contrôle #1 qui contient par exemple «4,5» en «D5» \ST_split#1,\ST_temp_a#1% \edef#1{% \ifcase\ST_temp_a \or A\or B\or C\or D\or E\or F\or G\or H\or I\or J\or K\or L\or M\or N\or O\or P\or Q\or R\or S\or T\or U\or V\or W\or X\or Y\or Z \fi #1% }% } %--------------------------------------------- %----- Macros de manipulation de chaines ----- %--------------------------------------------- \def\ST_eat_to_nil{% mange tous jusqu'à \ST_nil. Méthode LENTE privilégiée car il peut y avoir des ) de catcode 2 isolées \afterassignment\ST_eat_to_nil_a \let\ST_toks= } \def\ST_eat_to_nil_a{% \unless\ifx\ST_toks\ST_nil \ST_antefi\ST_eat_to_nil \fi } \def\ST_split_at_first_car#1{% \expandafter\ST_split_at_first_car_a#1\_nil } \def\ST_split_at_first_car_a#1#2\_nil#3#4{% \def#3{#1}% \def#4{#2}% } \def\ST_subst_decimal_sep#1{% dans la sc #1, remplace le . par le séparateur décimal \ST_if_instr#1. {\ST_e_second{\ST_subst_decimal_sep_a#1}{\ST_decimal_sep}} {}% } \def\ST_subst_decimal_sep_a#1#2{% \def\ST_subst_decimal_sep_b##1.##2\_nil{\def#1{##1#2##2}}% TODO: externaliser \expandafter\ST_subst_decimal_sep_b#1\_nil } \def\ST_remove_first_spaces#1{% enlève tous les espaces de la sc #1 et assigne le résultat à #1 \IfBeginWith#1\space% TODO : à eé-écrire ou à supprimer au profit de \Skv_stripsp {% \StrGobbleLeft#11[#1]% \ST_remove_first_spaces{#1}% }% {}% } \def\ST_keep_first_car#1{% on ne garde dans la sc #1 que le 1er caractère de la sc #1 ou on enlève les accolades \expandafter\ST_split_at_first_car_a#1\_nil#1\ST___temp } \def\ST_assign_first_car#1#2#3{% assigne à la sc #3 l'argument qui suit #2 dans le développement de la sc #1 \def\ST_assign_first_car_a##1#2##2##3\_nil{\def#3{##2}}% \expandafter\ST_assign_first_car_a#1\_nil } \def\ST_assign_third_car#1#2#3{% assigne à la sc #3 le 3è argument qui suit #2 dans le développement de la sc #1 \def\ST_assign_third_car_a##1#2##2##3##4##5\_nil{\def#3{##4}}% \expandafter\ST_assign_third_car_a#1\_nil } \def\ST_if_instr#1#2{% est ce que la sc #1 contient la sc #2 ? \ST_exp_two_args\ST_if_instr_a{#1}{#2}% } \def\ST_if_instr_a#1#2#3#4{% \def\ST_if_instr_b##1#2##2\_nil{\ST_ifempty{##2}{#4}{#3}}% \ST_if_instr_b#1\__nil#2\_nil } \def\ST_if_first_is#1#2{% la sc #1 commence par les caractères #2 ? \def\ST_if_first_is_a##1#2##2\_nil{\ST_ifempty{##1}}% \expandafter\ST_if_first_is_a#1\__nil#2\_nil } \def\ST_split#1#2#3#4{% Coupe la sc #1 au caractère #2 : ce qui est avant est assigné à #3 et ce qui est après à #4 \def\ST_split_a##1#2##2\_nil{% \def#3{##1}% \def#4{##2}% }% \expandafter\ST_split_a#1\_nil } \def\ST_left#1#2#3{% Dans la sc #1, assigne ce qui est avant le développement de la sc #2 à la sc #3 \ST_exp_two_args\ST_left_a{#1}{#2}#3% } \def\ST_left_a#1#2#3{% \def\ST_left_b##1#2##2\_nil{\def#3{##1}}% \ST_left_b#1\_nil } \def\ST_right#1#2#3{% Dans la sc #1, assigne ce qui est après le développement de la sc #2 à la sc #3 \ST_exp_two_args\ST_right_a{#1}{#2}#3% } \def\ST_right_a#1#2#3{% \def\ST_right_b##1#2##2\_nil{\def#3{##2}}% \ST_right_b#1\_nil } \def\ST_mid#1#2#3#4{% Dans la sc #1, assigne à la sc #4 ce qui est entre les caractères #2 et #3 \def\ST_mid_a##1#2##2#3##3\_nil{\def#4{##2}}% \expandafter\ST_mid_a#1\_nil } \def\ST_subst_once#1#2#3{% Dans la sc #1, substitue la première occurrence du pattern #2 par le pattern #3 \def\ST_subst_once_a##1#2##2\_nil{\def#1{##1#3##2}}% \ST_if_instr#1{\empty#2} {\expandafter\ST_subst_once_a#1\_nil} {}% } \def\ST_subst#1#2#3{% Dans la sc #1, substitue le pattern #2 par le pattern #3 \def\ST_subst_a##1#2##2\_nnil{% \ST_ifempty{##2} {% \def#1{##1}% \ST_remove_to_nnil } {% \ST_subst_a }% ##1#3##2\_nnil }% \expandafter\ST_subst_a#1#2\_nnil } \def\ST_eargs_subst#1#2#3{% \ST_exp_two_args{\ST_subst#1}{#2}{#3}% } \def\ST_remove_sp#1{% \ST_subst#1{ }{}% } \def\ST_ifvalid_csname#1#2#3{% \ST_ifcsname{#1} {\ST_e_second\ST_ifx{\csname#1\endcsname\ST_quark}{#3}{#2}} {#3}% } %------------------------------ %----- Lecture du tableau ----- %------------------------------ \def\ST_read_tab{% lit le tableau : considère que \\ sépare les lignes \def\ST_total_colnumber{0}% \ST_row_cnt0 \ST_search_hline% on met de côté la (ou les) ligne supérieure du tableau \ST_read_tab_a } \def\ST_read_tab_a{% \advance\ST_row_cnt1 \ST_if_instr\ST_tab{\ST_eol}% si contient \\, il reste encore des lignes {% \ST_e_second{\ST_split\ST_tab}{\ST_eol}\ST_current_row\ST_tab \ST_csdef{endrow_\number\ST_row_cnt\expandafter}\expandafter{\ST_eol}% est la fin de cette ligne, pour l'instant \ST_if_first_is\ST_tab[% on prend en compte l'éventuel argument optionnel de \\ {% \ST_mid\ST_tab[]\ST_temp_a% prend ce qui est entre crochet \ST_if_instr\ST_temp_a,% si c'est une référence (on teste juste la présence de la virgule, ça devrait suffire) {}% on ne fait rien {% \ST_split\ST_tab]\ST_temp_a\ST_tab% sinon, coupe au crocher fermant \expandafter\ST_eadd_tomacro\csname endrow_\number\ST_row_cnt\endcsname{\ST_temp_a]}% ajoute l'argument optionnel à la fin de ligne }% }% {}% \ST_search_hline% on va purger les hlines et les mettre dans la fin de ligne \ST_ifx{\ST_tab\empty} {% \let\ST_next_readrows\relax \edef\ST_total_rownumber{\number\ST_row_cnt}% } {% \let\ST_next_readrows\ST_read_tab_a }% }% {% \let\ST_current_row\ST_tab% plus de ligne ? on prend le tout c-à-d la ligne incomplète qui finit le tableau \let\ST_next_readrows\relax \edef\ST_total_rownumber{\number\ST_row_cnt}% }% \ST_if_instr\ST_current_row{\empty\SThiderow}% il est demandé de masquer la colonne ? {% \edef\ST_row_skiplist{(\number\ST_row_cnt)\ST_row_skiplist}% on ajoute le numéro de ligne à masquer à la skiplist \StrDel\ST_current_row{\empty\SThiderow}[\ST_current_row]% }% {}% \ST_col_cnt0 \let\STmulticol_number\empty \let\ST_nextcell\empty \ST_read_cells% lit les cellules de la ligne courante \ST_current_row \ST_next_readrows } \def\ST_read_cells{% divise la ligne contenue dans \ST_current_row en cellules \advance\ST_col_cnt1 \ST_if_instr\ST_current_row&% {% \ST_split\ST_current_row&\ST_current_cell\ST_current_row \let\ST_next_readcells\ST_read_cells }% {% \let\ST_current_cell\ST_current_row \let\ST_next_readcells\relax \ifnum\ST_col_cnt>\ST_total_colnumber \edef\ST_total_colnumber{\number\ST_col_cnt}% \fi }% \ST_if_instr\ST_current_cell{\empty\SThidecol}% on doit masquer cette colonnes ? {% \ST_if_instr\ST_col_skiplist{\expandafter(\number\ST_col_cnt)}% ça a déjà été demandé ? {}% on fait rien {% \edef\ST_col_skiplist{(\number\ST_col_cnt)\ST_col_skiplist}% sinon -> ajout à la skiplist \ifnum\ST_col_cnt>\ST_last_skipcol \edef\ST_last_skipcol{\number\ST_col_cnt}% \fi }% \StrDel\ST_current_cell{\empty\SThidecol}[\ST_current_cell]% }% {}% \exploregroups \ST_if_instr\ST_current_cell{\empty\multicolumn}% tester la présence d'un \multicol {\ST_assign_first_car\ST_current_cell\multicolumn\STmulticol_number}% {\let\STmulticol_number\empty}% \IfSubStr\ST_current_cell\ST_numeric_mark% il y a un marqueur de champ numérique ? {% \IfSubStr\ST_current_cell{\empty\STcopy}\ST_invalid_copy{}% s'il y a un \STcopy, erreur (pas de champ numérique et de \STcopy dans une même cellule) \StrBehind\ST_current_cell\ST_numeric_mark[\ST_current_formula]% \noexploregroups \StrChar\ST_current_formula1[\ST_temp_a]% \ST_temp_a contient {} par ":=" \noexploregroups \ST_keep_first_car\ST_temp_a% dans la formule, on enlève les accolades \ST_remove_sp\ST_temp_a% et tous les espaces \ST_ifx{\ST_temp_a\empty}% \ST_temp_a contient la formule : si la formule est vide {% \ST_ifx{\ST_copy_list\empty}% pas de copylist ? {% \ST_csdef{code_\number\ST_col_cnt _\number\ST_row_cnt}{0}% met le code à 0 } {% si la copylist existe \ST_look_in_copy_list{\number\ST_col_cnt}{\number\ST_row_cnt}\ST_celltocopy% on cherche si la cellule en cours est dans une plage de copie \ST_ifx{\ST_celltocopy\empty} {% \ST_csdef{code_\number\ST_col_cnt _\number\ST_row_cnt}{0}% si c'est non, met le code à 0 \StrDel[1]\ST_current_cell\ST_numeric_mark[\ST_current_cell]% on supprime aussi ":=" } {% \ST_cslet{formula_\number\ST_col_cnt _\number\ST_row_cnt}\ST_celltocopy% il y a une cellule à copier : on l'assigne au champ numérique \ST_csdef{code_\number\ST_col_cnt _\number\ST_row_cnt}{1}% et on met le code à 1 }% }% }% {% la formule n'est pas vide \ST_cslet{formula_\number\ST_col_cnt _\number\ST_row_cnt}\ST_temp_a% et on assigne à la formule \ST_csdef{code_\number\ST_col_cnt _\number\ST_row_cnt}{1}% code 1 à priori }% }% ci dessous, il n'y a pas de marqueur de champ numérique {% \IfSubStr\ST_current_cell\ST_text_mark% si c'est une cellule de texte {% \StrDel\ST_current_cell\ST_text_mark[\ST_current_cell]% on le(s) supprime les flags \ST_csdef{code_\number\ST_col_cnt _\number\ST_row_cnt}{0}% met le code à 0 }% ci dessous, ce n'est pas une cellule texte, c'est donc une cellule purement champ numérique sans marqueur {% \StrDel\ST_current_cell\space[\ST_temp_a]% \ST_ifx{\empty\ST_temp_a}% il n'y a que des espaces, c'est donc une cellule vide {% \ST_ifx{\ST_copy_list\empty}% pas de copylist ? {% \ST_csdef{code_\number\ST_col_cnt _\number\ST_row_cnt}{0}% met le code à 0 } {%% si la copylist existe \ST_look_in_copy_list{\number\ST_col_cnt}{\number\ST_row_cnt}\ST_celltocopy% on cherche si la cellule en cours est dans un oplage de copie \ST_ifx{\ST_celltocopy\empty} {% \ST_csdef{code_\number\ST_col_cnt _\number\ST_row_cnt}{0}% si c'est non, met le code à 0 } {% \let\ST_current_cell\ST_numeric_mark% il y a une cellule à copier ici \ST_cslet{formula_\number\ST_col_cnt _\number\ST_row_cnt}\ST_celltocopy% on l'assigne au champ numérique \ST_csdef{code_\number\ST_col_cnt _\number\ST_row_cnt}{1}% et on met le code à 1 }% }% }% ici, la cellule est composée d'une champ numérique sans marqueur {% \ST_csdef{code_\number\ST_col_cnt _\number\ST_row_cnt}{1}% toute la cellule est considérée comme champ numérique \ST_remove_first_spaces\ST_current_cell \ST_if_instr\ST_current_cell{\empty\STcopy}% {% \ST_find_copy_args\ST_current_cell\ST_copyrange\ST_copyformula% on chope les arguments de \STcopy \ST_scan_copy_offset\ST_copyrange% cherche les décalages \edef\ST_copyrange{\ST_hoffest,\ST_voffest}% et affecte sous forme a,b où a et b sont des nombres ou sont vides \ST_e_second{\def\STtocopylist}{\expandafter|\ST_copyformula|}% \STtocopylist est le nouvel élément à ajouter à la copylist \ST_xadd_tomacro\STtocopylist{[\number\ST_col_cnt,\number\ST_row_cnt]}% \ST_eadd_tomacro\STtocopylist{\expandafter(\ST_copyrange)}% \ST_eadd_tomacro\STtocopylist\ST_copy_list% ajoute la copylist à la fin \let\ST_copy_list\STtocopylist% et l'assigne à copylist \ST_transpose_formula00\ST_copyformula\ST_copyformula% on transpose éventuellement pour gérer les "!" \ST_cslet{formula_\number\ST_col_cnt _\number\ST_row_cnt}\ST_copyformula% affecte la formule inchangée au champ numérique courant }% {% \ST_cslet{formula_\number\ST_col_cnt _\number\ST_row_cnt}\ST_current_cell% et on assigne toute la cellule \let\ST_current_cell\ST_numeric_mark% et on met le flag pour la formule }% }% }% }% \noexploregroups \ifnum\csname code_\number\ST_col_cnt _\number\ST_row_cnt\endcsname>0 % si le code est > 0 \ST_try_calc_cell{\number\ST_col_cnt}{\number\ST_row_cnt}% on essaie de calculer la cellule \fi \ST_cslet{text_\number\ST_col_cnt _\number\ST_row_cnt}\ST_current_cell \ST_ifx{\empty\STmulticol_number}% si c'est une cellule qui contient \multicolumn {} {% \loop% on met tous les codes des cellules fusionées qui suivent la cellule en cours à -1 \ifnum\STmulticol_number>1 \edef\STmulticol_number{\number\numexpr\STmulticol_number-1}% \advance\ST_col_cnt1 \ST_csdef{code_\number\ST_col_cnt _\number\ST_row_cnt}{-1}% -1 = cellule multicol \repeat }% \ST_next_readcells } % On va essayer de purger dans #1 toutes les \hline, \clines, \hhline etc, et ajouter tout ce beau monde % et leur evéntuels arguments dans des sc spéciales (par ex \endrow_3 pour la fin de la 3e ligne.) \def\ST_search_hline{% \ST_ifvalid_csname{endrow_\number\ST_row_cnt}% {} {\ST_cslet{endrow_\number\ST_row_cnt}\empty}% \ST_search_hline_a } \def\ST_search_hline_a{% \ST_remove_first_spaces\ST_tab% on enlève les espaces au début \StrChar\ST_tab1[\ST_temp_a]% \ST_temp_a est le 1er car \let\ST_next\ST_search_hline_a \IfStrEqCase\ST_temp_a{% on envisage tous les cas de tracé de ligne horizontale {\empty\hline} {% \StrGobbleLeft\ST_tab1[\ST_tab]% \expandafter\ST_add_tomacro\csname endrow_\number\ST_row_cnt\endcsname\hline }% {\empty\cline} {% \StrSplit\ST_tab2\ST_temp_a\ST_tab \expandafter\ST_eadd_tomacro\csname endrow_\number\ST_row_cnt\endcsname\ST_temp_a }% {\empty\hhline} {% \StrSplit\ST_tab2\ST_temp_a\ST_tab \expandafter\ST_eadd_tomacro\csname endrow_\number\ST_row_cnt\endcsname\ST_temp_a }% {\empty\noalign} {% \StrSplit\ST_tab2\ST_temp_a\ST_tab \expandafter\ST_eadd_tomacro\csname endrow_\number\ST_row_cnt\endcsname\ST_temp_a }% {\empty\toprule} {% les commandes de booktabs \StrSplit\ST_tab1\ST_temp_a\ST_tab% chope le 1er lexème : la commande \toprule \IfBeginWith\ST_tab[ {% \StrBefore\ST_tab][\ST_temp_b]% \ST_eadd_tomacro\ST_temp_a{\ST_temp_b]}% \StrBehind\ST_tab][\ST_tab]% } {}% \expandafter\ST_eadd_tomacro\csname endrow_\number\ST_row_cnt\endcsname\ST_temp_a }% {\empty\midrule} {% \StrSplit\ST_tab1\ST_temp_a\ST_tab% chope le 1er lexème : la commande \midrule \IfBeginWith\ST_tab[ {\StrBefore\ST_tab][\ST_temp_b]% \ST_eadd_tomacro\ST_temp_a{\ST_temp_b]}% \StrBehind\ST_tab][\ST_tab]% } {}% \expandafter\ST_eadd_tomacro\csname endrow_\number\ST_row_cnt\endcsname\ST_temp_a }% {\empty\bottomrule} {% \StrSplit\ST_tab1\ST_temp_a\ST_tab% chope le 1er lexème : la commande \bottomrule \IfBeginWith\ST_tab[ {% \StrBefore\ST_tab][\ST_temp_b]% \ST_eadd_tomacro\ST_temp_a{\ST_temp_b]}% \StrBehind\ST_tab][\ST_tab]% } {}% \expandafter\ST_eadd_tomacro\csname endrow_\number\ST_row_cnt\endcsname\ST_temp_a }% {\empty\cmidrule} {% \StrSplit\ST_tab1\ST_temp_a\ST_tab% chope le 1er lexème : la commande \cmidrule \IfBeginWith\ST_tab[ {% \StrBefore\ST_tab][\ST_temp_b]% \ST_eadd_tomacro\ST_temp_a{\ST_temp_b]}% \StrBehind\ST_tab][\ST_tab]% } {}% \IfBeginWith\ST_tab( {% \StrBefore\ST_tab)[\ST_temp_b]% \ST_eadd_tomacro\ST_temp_a{\ST_temp_b)}% \StrBehind\ST_tab)[\ST_tab]% } {}% \StrSplit\ST_tab1\ST_temp_b\ST_tab% chope l'argument obligatoire : {a-b} \ST_eadd_tomacro\ST_temp_a\ST_temp_b% l'ajoute à \ST_temp_b \expandafter\ST_eadd_tomacro\csname endrow_\number\ST_row_cnt\endcsname\ST_temp_a% et on ajoute le tout à endrow }% {\empty\addlinespace} {% \StrSplit\ST_tab1\ST_temp_a\ST_tab% chope le 1er lexème : la commande \addlinespace \IfBeginWith\ST_tab[ {\StrBefore\ST_tab][\ST_temp_b]% \ST_eadd_tomacro\ST_temp_a{\ST_temp_b]}% \StrBehind\ST_tab][\ST_tab]% } {}% \expandafter\ST_eadd_tomacro\csname endrow_\number\ST_row_cnt\endcsname\ST_temp_a }% {\empty\morecmidrules} {% \StrGobbleLeft\ST_tab1[\ST_tab]% \expandafter\ST_add_tomacro\csname endrow_\number\ST_row_cnt\endcsname\morecmidrules }% {\empty\specialrule} {% \StrSplit\ST_tab4\ST_temp_a\ST_tab \expandafter\ST_eadd_tomacro\csname endrow_\number\ST_row_cnt\endcsname\ST_temp_a }% }[% \let\ST_next\relax ]% \ST_next } % Cette macro transpose toutes les références (absolues et relatives) de la sc #3. % Le décalage est de #1 (nombre signé) pour les colonnes et de #2 (nombre signé) pour les lignes. % La sc #4 recoit la formule transposée. \def\ST_transpose_formula#1#2#3#4{% \def\ST_addcol{#1}% \def\ST_addrow{#2}% \let\ST_temp_formula#3% \let\ST_initial_formula#3% \let\ST_transposed_formula\empty \ST_transpose_formula_a \let#4\ST_transposed_formula } \def\ST_transpose_formula_a{% \ST_ifx{\empty\ST_temp_formula} {} {% tant que l'on n'a pas parcouru \ST_temp_formula \ST_split_at_first_car\ST_temp_formula\ST_firstcar\ST_temp_formula% prend le 1er car de \ST_temp_formula \ST_if{\expandafter\noexpand\ST_tranpose_char\expandafter\noexpand\ST_firstcar}% si ce caractère est un ! {% \def\ST_addcol_{0} % pas de tranposition sur la colonne \ST_split_at_first_car\ST_temp_formula\ST_firstcar\ST_temp_formula% on prend le premier caractère qui suit le ! } {% \let\ST_addcol_\ST_addcol% sinon, on copie le vecteur }% \ST_e_second\ST_if_car_is_letter\ST_firstcar% est-ce que le 1er car est une lettre ? {% \ST_if{\expandafter\noexpand\ST_tranpose_char\expandafter\expandafter\expandafter\noexpand\expandafter\ST_first_tonil\ST_temp_formula\_nil}% le caractère suivant est un "!" ? {% \def\ST_addrow_{0}% pas de tranposition sur la ligne \ST_e_second{\ST_e_second{\def\ST_temp_formula}}{\expandafter\ST_remain_tonil\ST_temp_formula\_nil}% on prend ce qui est après le ! } {% \let\ST_addrow_\ST_addrow% sinon, on copie le vecteur }% \ST_grab_int\ST_temp_formula\ST_int_part\ST_temp_formula \ST_ifnum{\ST_int_part>0 }% si ce nombre est plus grand que 0 -> référence valide {% \lowercase\expandafter{\expandafter\def\expandafter\ST_firstcar\expandafter{\ST_firstcar}}% met en minuscules \edef\ST_firstcar{\number\numexpr\expandafter`\ST_firstcar-`a+1+\ST_addcol_}% est le numéro de la colonne \ifnum\ST_firstcar<1 % erreur de copie ? \ST_illegal_copy \fi% erreur de copie ? \ST_xadd_tomacro\ST_transposed_formula{% \ifcase\ST_firstcar \or a\or b\or c\or d\or e\or f\or g\or h\or i\or j\or k\or l\or m\or n\or o\or p\or q\or r\or s\or t\or u\or v\or w\or x\or y\or z \fi \number\numexpr\ST_int_part+\ST_addrow_}% \ifnum\numexpr\ST_int_part+\ST_addrow_<1 % erreur de copie ? \ST_illegal_copy \fi } {% \ST_eadd_tomacro\ST_transposed_formula\ST_firstcar }% }% {% \ST_if{[\expandafter\noexpand\ST_firstcar}% si le 1er car est [ {% \ST_left\ST_temp_formula]\ST_temp_ref% on prend ce qui est entre crochet \ST_right\ST_temp_formula]\ST_temp_formula% pour la suite, on prend ce qui après le crochet \ST_left\ST_temp_ref,\ST_rel_num% ce qui est avant la virgule \ST_if{\expandafter\noexpand\ST_tranpose_char\expandafter\expandafter\expandafter\noexpand\expandafter\ST_first_tonil\ST_rel_num\_nil}% commence par un "!" ? {% \let\ST_addcol_\ST_addcol% compensation pour conserver la cellule initiale \ST_e_second{\ST_e_second{\def\ST_rel_num}}{\expandafter\ST_remain_tonil\ST_rel_num\_nil}% on prend ce qui est après le ! } {% \def\ST_addcol_{0}% sinon, on n'ajoute rien }% \ST_if_integer\ST_rel_num {% \edef\ST_addcol_{\number\numexpr\ST_rel_num-\ST_addcol_}% \ST_right\ST_temp_ref,\ST_rel_num% ce qui est après la virgule \ST_if{\expandafter\noexpand\ST_tranpose_char\expandafter\expandafter\expandafter\noexpand\expandafter\ST_first_tonil\ST_rel_num\_nil}% commence par un "!"? {% \let\ST_addrow_\ST_addrow% on compense pour conserver la cellule initiale \ST_e_second{\ST_e_second{\def\ST_rel_num}}{\expandafter\ST_remain_tonil\ST_rel_num\_nil}% on prend ce qui est après le ! } {% \def\ST_addrow_{0}% sinon, on n'ajoute rien }% \ST_if_integer\ST_rel_num {\ST_xadd_tomacro\ST_transposed_formula{[\ST_addcol_,\number\numexpr\ST_rel_num-\ST_addrow_]}}% {\ST_illegal_relativeref}% }% \ST_illegal_relativeref } {% \ST_eadd_tomacro\ST_transposed_formula\ST_firstcar }% }% \ST_transpose_formula_a }% } % Cherche dans la sc #1 les 3 arguments qui se trouvent après \STcopy % Affecte le 1er à #2, le 2è à #3 et le 3è à #4 \def\ST_find_copy_args#1#2#3{% \def\ST_find_copy_args_a##1\STcopy##2##3##4\_nil{% \def#1{##1}% \ST_eadd_tomacro#1{\ST_numeric_mark##4}% dans #1,supprime \STcopy{}{} et le remplace par := \def#2{##2}% \def#3{##3}% }% \expandafter\ST_find_copy_args_a#1\_nil } % teste si #1 est dans l'intervalle [#3,#3+#5] et si #2 est dans [#4,#4+#6] % si #5 est vide, c'est l'intervalle [#3,+inf] et si #6 est vide, c'est [#4,+inf] \def\ST_if_intereval#1#2[#3,#4](#5,#6){% \csname @% \ifnum#1<#3 second% \else \ifnum#2<#4 second% \else \ifx\empty#5\empty \ifx\empty#6\empty first% \else \ifnum#2>\numexpr#4+#6\relax second\else first\fi \fi \else \ifnum#1>\numexpr#3+#5\relax second% \else \ifx\empty#6\empty first% \else \ifnum#2>\numexpr#4+#6\relax second\else first\fi \fi \fi \fi \fi \fi oftwo\endcsname } % Regarde dans la liste de copie si la cellule de coodonnées #1 #2 est dans une plage de copie % si oui, affecte à #3 la formule transposée % La liste de copy est parcourue de gauche à droite avec sortie dès qu'une plage qui convient est rencontrée \def\ST_look_in_copy_list#1#2#3{% \let\ST_alias_copylist\ST_copy_list \let\ST_returned_formula\empty \def\ST_copycol{#1}% \def\ST_copyrow{#2}% \let#3\empty \ST_look_in_copy_list_a \let#3\ST_returned_formula } \def\ST_look_in_copy_list_a{% \expandafter\ST_test_first_in_copy_list\ST_alias_copylist\_nil \ST_ifx{\empty\ST_alias_copylist} {} {% \ST_ifx{\empty\ST_returned_formula} {\ST_look_in_copy_list_a} {}% }% } \def\ST_test_first_in_copy_list|#1|[#2,#3](#4,#5)#6\_nil{% Teste si un élément de la copylist contient une plage qui inclus la cellule en cours. \def\ST_alias_copylist{#6}% on enlève le premier élément de la copylist \ST_if_intereval\ST_copycol\ST_copyrow[#2,#3](#4,#5)% si ça correspond {% \def\ST_returned_formula{#1}% \ST_transpose_formula{\numexpr\ST_copycol-#2}{\numexpr\ST_copyrow-#3}\ST_returned_formula\ST_returned_formula }% {% \ST_ifempty{#5} {} {% \ifnum\ST_copyrow>\numexpr#3+#5\relax \ST_subst\ST_copy_list{|#1|[#2,#3](#4,#5)}{}% si on a dépassé la ligne, on retire ce premier élément de la copylist \fi }% }% } % Cherche dans la sc#1 du type ">4,v9" les décalages horizontaux et verticaux % spécifiés avec > et v % S'il trouve > ou v sans nombre derrière, le décalage correspondant est vide % S'il ne trouve pas > ou v, le décalage correspond est égal à 0 % Assigne les décalages trouvés dans \ST_hoffest et \ST_voffest \def\ST_scan_copy_offset#1{% \ST_if_instr#1>% {\ST_assign_copy_offest#1>\ST_hoffest}% {\def\ST_hoffest{0}}% \ST_if_instr#1v% {\ST_assign_copy_offest#1v\ST_voffest}% {\def\ST_voffest{0}}% } % Cherche dans la sc #1 ce qui est entre #2 et , \def\ST_assign_copy_offest#1#2#3{% \def\ST_assign_copy_offest_a##1#2##2,##3\_nil{\def#3{##2}}% \expandafter\ST_assign_copy_offest_a#1,\_nil } %------------------------------------- %----- Recherche d'une référence ----- %------------------------------------- % teste si le token #1 est une lettre (majuscule ou minuscule) \def\ST_if_car_is_letter#1{% \lowercase{\expandafter\ST_if_car_is_letter_a\detokenize{#1}}.\_nil } \def\ST_if_car_is_letter_a#1#2\_nil{% \ST_ifnum{\numexpr(`#1-`a)*(`#1-`z)\relax<1 }% } % cherche une référence du type lettre+nombre dans la sc #1 % si on trouve, renvoie les coordonnées dans #2 sous la forme x_y % si on ne trouve pas, #2 est vide. \def\ST_return_ref_in_formula#1#2{% \let\ST_temp_formula#1% \ST_return_ref_in_formula_a \let#2\ST_temp_formula \unless\ifx\empty\ST_ref_found \ST_if_first_is\ST_after_ref_found| {} {\ST_exp_two_args{\ST_subst_once#1}\ST_ref_found{\ST_ref_found|}}% \fi } \def\ST_return_ref_in_formula_a{% \let\ST_next_search\ST_return_ref_in_formula_a \ST_ifx{\empty\ST_temp_formula} {% \let\ST_next_search\relax } {% \StrSplit\ST_temp_formula1\ST_firstcar\ST_temp_formula% prend le 1er car de ce qui reste \ST_e_second\ST_if_car_is_letter\ST_firstcar% est-ce que le 1er car est une lettre ? {% \ST_grab_int\ST_temp_formula\ST_target_rownumber\ST_after_ref_found \ifnum\ST_target_rownumber>0 % si ce nombre est plus grand que 0 -> référence valide \edef\ST_ref_found{\ST_firstcar\ST_target_rownumber}% est la référence trouvée \edef\ST_target_colnumber{\number\numexpr\expandafter`\ST_firstcar-`a+1}% traduction lettre->chiffre \ifnum\ST_target_colnumber<0 \edef\ST_target_colnumber{\number\numexpr\ST_target_colnumber+32}% met les majuscules en minuscules \fi \edef\ST_temp_formula{\ST_target_colnumber _\ST_target_rownumber}% les coordonnées de la référence \let\ST_next_search\relax \fi }% {% \ST_if{[\expandafter\noexpand\ST_firstcar}% si le 1er car est [ {% \ST_right\ST_temp_formula]\ST_after_ref_found \ST_left\ST_temp_formula]\ST_temp_formula% on prend ce qui est entre crochet \ST_e_second{\def\ST_ref_found}{\expandafter[\ST_temp_formula]}% \ST_left\ST_temp_formula,\ST_rel_num \ST_if_integer\ST_rel_num {% \edef\ST_target_colnumber{\number\numexpr\ST_current_colnumber+\ST_rel_num}% \ST_right\ST_temp_formula,\ST_rel_num \ST_if_integer\ST_rel_num {% \edef\ST_target_rownumber{\number\numexpr\ST_current_rownumber+\ST_rel_num}% \edef\ST_temp_formula{\ST_target_colnumber _\ST_target_rownumber}% les coordonnées de la référence \let\ST_next_search\relax }% {% \ST_illegal_relativeref }% }% {% \ST_illegal_relativeref }% }% {}% }% }% \ST_next_search } % cette commande teste si la sc #1 est syntaxiquement une référence \def\ST_if_ref#1#2#3{% \let\ST_temp_formula#1% \let\ST_ref_found\empty \ST_return_ref_in_formula_a \ST_ifx{\empty\ST_ref_found} {#3} {\ST_ifx{#1\ST_ref_found}{#2}{#3}}% } %-------------------------------------------------------- %----- Le noyau : évaluation de toutes les cellules ----- %-------------------------------------------------------- \def\ST_try_calc_cell#1#2{% Essaie de calculer la cellule (#1,#2) \ST_ifvalid_csname{formula_#1_#2}% on vérifie que la formule existe {% \ST_e_second\ST_ifx{\csname formula_#1_#2\endcsname\empty} {% \ST_csdef{code_#1_#2}{0}% si vide, code à 0 } {% et qu'elle est non vide \def\ST_current_colnumber{#1}\def\ST_current_rownumber{#2}% \expandafter\ST_find_first_func\csname formula_#1_#2\endcsname% cherche une fonction dans la formule \ifx\empty\ST_function_namefound% s'il n'y a pas de fonction \expandafter\ST_return_ref_in_formula\csname formula_#1_#2\endcsname\ST_coord \ifx\empty\ST_coord% ni de référence \ST_calc_cell{#1}{#2}% on va calculer la cellule \fi \fi }% } {}% } \def\ST_calc_cell#1#2{% calcule la formule numérique de la cellule (#1,#2) \ifnum\csname code_#1_#2\endcsname>0 % si le code est >= 1 \ST_ifx{\ST_round_digit\empty} {\expandafter\ST_fpeval\csname formula_#1_#2\endcsname{\csname formula_#1_#2\endcsname}} {\expandafter\ST_fpeval\csname formula_#1_#2\endcsname{round(\csname formula_#1_#2\endcsname,\ST_round_digit)}}% \ST_csdef{code_#1_#2}{2}% et met le code à 2 (cellule calculée) \fi } \def\ST_eval_tab{% Calcule toutes les formules du tableau \ST_row_cnt1 \ST_col_cnt1 \ST_eval_tab_a } \def\ST_eval_tab_a{% \ST_ifnum{\ST_row_cnt>\ST_total_rownumber\space} {} {% \ST_ifvalid_csname{formula_\number\ST_col_cnt _\number\ST_row_cnt}% si la formule existe {\ST_eval_cell(\number\ST_col_cnt,\number\ST_row_cnt)}% on la calcule {}% \advance\ST_col_cnt1 \ifnum\ST_col_cnt>\ST_total_colnumber \ST_col_cnt1 \advance\ST_row_cnt1 \fi \ST_eval_tab_a }% } % la sc #1 est le nom d'une fonction. % La macro renvoie dans la sc #2 : 1, 2 ou 2 selon que #1 est le nom d'une macro à argument numérique, à argument plage ou à argument texte. % #2 est vaut 0 si #1 n'est pas le nom d'une fonction \def\ST_assign_function_code#1#2{% \ST_if_instr{\ST_functions_with_num_arg,}{#1,}% {% \def#2{1}% }% {\ST_if_instr{\ST_functions_with_range_arg,}{#1,}% {% \def#2{2}% }% {% \ST_if_instr{\ST_functions_with_text_arg,}{#1,}% {\def#2{3}}% {\def#2{0}}% }% }% } \def\ST_eval_cell(#1,#2){% évalue la cellule (#1#2) par ses coordonnées numériques (col,row) \edef\ST_dependance_tree{(#1,#2)}% \let\ST_stack_call\empty \let\ST_function_namesaved\empty \ST_eval_cell_a(#1,#2)% on appelle la macro récursive \ifboolKV[\STname]{messages} {% \ST_calc_dep_tree \message{\space\space\space cell \ST_dep_tree^^J}% } {}% } % Ceci est la macro principale : elle évalue la cellule (#1,#2) \def\ST_eval_cell_a(#1,#2){% #1 = no colonne #2 = no ligne \ST_ifnum{\csname code_#1_#2\endcsname=1 }% on ne fait quelque chose que si le code est 1 : cellule non calculée {% \def\ST_current_colnumber{#1}\def\ST_current_rownumber{#2}% \expandafter\ST_find_first_func\csname formula_#1_#2\endcsname% cherche une fonction dans la formule \ST_ifx{\empty\ST_function_namefound}% il n'y a pas de fonction {% \IfSubStr[2]\ST_dependance_tree{(#1,#2)}% {\edef\ST_coord{#1,#2}\ST_circular_reference}% message et on s'arrête si référence circulaire {}% \expandafter\ST_return_ref_in_formula\csname formula_#1_#2\endcsname\ST_coord% y a t-il une référence dans l'argument ? \ST_ifx{\ST_coord\empty}% pas de référence dans l'argument {% \ST_ifx{\ST_function_namesaved\empty}% si aucune fonction n'a été décelée {% \ST_calc_cell{#1}{#2}% on calcule la cellule en cours } {% \ST_if_instr{\ST_functions_with_textresult,}{\ST_function_namesaved,}% si la dernière fonction rend du texte {% \ST_ifx{\ST_stack_call\empty}% et si on est dans la cellule source {% \ST_csdef{code_#1_#2}{0}% on met le code à 0, la cellule devient textuelle \exploregroups \expandafter\StrSubstitute\expandafter[\expandafter1\expandafter]\csname text_#1_#2\expandafter\endcsname\expandafter\ST_numeric_mark\csname formula_#1_#2\expandafter\endcsname\expandafter[\csname text_#1_#2\endcsname]% copie de la formule vers la zone texte \noexploregroups \ST_cslet{formula_#1_#2}\empty% et plus rien dans la formule } {% \ST_calc_cell{#1}{#2}% sinon, on se trouve dans une cellule appelée par une macrofonction et donc, on la calcule }% }% {% \ST_calc_cell{#1}{#2}% c'est une fonction qui donne un arg numérique : on calcule la cellule en cours }% }% } {% \ST_ifvalid_csname{code_\ST_coord}% le code cible exite ? {% \ifcase\csname code_\ST_coord\endcsname% code cible = 0 ==> cellule vide ou textuelle, pas bon du tout ! \edef\ST_temp_callcell{#1,#2}% coordonnées de la cellule appelante \edef\ST_coord{\ST_target_colnumber,\ST_target_rownumber}% coordonnées appelées \ST_zerocodecell_cell% erreur : référence à une cellule de code 0 \or% code cible = 1 \edef\ST_dependance_tree{\ST_dependance_tree(\ST_target_colnumber,\ST_target_rownumber)}% on l'ajoute à l'arbre des dépendances % on doit évaluer cette formule ciblee et ensuite, on doit encore recommence avec la formule en cours : on les ajoute sur la pile lifo \edef\ST_stack_call{(\ST_target_colnumber,\ST_target_rownumber)(#1,#2)\ST_stack_call}% \or% code cible = 2, la cellule cible est calculée, on créé un alias pour le contenu de la formule cible \ST_letcs\ST_target_formula{formula_\ST_coord}% % si la valeur cible est <0, on la met entre parenthèses \ST_if_first_is\ST_target_formula-% {\ST_e_second{\def\ST_target_formula}{\expandafter(\ST_target_formula)}}% {}% % on remplace toutes les références par la valeur cible \expandafter\ST_eargs_subst\csname formula_#1_#2\endcsname{\ST_ref_found|}\ST_target_formula \edef\ST_stack_call{(#1,#2)\ST_stack_call}% puis, on évalue à nouveau cette cellule \else% code cible n'est pas {0,1,2} donc est -1, pas bon du tout ! \edef\ST_temp_callcell{#1,#2}% coordonnées de la cellule appelante \ST_subst\ST_coord _,% coordonnées appelées \ST_multicol_cell \fi }% {% \edef\ST_temp_callcell{#1,#2}% coordonnées de la cellule appelante \ST_subst\ST_coord _,% coordonnées appelées \ST_undefined_cell% code cible inexistant -> cellule hors limite du tableau }% }% } {% il y a une fonction dans la formule \let\ST_function_namesaved\ST_function_namefound \ST_assign_function_code\ST_function_namefound\ST_codefunc% détermine le code de la fonction \ifcase\ST_codefunc\relax \PackageError\STname{This error should not occur! Please email the author. Thanks.}{}% \or% le code vaut 1, c'est une fonction à argument numérique \ST_return_ref_in_formula\ST_function_argfound\ST_temp_formula% y a t-il une référence dans l'argument de la formule ? \ST_ifx{\ST_temp_formula\empty}% pas de référence dans l'argument de la fonction {% \ST_letcs\ST_current_formula{formula_#1_#2}% alias pour la formule \let\ST_to_subst\ST_function_namefound \ST_eadd_tomacro\ST_to_subst{\expandafter(\ST_function_argfound)}% ce qui va être replacé : fonction(argument) \ST_if_instr{\ST_functions_no_calc_arg,}{\ST_function_namefound,}% doit-on calculer l'argument de cette fonction ? {}% {\ST_fpeval\ST_function_argfound\ST_function_argfound}% \csname ST_func_\ST_function_namefound\endcsname\ST_function_argfound\ST_result_func% puis on évalue la fonctionfound \ST_if_first_is\ST_result_func-% {\ST_e_second{\def\ST_result_func}{\expandafter(\ST_result_func)}} {}% \ST_eargs_subst\ST_current_formula\ST_to_subst\ST_result_func% on replace dans l'alias \ST_cslet{formula_#1_#2}\ST_current_formula% on l'assigne dans la formule \ST_ifx{\empty\ST_current_formula}% pour cause de macro fonction "tag" qui est seule et qui a disparue après substitution {\ST_csdef{code_#1_#2}{0}}% {\edef\ST_stack_call{(#1,#2)\ST_stack_call}}% puis, on évalue à nouveau cette cellule } {% \ST_ifnum{\csname code_\ST_temp_formula\endcsname=2 }% si la référence est calculée, on la replace par sa valeur {% \ST_letcs\ST_current_formula{formula_\ST_temp_formula}% alias pour la formule cible \ST_if_first_is\ST_current_formula-% {\ST_e_second{\def\ST_current_formula}{\expandafter(\ST_current_formula)}}% {}% \let\ST_to_subst\ST_function_namefound \ST_eadd_tomacro\ST_to_subst{\expandafter(\ST_function_argfound)}% \let\ST_replaced\ST_to_subst \ST_eargs_subst\ST_replaced{\ST_ref_found|}\ST_current_formula \ST_subst\ST_to_subst|{}% \expandafter\ST_eargs_subst\csname formula_#1_#2\endcsname\ST_to_subst\ST_replaced \edef\ST_stack_call{(#1,#2)\ST_stack_call}% } {% la référence n'est pas calculée, donc d'abord il faut \edef\ST_stack_call{(\ST_target_colnumber,\ST_target_rownumber)(#1,#2)\ST_stack_call}% l'évaluer, et ensuite ré-evaluer la cellule courante \edef\ST_dependance_tree{\ST_dependance_tree(\ST_target_colnumber,\ST_target_rownumber)}% mise à jour de l'arbre des dépendances }% }% \or% le code vaut 2, c'est une fonction à argument «plage de cellules» \let\ST_to_subst\ST_function_namefound \ST_eadd_tomacro\ST_to_subst{\expandafter(\ST_function_argfound)}% \csname ST_func_\ST_function_namefound\endcsname\ST_function_argfound\ST_result_func% puis on essaie d'évaluer la fonction \unless\ifx\ST_result_func\empty% si le calcul a abouti \ST_if_first_is\ST_result_func-% {\ST_e_second{\def\ST_result_func}{\expandafter(\ST_result_func)}} {}% \expandafter\ST_eargs_subst\csname formula_#1_#2\endcsname\ST_to_subst\ST_result_func% on replace dans la formule \edef\ST_stack_call{(#1,#2)\ST_stack_call}% puis, on évalue à nouveau cette cellule \fi \or% le code vaut 3, c'est un fonction dont l'argument est textuel \let\ST_function_argfound_edefed\ST_function_argfound \ST_if_instr{\ST_functions_with_assign_argument,}{\ST_function_namefound,}% {\let\ST_temp_formula\empty}% {\ST_return_ref_in_formula\ST_function_argfound\ST_temp_formula}% y a t-il une référence dans l'argument de la fonction ? \ST_ifx{\ST_temp_formula\empty}% pas de référence dans l'argument de la fonction {% \ST_letcs\ST_current_formula{formula_#1_#2}% alias pour la formule \let\ST_to_subst\ST_function_namefound \ST_eadd_tomacro\ST_to_subst{\expandafter(\ST_function_argfound)}% ce qui va être remplacé : fonction(argument) \csname ST_func_\ST_function_namefound\endcsname\ST_function_argfound_edefed\ST_result_func% puis on évalue la fonction \ST_eargs_subst\ST_current_formula\ST_to_subst\ST_result_func% on replace dans l'alias \ST_cslet{formula_#1_#2}\ST_current_formula% on l'assigne dans la formule \edef\ST_stack_call{(#1,#2)\ST_stack_call}% puis, on évalue à nouveau cette cellule } {% il y a une référence dans l'argument de la fontion \ST_ifnum{\csname code_\ST_temp_formula\endcsname=0 }% si la référence est une cellule texte {% \ST_letcs\ST_current_formula{text_\ST_temp_formula}% alias pour la zone texte cible \StrDel[1]\ST_current_formula\ST_numeric_mark[\ST_current_formula]% on enlève le := si besoin \ST_if_instr\ST_current_formula{\empty\multicolumn}% on ne prend que le texte s'il y a un \multicolumn qui traine {\ST_assign_third_car\ST_current_formula\multicolumn\ST_current_formula} {}% \let\ST_to_subst\ST_function_namefound \ST_eadd_tomacro\ST_to_subst{\expandafter(\ST_function_argfound)}% \ST_subst\ST_to_subst|{}% \csname ST_func_\ST_function_namefound\endcsname\ST_current_formula\ST_result_func% puis on évalue la fonction \expandafter\ST_eargs_subst\csname formula_#1_#2\endcsname\ST_to_subst\ST_result_func \edef\ST_stack_call{(#1,#2)\ST_stack_call}% puis, on évalue à nouveau cette cellule } {% \PackageError\STname{Macro function \ST_function_namefound\space requires a reference to a text cell!}\STseedoc_a }% }% \fi }% \ST_next_onstack } {}% } % On regarde s'il y a des appels de calcul de cellules en attente % Si oui, on enlève le 1er appel de la pile lifo et on l'exécute \def\ST_next_onstack{% \ST_ifx{\ST_stack_call\empty} {} {% \ST_split\ST_stack_call)\ST_temp_a\ST_stack_call \ST_add_tomacro\ST_temp_a)% \ST_sanitize_stack \ST_e_second{\def\ST_temp_a}{\expandafter\ST_eval_cell_a\ST_temp_a}% \ST_temp_a }% } \def\ST_sanitize_stack{% enlève de \ST_stack_call toutes les occurrences de \ST_temp_a \ST_if_instr\ST_stack_call\ST_temp_a {% \ST_e_second{\ST_subst\ST_stack_call}{\ST_temp_a}{}% \ST_sanitize_stack }% {}% } %--------------------------- %----- Macro-fonctions ----- %--------------------------- \def\ST_for#1#2{% \ST_for{#1}{code} -> exécute le code pour toutes les cellules des plages passées en argument #1 \def\ST_forcode{#2}% \ST_e_second{\def\ST_for_rangelist}{#1;}% \ST_for_a } \def\ST_for_loopcode{% #2 est le code à exécuter dans la boucle for \ST_forcode% on exécute le code \advance\ST_col_cnt_a1 \ST_ifnum{\ST_col_cnt_a>\ST_for_col_end\space} {% \ST_col_cnt_a\ST_for_col_start \advance\ST_row_cnt_a1 \ST_ifnum{\ST_row_cnt_a>\ST_for_row_end\space} {% \ST_right\ST_for_rangelist;\ST_for_rangelist \ST_ifx{\ST_for_rangelist\empty} {} {\ST_for_a}% } {% \ST_for_loopcode }% } {% \ST_for_loopcode }% } \def\ST_for_a{% \ST_left\ST_for_rangelist;\ST_for_currentrange \ST_left{\ST_for_currentrange:}{\empty:}\ST_temp_a \ST_if_ref\ST_temp_a{}\ST_invalid_range \let\ST_for_col_start\ST_target_colnumber \let\ST_for_row_start\ST_target_rownumber \ST_col_cnt_a\ST_target_colnumber \ST_row_cnt_a\ST_target_rownumber \ST_if_instr\ST_for_currentrange{\empty:}% {% \ST_right\ST_for_currentrange{\empty:}\ST_temp_a \ST_if_ref\ST_temp_a{}\ST_invalid_range \let\ST_for_col_end\ST_target_colnumber \let\ST_for_row_end\ST_target_rownumber \ST_ifnum{\ST_ifnum{\ST_for_col_start>\ST_for_col_end\space}10\ST_ifnum{\ST_for_row_start>\ST_for_row_end\space}10>0 } {} {\ST_for_loopcode}% }% {% \ST_forcode \ST_right\ST_for_rangelist;\ST_for_rangelist \ST_ifx{ST_for_rangelist\empty} {} {\ST_for_a}% }% } % Cette macro cherche dans la sc #1 la première fonction qu'elle trouve ne contenant pas d'autre fonction dans son argument % En sortie, \ST_function_namefound contient le nom de la fonction trouvée et \ST_function_argfound son argument. % Si aucune fonction mot-clé n'est trouvé, ces 2 dernières séquences de contrôles sont vides. \def\ST_find_first_func#1{% \let\ST_function_namefound\empty\let\ST_function_argfound\empty \let\ST_function_namefound_\empty\let\ST_function_argfound_\empty \let\ST_tempfunc#1% \ST_find_first_func_a } \def\ST_find_first_func_a{% \let\ST_tempfunctions_list\ST_functions_list% réinitialise la liste \let\ST_tempfunc_\ST_tempfunc% sauveagrde pour restauration ultérieure \let\ST_function_namefound\empty% initialise avant appel à la macro \ST_if_instr\ST_tempfunc(% s'il y a une parenthèse {% \ST_find_first_func_b% cherche la 1ere fonction dans l'argument \ST_ifx{\ST_function_namefound\empty}% elle n'existe pas ? {% \let\ST_function_namefound\ST_function_namefound_\let\ST_function_argfound\ST_function_argfound_% on restaure les valeurs précédentes } {% si il y a une fonction dans l'argument \ST_right\ST_tempfunc_\ST_function_namefound\ST_tempfunc% prend ce qui est après le nom de la fonction \ST_scan_func_arg\ST_tempfunc\ST_function_argfound% isole l'argument entre parenthèses \let\ST_function_namefound_\ST_function_namefound\let\ST_function_argfound_\ST_function_argfound% met à jour les valeurs précédentes \let\ST_tempfunc\ST_function_argfound% recommence avec l'argument \ST_find_first_func_a }% }% {% \let\ST_function_namefound\ST_function_namefound_ \let\ST_function_argfound\ST_function_argfound_ }% } \def\ST_find_first_func_b{% \ST_ifnum{0\ifx\ST_tempfunc\empty\else1\fi\ifx\ST_tempfunctions_list\empty\else1\fi=11 } {% \ST_split\ST_tempfunctions_list,\ST_current_funcname\ST_tempfunctions_list \ST_if_instr\ST_tempfunc{\ST_current_funcname(}% si l'argument contient le nom de fonction courant {% \let\ST_function_namefound\ST_current_funcname \ST_left\ST_tempfunc{\ST_current_funcname(}\ST_tempfunc% on réduit l'argument à ce qui est avant ce nom }% {}% \ST_find_first_func_b } {}% } % la sc #1 commence normalement (sinon, ça va gueuler) par une parenthèse % La macro trouve l'argument se trouvant entre les parenthèses les plus extérieures % et l'assigne à la sc #2 \def\ST_scan_func_arg#1#2{% \begingroup \everyeof{\ST_nil}% met un \_nil à la fin du fichier virtuel \endlinechar-1 \catcode0 12 \catcode`(=1 \catcode`)=2 \afterassignment\ST_eat_to_nil \expandafter\def\expandafter\ST_temp_a\scantokens\expandafter{#1}% Attentionn !!! Il peut rester des ) non équilibrées \catcode`(=12 \catcode`)=12 \def\ST_assign_result##1\ST_nil{\endgroup\def#2{##1}}% \expandafter\ST_assign_result\scantokens\expandafter{\ST_temp_a}% on fait l'assignation } \def\ST_func_sum#1#2{% #1 est la sc contenant la plage de valeurs, #2 est la sc recevant le résultat \def#2{0}% résultat nul pour l'instant \let\ST_temp_stack\empty% pile d'appel temporaire vide aussi au début \ST_for{#1}% on parcourt la {% \ifnum\numexpr10000*(\ST_col_cnt_a-\ST_current_colnumber)+\ST_row_cnt_a-\ST_current_rownumber=0 \ST_invalid_range% si la cellule courante est dans la plage, erreur \fi \ifcase\csname code_\number\ST_col_cnt_a _\number\ST_row_cnt_a\endcsname\relax% on ne prend en compte que les code 1 et 2 \or% code=1 \edef\ST_temp_stack{\ST_temp_stack(\number\ST_col_cnt_a,\number\ST_row_cnt_a)}% \or% code=2 \ifx\ST_temp_stack\empty% on ne prend la peine d'additionner que si toutes les cellules sont calculées \edef#2{\fpeval{#2+\csname formula_\number\ST_col_cnt_a _\number\ST_row_cnt_a\endcsname}}% \edef\ST_dependance_tree{\ST_dependance_tree(\number\ST_col_cnt_a,\number\ST_row_cnt_a)}% mise à jour de l'arbre des dépendances \fi \fi }% \unless\ifx\ST_temp_stack\empty% toutes les cellules dans la plage étaient calculées ? \let#2\empty \edef\ST_stack_call{\ST_temp_stack(\ST_current_colnumber,\ST_current_rownumber)\ST_stack_call}% on met à jour la pile d'appel \fi } \def\ST_func_sumprod#1#2{% #1 est la sc contenant la plage de valeurs, #2 est la sc recevant le résultat \ST_left{#1;};\ST_firstmat \ST_right#1;\ST_othermat \ST_left\ST_firstmat{\empty:}\ST_temp_a \ST_if_ref\ST_temp_a{}\ST_invalid_range \let\ST_mat_firstcol\ST_target_colnumber \let\ST_mat_firstrow\ST_target_rownumber \StrBehind\ST_firstmat{\empty:}[\ST_temp_a]% \ST_if_ref\ST_temp_a{}\ST_invalid_range \edef\ST_matcol{\number\numexpr\ST_target_colnumber-\ST_mat_firstcol}% \edef\ST_matrow{\number\numexpr\ST_target_rownumber-\ST_mat_firstrow}% \ST_cnt1 \loop% regarde si toutes les matrices ont la même dimension que la 1ere et pour chacune, calcule les vecteurs de décalage par rapport à la première \unless\ifx\ST_othermat\empty \ST_left{\ST_othermat;};\ST_currentmat \ST_right{\ST_othermat;};\ST_othermat \ST_left{\ST_currentmat:}{\empty:}\ST_temp_a \ST_if_ref\ST_temp_a{}\ST_invalid_range \let\ST_currentmatcol\ST_target_colnumber \let\ST_currentmatrow\ST_target_rownumber \expandafter\edef\csname ST_vectorcol_\romannumeral\ST_cnt\endcsname{\number\numexpr\ST_target_colnumber-\ST_mat_firstcol}% \expandafter\edef\csname ST_vectorrow_\romannumeral\ST_cnt\endcsname{\number\numexpr\ST_target_rownumber-\ST_mat_firstrow}% \ST_right\ST_currentmat{\empty:}\ST_temp_a \ST_if_ref\ST_temp_a{}\ST_invalid_range \edef\ST_currentmatcol{\number\numexpr\ST_target_colnumber-\ST_currentmatcol}% \edef\ST_currentmatrow{\number\numexpr\ST_target_rownumber-\ST_currentmatrow}% \unless\ifnum\ST_matcol=\ST_currentmatcol\relax% la dimension horizontale ne correspond pas \ST_unmatch_matrixdim \fi \unless\ifnum\ST_matrow=\ST_currentmatrow\relax% la dimension verticale ne correspond pas \ST_unmatch_matrixdim \fi \advance\ST_cnt1 \repeat \let\ST_temp_stack\empty% pile d'appel temporaire vide au début \edef\ST_numbermat{\number\ST_cnt}% c'est le nombre de matrices à multiplier \def#2{0}% résultat nul pour l'instant \ST_for{\ST_firstmat}% pour chaque cellule de la 1ere matrice {% \def\ST_inter_result{0}% résultat partiel nul pour l'instant \ifcase\csname code_\number\ST_col_cnt_a _\number\ST_row_cnt_a\endcsname \or% code =1 \edef\ST_temp_stack{\ST_temp_stack(\number\ST_col_cnt_a,\number\ST_row_cnt_a)}% \edef\ST_dependance_tree{\ST_dependance_tree(\number\ST_col_cnt_a,\number\ST_row_cnt_a)}% mise à jour de l'arbre des dépendances \or% code=2 \ST_letcs\ST_inter_result{formula_\number\ST_col_cnt_a _\number\ST_row_cnt_a}% \fi \ST_cnt1 \loop% on multiplie tous les nombres qui se correspondent dans les matrices \edef\ST_tempcoord{\number\numexpr\ST_col_cnt_a+\csname ST_vectorcol_\romannumeral\ST_cnt\endcsname _\number\numexpr\ST_row_cnt_a+\csname ST_vectorrow_\romannumeral\ST_cnt\endcsname}% \ifcase\csname code_\ST_tempcoord\endcsname \def\ST_inter_result{0}% code =0 -> on met le résultat partiel à 0 \or% code =1 \ST_subst\ST_tempcoord _,% \edef\ST_temp_stack{\ST_temp_stack(\ST_tempcoord)}% \edef\ST_dependance_tree{\ST_dependance_tree(\ST_tempcoord)}% mise à jour de l'arbre des dépendances \or% code=2 \edef\ST_inter_result{\fpeval{\ST_inter_result*\csname formula_\ST_tempcoord\endcsname}}% \else \def\ST_inter_result{0}% code = autre -> on met le résultat partiel à 0 \fi \advance\ST_cnt1 \ifnum\ST_cnt<\ST_numbermat \repeat \edef#2{\fpeval{#2+\ST_inter_result}}% }% \ST_ifx{\ST_temp_stack\empty}% toutes les cellules dans la plage étaient calculées ? {} {% \let#2\empty \edef\ST_stack_call{\ST_temp_stack(\ST_current_colnumber,\ST_current_rownumber)\ST_stack_call}% on met à jour la pile d'appel }% } \def\ST_func_id#1#2{% \ST_e_second{\def#2}{#1}% } \def\ST_arithmetic#1#2{% #1 est une liste de nombres séparés par des virgules, #2 la sc qui reçoit leur pgcd ou ppcm selon \ifST_gcd \ST_split{#1},#2\ST_argB \ST_if_instr\ST_argB, {% \ST_split\ST_argB,\ST_argB\ST_remain \let\ST_next\ST_arithmetic } {% \let\ST_remain\empty \let\ST_next\ST_gobtwo }% \let\ST_argA#2% \ST_fpeval\ST_argA\ST_argA \ST_fpeval\ST_argB\ST_argB% évalue les 2 nombres au cas où il y ait des opérations \ST_fpeval#2{trunc(abs(max(\ST_argB,\ST_argA)),0)}% \ST_fpeval\ST_argB{trunc(abs(min(\ST_argB,\ST_argA)),0)}% \ST_ifnum{\ST_argB=0 } {}% si 0, on ignore puisque tous les nombres divisent 0 ou en sont leur multiple {% \ifST_gcd\else \edef\ST_argC{\fpeval{#2*\ST_argB}}% \fi \ST_arithmetic_a#2% \ifST_gcd\else \ST_fpeval#2{trunc(\ST_argC/#2,0)}% \fi }% \ifST_gcd \ST_ifnum{#2=1 }% {\let\ST_next\ST_gobtwo} {}% pour le pgcd, inutile de continuer si le pgcd est 1 \fi \ST_e_second{\ST_e_second\ST_next}{\expandafter#2\expandafter,\ST_remain}#2% } \def\ST_arithmetic_a#1{% \let\ST_argA\ST_argB \ST_fpeval\ST_argB{trunc(#1-trunc(#1/\ST_argB,0)*\ST_argB,0)}% reste de la division #2/\ST_argB \let#1\ST_argA \ST_ifnum{\ST_argB=0 } {} {\ST_arithmetic_a#1}% } \def\ST_func_gcd{\ST_gcdtrue\ST_arithmetic} \def\ST_func_lcm{\ST_gcdfalse\ST_arithmetic} \def\ST_func_scitodec#1#2{% #1=sc contenant l'argument #2: sc recevant le résultat \lowercase\expandafter{\expandafter\def\expandafter#1\expandafter{#1}}% \ST_if_instr#1{ee}% on regarde s'il y a "ee" {% \ST_split#1{ee}\ST_mantissa\ST_exposant \edef#2{\ST_mantissa*10^{\ST_exposant}}% }% {% \let#2#1% }% } % les fonctions de test------------------------| % TODO à supprimer | \def\ST_def_funcif#1{% | \expandafter\ST_def_funcif_a#1\_nil% | }% | % | \def\ST_def_funcif_a#1,#2,#3,#4\_nil#5#6{% | \csname ST_if#6\endcsname{#1}{#2}% | {\def#5{#3}}% | {\def#5{#4}}% | }% | % | \def\ST_func_ifeq#1#2{\ST_def_funcif#1#2{eq}}% | \def\ST_func_ifgt#1#2{\ST_def_funcif#1#2{gt}}% | \def\ST_func_iflt#1#2{\ST_def_funcif#1#2{lt}}%_| % Transforme une date en nombre \def\STdatetonum#1#2#3#4{% #1=sc recevant le résultat #2=jj #3=mm #4=aa \ST_fpeval#1{#3+9-12*trunc((#3+9)/12,0)}% \ST_fpeval\ST___year{#4-trunc(#1/10,0)}% \ST_fpeval#1{365*\ST___year+trunc(\ST___year/4,0)-trunc(\ST___year/100,0)+trunc(\ST___year/400,0)+trunc((#1*306+5)/10,0)+#2-1}% } % Transforme un nombre en une date \def\ST_numtodate#1#2#3#4{% #1=nombre représentant la date #2=jour #3=mois #4=année \ST_fpeval#4{trunc((10000*#1+14780)/3652425,0)}% \ST_fpeval#2{#1-(365*#4+trunc(#4/4,0)-trunc(#4/100,0)+trunc(#4/400,0))}% \ST_ifnum{#2<0 } {% \edef#4{\fpeval{#4-1}}% \ST_fpeval#2{#1-(365*#4+trunc(#4/4,0)-trunc(#4/100,0)+trunc(#4/400,0))} } {}% \ST_fpeval#3{trunc((100*#2+52)/3060,0)}% \ST_fpeval#4{#4+trunc((#3+2)/12,0)}% \ST_fpeval#2{#2-trunc((#3*306+5)/10,0)+1}% \ST_fpeval#3{#3+2-12*trunc((#3+2)/12,0)+1}% } \def\ST_parse_datefr#1/#2/#3\_nil{% \def\ST___day{#1}% \def\ST___month{#2}% \def\ST___year{#3}% } \def\ST_parse_dateeng#1/#2/#3\_nil{% \def\ST___day{#3}% \def\ST___month{#2}% \def\ST___year{#1}% } % transforme une date française courte du type jj/mm/aaaa en nombre \def\ST_func_frshortdatetonum#1#2{% #1=sc étant l'argument jj/mm/aaaa #2=sc recevant le résultat \expandafter\ST_parse_datefr#1\_nil \STdatetonum#2\ST___day\ST___month\ST___year } % Transforme un nombre en une date française de type jj/mm/aaaa \def\ST_func_numtofrshortdate#1#2{% % #1=nombre représentant la date #2=sc recevant le résultat \ST_numtodate#1\ST___day\ST___month\ST___year \edef#2{\ST___day/\ST___month/\ST___year}% } % Transforme un nombre en une date longue française du type «14 juillet 1789» \def\ST_num_to_frmonth#1{% \ifcase#1 \or janvier\or f\'evrier\or mars\or avril\or mai\or juin\or juillet% \or ao\^ut\or septembre\or octobre \or novembre\or d\'ecembre% \fi } \def\ST_func_numtofrlongdate#1#2{% % #1=nombre représentant la date #2=sc recevant le résultat \ST_numtodate#1\ST___day\ST___month\ST___year \edef#2{\ST___day\space\ST_num_to_frmonth\ST___month\space\ST___year}% } % Extrait d'un nombre représentant une date le mois en toutes lettres en français \def\ST_func_numtofrmonth#1#2{% % #1=nombre représentant la date #2=sc recevant le résultat \ST_numtodate#1\ST___day\ST___month\ST___year \edef#2{\ST_num_to_frmonth\ST___month}% } % Extrait d'un nombre repésentant une date le nom du jour en français \def\ST_func_numtofrday#1#2{% % #1=nombre représentant la date #2=sc recevant le résultat \ST_fpeval\ST___day{#1-7*trunc(#1/7,0)}% \edef#2{\ifcase\ST___day mercredi\or jeudi\or vendredi\or samedi\or dimanche\or lundi\or mardi\fi} } % transforme une date anglaise courte du type aaaa/mm/jj en nombre \def\ST_func_engshortdatetonum#1#2{% #1=sc étant l'argument aaaa/mm/jj #2=sc recevant le résultat \expandafter\ST_parse_dateeng#1\_nil \STdatetonum#2\ST___day\ST___month\ST___year } % Transforme un nombre en une date anglaise de type aaaa/mm/jj \def\ST_func_numtoengshortdate#1#2{% % #1=nombre représentant la date #2=sc recevant le résultat \ST_numtodate#1\ST___day\ST___month\ST___year \edef#2{\ST___year/\ST___month/\ST___day} } % Transforme un nombre en une date longue anglaise du type «July 14, 1789» \def\ST_num_to_enmonth#1{% \ifcase#1 \or January\or February\or March\or April\or May\or June\or July% \or August\or September\or October\or November\or December% \fi } \def\ST_func_numtoenglongdate#1#2{% % #1=nombre représentant la date #2=sc recevant le résultat \ST_numtodate#1\ST___day\ST___month\ST___year \edef#2{\ST_num_to_enmonth\ST___month\space\ST___day,\space\ST___year}% } % Extrait d'un nombre représentant une date le mois en toutes lettres en anglais \def\ST_func_numtoengmonth#1#2{% % #1=nombre représentant la date #2=sc recevant le résultat \ST_numtodate#1\ST___day\ST___month\ST___year \edef#2{\ST_num_to_enmonth\ST___month}% } % Extrait d'un nombre repésentant une date le nom du jour en anglais \def\ST_func_numtoengday#1#2{% % #1=nombre représentant la date #2=sc recevant le résultat \ST_fpeval\ST___day{#1-7*trunc(#1/7,0)}% \edef#2{\ifcase\ST___day wednesday\or thursday\or friday\or saturday\or sunday\or monday\or tuesday\fi} } % Teste si la date contenue dans les 3 sc #1 (jour) #2(mois) #3(année) est valide. Sinon, envoie un message d'erreur \def\ST_test_date_validity#1#2#3{% \ST_if_integer#1{}\ST_invalid_date \ST_if_integer#2{}\ST_invalid_date \ST_if_integer#3{}\ST_invalid_date \ST_ifnum{0% \ifnum#2<1 1\fi \ifnum#2>12 1\fi \ifnum#1<1 1\fi \ifnum#1>\ifcase#2\or31\or29\or31\or30\or31\or30\or31\or31\or30\or31\or30\or31\fi1\fi \ifnum#3<1 1\fi>0 } {\ST_invalid_date} {}% } % Transforme une date anglaise longue du type «July 14, 1789» en un nombre \def\ST_func_englongdatetonum#1#2{% #1=sc contenant la date longue #2=sc recevant le résultat \ST_analyse_text_engdate#1\ST___day\ST___month\ST___year \STdatetonum#2\ST___day\ST___month\ST___year } \def\ST_analyse_text_engdate#1#2#3#4{% #1=texte représentant la date #2=jour #3=n° mois #4=année \ST_remove_first_spaces#1% \def\ST_today{\today}% \ST_ifx{#1\ST_today} {% \edef#2{\number\day}\edef#3{\number\month}\edef#4{\number\year}% } {% \ST_left#1\space#3% \ST_remove_sp#3% \lowercase\expandafter{\expandafter\def\expandafter#3\expandafter{#3}}% \IfStrEqCase#3{% {january}{\def#3{1}}% {february}{\def#3{2}}% {march}{\def#3{3}}% {april}{\def#3{4}}% {may}{\def#3{5}}% {june}{\def#3{6}}% {july}{\def#3{7}}% {august}{\def#3{8}}% {september}{\def#3{9}}% {october}{\def#3{10}}% {november}{\def#3{11}}% {december}{\def#3{12}}% }% [\def#3{-1}]% \ST_right#1\space#2% \ST_if_instr#2,% {\ST_split#2,#2#4} {\ST_split#2{ }#2#4}% \ST_grab_int#2#2\ST___temp \ST_remove_sp#4% \ST_test_date_validity#2#3#4% }%0 } % Transforme une date anglaise longue du type «14 juillet 1789» en un nombre \def\ST_func_frlongdatetonum#1#2{% #1=sc contenant la date longue #2=sc recevant le résultat \ST_analyse_text_frdate#1\ST___day\ST___month\ST___year \STdatetonum#2\ST___day\ST___month\ST___year } \def\ST_utfencoding{utf8} \def\ST_analyse_text_frdate#1#2#3#4{% #1=texte représentant la date #2=jour #3=n° mois #4=année \ST_remove_first_spaces#1% \def\ST_today{\today}% \ST_ifx{#1\ST_today} {% \edef#2{\number\day}\edef#3{\number\month}\edef#4{\number\year}% } {% \ST_split#1{ }#2#3% \ST_if_integer#2{}{\edef#2{\number\ST_intpart_cnt}}% \ST_split#3{ }#3#4% \ST_remove_sp#3% \def\ST_e{^^e9}\def\ST_u{^^fb}% é et û en latin1 \ifdefined\inputencodingname\ifx\ST_utfencoding\inputencodingname \def\ST_e{^^c3^^a9}\def\ST_u{^^c3^^bb}% é et û en utf8 \fi\fi \expandafter\ST_subst\expandafter#3\expandafter{\ST_e}e\ST_subst#3\'{}% \expandafter\ST_subst\expandafter#3\expandafter{\ST_u}u\ST_subst#3\^{}% \lowercase\expandafter{\expandafter\def\expandafter\ST_temp_a\expandafter{#3}}% \IfStrEqCase#3{% {janvier}{\def#3{1}}% {fevrier}{\def#3{2}}% {mars}{\def#3{3}}% {avril}{\def#3{4}} {mai}{\def#3{5}} {juin}{\def#3{6}}% {juillet}{\def#3{7}} {aout}{\def#3{8}} {septembre}{\def#3{9}}% {octobre}{\def#3{10}} {novembre}{\def#3{11}} {decembre}{\def#3{12}}% } [\def#3{-1}]% \ST_test_date_validity#2#3#4% }% } \def\ST_func_tag#1#2{% \let#2\empty \ST_ifcsname{ST__\detokenize\expandafter{#1}} {\PackageWarning\STname{The tag "\detokenize\expandafter{#1}" already exists, previous is lost.}} {}% \expandafter\edef\csname ST__\detokenize\expandafter{#1}\endcsname{\number\ST_col_cnt,\number\ST_row_cnt}% \ST_eadd_tomacro\ST_tag_list{% \expandafter\global\expandafter\let \csname ST_celltag_\detokenize\expandafter{#1}\expandafter\endcsname \csname formula_\number\ST_col_cnt _\number\ST_row_cnt\expandafter\endcsname }% \ifboolKV[\STname]{tag to aux} {% \ST_xadd_tomacro\ST_tag_list{% \immediate\write\expandafter\noexpand\csname @auxout\endcsname{% \noexpand\noexpand\noexpand\expandafter\gdef\noexpand\noexpand\noexpand\csname ST_celltag_\detokenize\expandafter{#1}\noexpand\noexpand\noexpand\endcsname{% \expandafter\noexpand\csname formula_\number\ST_col_cnt _\number\ST_row_cnt\endcsname }% }% }% } {}% \expandafter\ST_coord_toref\csname ST__\detokenize\expandafter{#1}\endcsname } \def\ST_func_cell#1#2{% \ST_ifcsname{ST__\detokenize\expandafter{#1}}{}{\ST_unknown_tag}% \edef#2{\csname ST__\detokenize\expandafter{#1}\endcsname}% } \def\ST_func_row#1#2{% \ST_ifcsname{ST__\detokenize\expandafter{#1}}{}{\ST_unknown_tag}% \edef#2{\expandafter\expandafter\expandafter\ST_gobone\csname ST__\detokenize\expandafter{#1}\endcsname}% } \def\ST_func_col#1#2{% \ST_ifcsname{ST__\detokenize\expandafter{#1}}{}{\ST_unknown_tag}% \edef#2{% \number\numexpr \expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter `\expandafter\expandafter\expandafter\ST_first_tonil\csname ST__\detokenize\expandafter{#1}\endcsname\_nil-64}% } \def\ST_func_value#1#2{% \ST_ifcsname{ST_celltag_\detokenize\expandafter{#1}} {% \ST_letcs#2{ST_celltag_\detokenize\expandafter{#1}}% } {% \PackageWarning\ST_package_name{The tag "\detokenize\expandafter{#1}" does not exists, have you defined it?}% \def#2{0}% use 0 if tag undefined }% } %---------------------------------------------- %----- Construction du tableau à afficher ----- %---------------------------------------------- \def\ST_search_lastshowcol{% cherche le numéro de la dernière colonne non masquée \ST_col_cnt\ST_total_colnumber% on commence par la fin \ST_search_lastshowcol_a } \def\ST_search_lastshowcol_a{% \ST_if_instr{\ST_col_skiplist}{\expandafter(\number\ST_col_cnt)}% {\advance\ST_col_cnt-1 \ST_search_lastshowcol_a}% {\edef\ST_lastshowcol{\number\ST_col_cnt}}% } % cherche "<>" dans la sc #1 et remplace toutes ces occurences par le champ numérique de "ref" \def\ST_displaynumfields#1{% \StrBehind#1\ST_startdisplay[\ST_dipslaycell]% \StrBefore\ST_dipslaycell\ST_enddisplay[\ST_dipslaycell]% \let\ST_next\ST_gobone% à priori, on ne reboucle pas \ST_ifx{\empty\ST_dipslaycell}% si ce qui a entre les marqueurs n'est pas vide {} {% \edef\ST_current_colnumber{\number\ST_col_cnt}\edef\ST_current_rownumber{\number\ST_row_cnt}% \ST_if_ref\ST_dipslaycell% et si c'est une référence valide {% \let\ST_next\ST_startdisplay \ST_eadd_tomacro\ST_next\ST_dipslaycell \ST_eadd_tomacro\ST_next\ST_enddisplay \ST_letcs\ST_dipslaycell{formula_\ST_temp_formula}% alias pour la formule cible \unless\ifx\ST_decimal_sep\ST_default_dec_sep% subsitution du séparateur décimal s'il y a lieu \ST_subst_decimal_sep\ST_dipslaycell \fi \StrSubstitute#1\ST_next\ST_dipslaycell[#1]% on substitue <> par le champ numérique de la formule cible \let\ST_next\ST_displaynumfields } {}% }% \ST_next#1% } \def\ST_build_tab{% \ST_row_cnt1 \ST_col_cnt1 \expandafter\ST_eadd_tomacro\expandafter\ST_tab\csname endrow_0\endcsname% la (ou les) éventuelle ligne supérieure du tableau \ST_build_tab_a } \def\ST_build_tab_a{% reconstitue le tableau à partir des valeurs calculées et des cellules \ST_ifnum{\ST_row_cnt>\ST_total_rownumber\space} {% \let\ST_nextcell\relax } {% \let\ST_nextcell\ST_build_tab_a \ST_if_instr\ST_row_skiplist{\expandafter(\number\ST_row_cnt)}% la ligne fait partie de la skiplist ? {% \advance\ST_row_cnt1 % on passe à la ligne suivante } {% \ST_if_instr\ST_col_skiplist{\expandafter(\number\ST_col_cnt)}% la colonne fait partie de la skiplist ? \ST_hiddencoltrue {% \ST_hiddencolfalse \ST_ifvalid_csname{text_\number\ST_col_cnt _\number\ST_row_cnt}% si la cellule existe {% \ST_letcs\ST_temp_b{text_\number\ST_col_cnt _\number\ST_row_cnt}% on créé un alias pour le texte \exploregroups \ST_ifnum{\csname code_\number\ST_col_cnt _\number\ST_row_cnt\endcsname=2 }% si la cellule contient un champ numérique {% \ST_letcs\ST_temp_a{formula_\number\ST_col_cnt _\number\ST_row_cnt}% alias pour la valeur \if.\ST_decimal_sep\relax\else \ST_subst_decimal_sep\ST_temp_a% et si le "." doit être remplacé par "," on substitue \fi \StrSubstitute[1]\ST_temp_b\ST_numeric_mark{\expandafter\STprintnum\expandafter{\ST_temp_a}}[\ST_temp_a]% on remplace le flag de formule par la valeur calculée } {% si la cellule ne contient pas de champ numérique \StrDel[1]\ST_temp_b\ST_numeric_mark[\ST_temp_a]% on enlève l'éventuel marqueur }% \ST_displaynumfields\ST_temp_a% affiche les champs numériques des cellules entre << et >> \noexploregroups \ST_eadd_tomacro\ST_tab\ST_temp_a% on ajoute la cellule au tableau } {}% }% \advance\ST_col_cnt1 % on passe à la colonne suivante \ST_ifvalid_csname{code_\number\ST_col_cnt _\number\ST_row_cnt}% y a t-il encore un code défini ensuite ? {% \ST_ifnum{\csname code_\number\ST_col_cnt _\number\ST_row_cnt\endcsname<0 }% on est dans une cellule contenant \multicol ? {% \expandafter\ST_assign_first_car\csname text_\number\numexpr\ST_col_cnt-1_\number\ST_row_cnt\endcsname\multicolumn\STmulticol_number% combien de cellules ? \advance\ST_col_cnt\STmulticol_number% on va voir après le multicol en sautant toutes les valeurs des colonnes intermédiaires \advance\ST_col_cnt-1 \ST_ifvalid_csname{code_\number\ST_col_cnt _\number\ST_row_cnt}% y a t-il un code défini après le multicol ? {\ST_add_tomacro\ST_tab&}% on ajoute la tabulation {}% } {% pas de \multicolumn \unless\ifST_hiddencol% si la cellule n'est pas masquée \unless\ifnum\ST_col_cnt>\ST_lastshowcol% si ce n'est pas la dernière cellule affichée \ST_add_tomacro\ST_tab&%on ajoute la tabulation \fi \fi }% }% {% \ST_ifvalid_csname{endrow_\number\ST_row_cnt}% {\expandafter\ST_eadd_tomacro\expandafter\ST_tab\csname endrow_\number\ST_row_cnt\endcsname}% ajoute la fin de la ligne {}% \ST_col_cnt1 % on remet la colonne à 1 \advance\ST_row_cnt1 % on passe à la ligne suivante }% }% }% \ST_nextcell } %-------------------------------- %----- Tableaux de débogage ----- %-------------------------------- % format des lettres et nombres représentant les coordonnées (helvetica gras très petit) \def\ST_debugformat_headers{\usefont{T1}{phv}{b}{n}} % format utilisé pour les cellules \def\ST_debug_format_cells{\usefont{T1}{lmtt}{m}{n}} \def\ST_colorcell{\ifST_colortblloaded\noexpand\cellcolor[gray]{.6}\fi} \def\ST_show_debug_tab#1{% \begingroup \def\ST_debuginfo{#1}% \ifmmode\scriptscriptstyle\else\scriptsize\fi \def\ST_temp_a{formula}% pour n'afficher les fins de ligne que dans ce cas \IfStrEqCase\ST_debuginfo{% {formula}{}% {text}{}% {code}{}% }[\ST_illegaldebugcommand]% \ST_debug_format_cells \tabcolsep0.3em \ST_row_cnt1 \ST_col_cnt1 \edef\ST_debugtab{% \noexpand\begin{tabular}{r|*{\number\numexpr\ST_lastshowcol+1}{c|}}% \noexpand\multicolumn1{c}{\ST_colorcell}&% }% \loop \ST_xadd_tomacro\ST_debugtab{\noexpand\multicolumn1c{\ST_colorcell\noexpand\ST_debugformat_headers\csname @Alph\endcsname\ST_col_cnt}}% \ifnum\ST_col_cnt<\ST_lastshowcol \advance\ST_col_cnt1 \ST_add_tomacro\ST_debugtab&% \repeat \ST_col_cnt1 \ST_xadd_tomacro\ST_debugtab{% &% passe à la dernière colonne de la première ligne \noexpand\multicolumn1l{% \ifx\ST_temp_a\ST_debuginfo \ST_ifvalid_csname{endrow_\number\ST_row_cnt}% {\detokenize\expandafter\expandafter\expandafter{\csname endrow_0\endcsname}}% {}% \fi}% \noexpand\\\noexpand\cline{2-\number\numexpr\ST_lastshowcol+1}% }% \ST_debug_tab_a } \def\ST_debug_tab_a{% affiche le tableau de débobage \ST_ifnum{\ST_row_cnt>\ST_total_rownumber\space} {% \ST_xadd_tomacro\ST_debugtab{\noexpand\cline{2-\number\numexpr\ST_lastshowcol+1}\noexpand\end{tabular}}% \ST_debugtab% affichage du tableau de débogage \ifmmode \\[0.5ex]% \else \par\smallskip% retour à la ligne \fi \endgroup } {% \relax\ST_ifnum{\ST_col_cnt=1 } {\ST_xadd_tomacro\ST_debugtab{\noexpand\multicolumn1{c|}{\ST_colorcell\noexpand\ST_debugformat_headers\number\ST_row_cnt}&}} {}% \ST_ifcsname{\ST_debuginfo _\number\ST_col_cnt _\number\ST_row_cnt}% si l'info existe pour la cellule concernée {% \ifx\ST_temp_a\ST_debuginfo \expandafter\ST_subst\csname\ST_debuginfo _\number\ST_col_cnt _\number\ST_row_cnt\endcsname|{}% \fi \ST_xadd_tomacro\ST_debugtab{\detokenize\expandafter\expandafter\expandafter{\csname\ST_debuginfo _\number\ST_col_cnt _\number\ST_row_cnt\endcsname}}% on ajoute la cellule au tableau que l'on a detokenisée au préalable } {}% \advance\ST_col_cnt1 % on passe à la colonne suivante ! \ST_ifnum{\ST_col_cnt>\ST_lastshowcol\space}% si c'est la dernière cellule affichée {% \ST_xadd_tomacro\ST_debugtab{% &\noexpand\multicolumn1l{% \ifx\ST_temp_a\ST_debuginfo \ST_ifvalid_csname{endrow_\number\ST_row_cnt}% {\detokenize\expandafter\expandafter\expandafter{\csname endrow_\number\ST_row_cnt\endcsname}}% {}% \fi}% \noexpand\\\noexpand\cline{2-\number\numexpr\ST_lastshowcol+1}}% \ST_col_cnt1 % on remet la colonne à 1 \advance\ST_row_cnt1 % on passe à la ligne suivante } {% il reste encore des cellules dans la ligne \ST_add_tomacro\ST_debugtab&% on ajoute la tabulation }% \ST_debug_tab_a }% } %------------------------------------------------------- %----- Environnement spreadtab et macros publiques ----- %------------------------------------------------------- \def\STprintnum#1{#1} \ST_e_second\AtBeginDocument{% \csname @ifpackageloaded\endcsname{colortbl} \ST_colortblloadedtrue \ST_colortblloadedfalse } \def\STtag#1{%% renvoie la valeur numérique de la cellule de tag #1 \ST_ifcsname{ST_celltag_\detokenize\expandafter{#1}} {\csname ST_celltag_\detokenize\expandafter{#1}\endcsname} {\PackageError\STname{Tag "#1" is unknown, have you defined it?}\STseedoc_a}% } \long\def\ST_get_body_env#1\end#2{% \ST_add_tomacro\ST_tab{#1}% \def\ST_temp_a{#2}% \ST_ifx{\ST_temp_a\STname} {% \spreadtab_a } {% \ST_add_tomacro\ST_tab{\end{#2}}% \ST_get_body_env }% } \long\def\ST_get_body_cs#1\endspreadtab{% \def\ST_tab{#1}% \spreadtab_a } \newcommand*\spreadtab[2][]{% \ST_e_second\ST_ifx{\csname @currenvir\endcsname\STname}% on vient d'un \begin{spreadtab} ? {% \let\ST_get_body\ST_get_body_env } {% \begingroup \let\ST_get_body\ST_get_body_cs }% \expandarg% 1-développement des arguments (réglages de xstring) \noexploregroups% pas d'exploration des groupes (réglages de xstring) \catcode`\: 12 \catcode`\; 12 % changer les catcodes pour éviter les incompatibilités avec frenchb \def\ST_tab_preamble{#2}% préambule du tableau \StrChar{\empty#2}1[\ST_tab_name]% \STset{#1}% \let\ST_copy_list\empty \let\ST_row_skiplist\empty \let\ST_col_skiplist\empty \def\ST_last_skipcol{0}% \let\ST_tab\empty \ST_emit_message{% ^^J% New spreadtab: \string\begin\detokenize{#2}^^J% * reading tab:% }% \ST_get_body% met le code du tableau dans \ST_tab } \def\spreadtab_a{% \ST_scan_stxp% cherche \STxp et x-développe son argument \let\ST_tag_list\empty \let\ST_ref_found\empty \ST_read_tab% analyse le tableau contenu dans \ST_tab \ST_emit_message{ok^^J}% \ST_search_lastshowcol% cherche la dernière colonne affichée \ST_e_second{\setKV[STdebug]}{\ST_debug_directives}% \ST_if_show_debug_tabtrue% à priori, on n'est pas en mode débogage \ifboolKV[STdebug]{text} {\ST_if_show_debug_tabfalse\ST_show_debug_tab{text}} {}% \ifboolKV[STdebug]{formula} {\ST_if_show_debug_tabfalse\ST_show_debug_tab{formula}}{}% \ifboolKV[STdebug]{cellcode}{\ST_if_show_debug_tabfalse\ST_show_debug_tab{code}} {}% \ifboolKV[STdebug]{show tab}{\ST_if_show_debug_tabtrue} {}% \ifST_if_show_debug_tab% si on doit afficher le tableau, on fait le boulot \ST_emit_message{* computing formulas:^^J}% \ST_eval_tab% \ST_run_save_cell_list \ST_run_aux_save_cell_list \ST_e_second{\def\ST_tab}{\expandafter\begin\ST_tab_preamble}% \ST_emit_message{* building tab:}% \ST_build_tab \ST_emit_message{ok^^J}% \ST_eadd_tomacro\ST_tab{\expandafter\end\ST_tab_name}% \ST_antefi \useKV[\STname]{pretab code}% \ST_tab% affiche le tableau \useKV[\STname]{posttab code}% \fi \ST_emit_message{End of spreadtab: \string\end\detokenize\expandafter{\ST_tab_name}^^J^^J}% \ST_tag_list \ST_sanitize_cs \endgroup \restoreKV[STdebug]% } %---------------------------------- %----- Réglages et paramètres ----- %---------------------------------- \def\ST_set_display_marks#1{% \ST_set_display_marks_a#1\_nil } \def\ST_set_display_marks_a#1;#2\_nil{% \skv_stripsp{\def\ST_startdisplay}{#1}% \skv_stripsp{\def\ST_enddisplay}{#2}% \ST_ifnum{\ST_ifx{\ST_startdisplay\empty}10\ST_ifx{\ST_enddisplay\empty}10>0 } {% \PackageError\STname{Empty delimiter not allowed^^J"<<" and ">>" taken by default}\STseedoc_a \def\ST_startdisplay{<<}% \def\ST_enddisplay{>>}% }% {} } \def\ST_set_freeze_char#1{% \expandafter\ST_set_freeze_char_a\detokenize{#1!}\_nil } \def\ST_set_freeze_char_a#1#2\_nil{% \def\ST_tranpose_char{#1}% } \setKVdefault[STdebug]{ text = false, formula = false, cellcode = false, show tab = false, } \def\STset#{\setKV[\STname]} \def\STreset{\restoreKV[\STname]} \def\ST_defifempty#1#2#3{\ST_ifempty{#2}{\def#1{#3}}{\def#1{#2}}} \defKV[\STname]{% tabline sep = \ST_defifempty\ST_eol{#1}{\\}, dec sep = \ST_defifempty\ST_decimal_sep{#1}{.}, save list = \def\ST_savecell_list{#1}, aux save list = \def\ST_aux_savecell_list{#1}, display marks = \ST_set_display_marks{#1}, debug = \def\ST_debug_directives{#1}, autoround = \def\ST_round_digit{#1}, text mark = \ST_exp_two_args{\ST_defifempty\ST_text_mark}{\detokenize{#1}}{\string @}, numeric mark = \ST_defifempty\ST_numeric_mark{#1}{:=}, freeze char = \ST_set_freeze_char{#1}, } \def\ST_default_dec_sep{.}% le séparateur décimal par défaut \setKVdefault[\STname]{% tabline sep = \\, dec sep = ., messages = false, save list = {}, aux save list = {}, display marks = <<;>>, tag to aux = false, debug = {}, autoround = {}, text mark = @, numeric mark = {:=}, freeze char = !, pretab code = {}, posttab code = {}, } %------------------------------ %----- Macros auxiliaires ----- %------------------------------ \def\ST_save_cell#1#2{% met dans la sc #1 la valeur du champ numérique de la cellule spécifiée par sa référence ABSOLUE \skv_stripsp{\skv_stripsp\ST_save_cell_a{#1}}{#2}% } \def\ST_save_cell_a#1#2#3{% #3=\ST_gobone ou \ST_id \def\ST_temp_ref{#2}% \ST_if_ref\ST_temp_ref{}\ST_illegal_ref \ST_if_instr\ST_temp_ref[\ST_illegal_ref{}% \ST_letcs#1{formula_\ST_temp_formula}% le nombre est assigné à #1 \unless\ifx\ST_decimal_sep\ST_default_dec_sep \ST_subst_decimal_sep#1% et si le "." doit être remplacé par "," on substitue \fi \global\let#1#1% on rend l'assignation globale #3{\expandafter\immediate\expandafter\write\csname @auxout\endcsname{\gdef\noexpand#1{#1}}}% } \def\ST_run_save_cell_list{% \ST_ifx{\ST_savecell_list\empty} {} {\expandafter\ST_run_save_cell_list_a\ST_savecell_list,\ST_quark=,}% } \def\ST_run_save_cell_list_a#1=#2,{% \ST_ifx{\ST_quark#1} {% \let\ST_savecell_list\empty% fini -> vider la liste } {% \ST_save_cell{#1}{#2}\ST_gobone% \ST_run_save_cell_list_a }% } \def\ST_run_aux_save_cell_list{% \ST_ifx{\ST_aux_savecell_list\empty} {} {\expandafter\ST_run_aux_save_cell_list_a\ST_aux_savecell_list,\ST_quark=,}% } \def\ST_run_aux_save_cell_list_a#1=#2,{% \ST_ifx{\ST_quark#1} {% \let\ST_aux_savecell_list\empty% fini -> vider la liste } {% \ST_save_cell{#1}{#2}\ST_id% assigne normalement _et_ écrit dans le fichier aux \ST_run_aux_save_cell_list_a }% } \def\ST_scan_stxp{% \IfSubStr\ST_tab{\noexpand\STxp} {\expandafter\ST_scan_stxp_a\ST_tab\_nil} {}% TODO : chercher \STxp dans des accolades et renvoyer une erreur ? } \def\ST_scan_stxp_a#1\STxp#2#3\_nil{% \edef\ST_tab{\unexpanded{#1}#2\unexpanded{#3}}% \ST_scan_stxp } \def\ST_sanitize_cs{% \ST_cslet{endrow_0}\ST_quark \ST_sanitize_cs_a1} \def\ST_sanitize_cs_a#1{% \ST_ifnum{#1>\ST_total_rownumber\space} {} {% \def\ST_temp_a{#1}% \ST_cslet{endrow_#1}\ST_quark \ST_sanitize_cs_b1% \ST_e_second\ST_sanitize_cs_a{\number\numexpr#1+1}% }% } \def\ST_sanitize_cs_b#1{% \ST_ifnum{#1>\ST_total_colnumber\space} {} {% \ST_cslet{formula_#1_\ST_temp_a}\ST_quark \ST_cslet{code_#1_\ST_temp_a}\ST_quark \ST_cslet{text_#1_\ST_temp_a}\ST_quark \ST_e_second\ST_sanitize_cs_b{\number\numexpr#1+1}% }% } \ST_restorecatcode %------------------------------------------ %----- Macros utilisateur à supprimer ----- %------------------------------------------ \def\STautoround#1{\STset{autoround={#1}}} \def\STsetdisplaymarks#1#2{\STset{display marks={#1;#2}}} \def\STsetdecimalsep#1{\STset{dec sep={#1}}} \def\STeol#1{\STset{tabline sep={#1}}} \def\STtextcell#1{\STset{text mark={#1}}} \def\STmessage#1{\STset{messages={#1}}} \def\STnumericfieldmarker#1{\STset{numeric mark={#1}}} \def\STtranposechar#1{\STset{freeze char={#1}}} \endinput %---------------------- %----- Historique ----- %---------------------- v0.1alpha avril 2009 ------------------------------------------------------------------------------- v0.1beta1 2009/06/06 ------------------------------------------------------------------------------- v0.1beta2 2009/06/07 1 Une valeur négative dans une cellule provoque un bug. Les valeurs négatives sont désormais mises entre parenthèses 2 Espaces supprimés en début de formules. ------------------------------------------------------------------------------- v0.1beta3 2009/06/12 1 Espaces laissés dans les formules pour pouvoir utiliser la notation postfixée de fp. 2 Les références ne sont plus «@(B4)» mais indifféremment «b4» ou «B4». 3 Références relatives possibles par [x,y] ou x et y sont les décalages de la colonne et de la ligne par rapport à la cellule où est la formule. 4 Bugs corrigés pour rendre le package compatible avec tabularx ou tabulary (entre autres). ------------------------------------------------------------------------------- v0.1beta4 2009/06/21 1 Les espaces sont supprimés au début de chaque cellule, cela créait un bug lorsque la cellule commençait par un nombre négatif. 2 Mise en place de la compatibilité avec la commande \multicolumn{nbre}{type}{contenu} du package éponyme 3 Possibilité de masquer des lignes ou des colonnes entières avec les commandes \SThiderow et \SThidecol Seule condition : aucune colonne masquée ne doit se trouver dans les colonnes impliquées dans un \multicolum à moins de prendre de grandes précautions et savoir les conséquences que cela occasionne. ------------------------------------------------------------------------------- v0.1beta5 2009/06/29 1 Amélioration des messages d'erreur et d'information 2 Suppression de tests superflus 3 Redéfinition de \ST_text_mark en «@» qui est plus simple 4 Implémentation de \STsavecell 5 écriture de la documentation provisoire en français ------------------------------------------------------------------------------- v0.1beta6 2009/08/23 1 Correction d'un bug : dans une cellule, on ne pouvait pas écrire plusieurs fois la même référence. 2 Correction d'un bug avec \multicolumn : cette commande était mal gérée lorsqu'employée avec une cellule de texte. 3 Implémentation de macro-fonctions avec différenciation selon le type d'argument et le type de donnée renvoyée. 4 Possibilité d'imbrication des macro-fonctions. 5 Mise en place d'un environnement « spreadtab » 6 Nombreuses optimisations pour une meilleure vitesse d'exécution ------------------------------------------------------------------------------- v0.1pre 2009/09/02 1 Mise au point des messages d'erreurs et des arrêts de compilation selon les erreurs rencontrées. 2 Correction d'un bug dans \ST@coord@toref 3 Les cellules vides, textuelles ou jointes par un \multicolumn sont ignorées dans les plages de cellules concernées par les fonctions sum et sumprod 4 Les noms de mois accentués et \today sont désormais permis en argument de la fonction frlongdatetonum 5 somprod corrigé en sumprod, plus anglais ! 6 La macro fonction rnd, trop complexe est supprimée au profit de rand et randint 7 Améliorations et optimisations ------------------------------------------------------------------------------- v0.1 2009/11/03 Première version publique sur le CTAN ------------------------------------------------------------------------------- v0.2 2010/01/24 1 On peut définir le séparateur décimal par la macro \STsetdecimalsep{} 2 Il est possible de copier une formule dans le tableau à l'aide de \STcopy{>a,vb}{formule} où a et b sont les nombres de cellules horizontaux et verticaux vers lesquels la formule sera copiée. 3 Désormais, spreadtab est entièrement compatible avec toutes les commandes du package booktabs 4 La commande \noalign et son argument est prise en compte lorsqu'elle se trouve après un \\ 5 Suppression d'espaces indésirables 6 Mise en place d'un mode débogage où l'on peut visualiser les champs numériques, les champs textuels ou les codes des cellules du tableau ------------------------------------------------------------------------------- v0.2a 2010/02/02 1 Ajout de la traduction en vietnamien et correction d'erreurs dans la documentation française. 2 Implementation beta et donc non visible des macros fonctions gcd, lcm Ces macro-fonctions ne sont pas encore documentées. ------------------------------------------------------------------------------- v0.3 2010/03/28 1 La macro \STautoround{x} admet une version étoilée pour laquelle les nombres sont arrondis et formatés pour avoir x chiffres après la virgule en rajoutant des 0 inutiles si besoin 2 La valeur sauvegardée par \STsavecell tient désormais compte du séparateur décimal 3 Macro fonctions "gcd" (PGCD), "lcm" (PPCM) et "scitodec" pour convertir une écriture scientifique en une écriture décimale 4 Dans le champ textuel d'une cellule, on peut désormais afficher le champ numérique d'une cellule avec <> 5 Amélioration de l'algorithme pour \STcopy : enleve une formule de \ST@copylist lorsqu'on a dépassé la dernière ligne de la plage où elle doit être copiée 6 Correction d'un bug lorsque la macro-fonction admettant un argument textuel a un argument faisant référence à une autre cellule : enlever ":=" si besoin 7 Correction d'un bug dans xstring qui changeait les catcodes des tokens de \@xs@afterinteger lorsqu'on appelle \ST@IfInteger 8 Correction d'un bug dans \ST@build@tab@i pour prendre en compte les cellules de code 0 dans lesquelles ":=" doit être supprimé ------------------------------------------------------------------------------- v0.3a 2010/05/15 1 Correction d'un bug dans \ST@gobble@remain : ce qui reste à manger peut contenir des tokens de catcode 1 ou 2 ce qui fait que l'utilisation d'arguments délimités ne fonctionnerait pas dans ces cas. ------------------------------------------------------------------------------- v0.3b 2010/06/06 1 Correction d'un bug concernant les macro-fonctions renvoyant un texte. Lorsque leur argument contenait une référence, celle-ci devenait une cellule texte. 2 Correction d'un bug sur la macro \ST@stackcall : il faut y enlever toutes les occurrences de la cellule en train d'être calculée 3 Modification de l'ordre de calcul des cellules concernées par les macro fonctions sum et somprod 4 Correction d'un bug dans la façon dont sont produites les skiplist \ST@row@skiplist et \STcol@skiplist 5 Implémentation de la macro fonction "id" ------------------------------------------------------------------------------- v0.3c 2011/04/08 1 Correction d'un gros bug lors de la substitution d'une référence de type lorsque nbre>10 : a10 était vu comme "a1" suivi de 0, et même pour tous les nbre>10 ! 2 Macro \STprintnum ------------------------------------------------------------------------------- v0.4 2011/07/04 1 Les calculs se font en dehors d'un groupe pour éviter l'erreur de save-satck 2 Possibilité d'écrire aussi \spreadtab\endspreadtab 3 Macros fonctions tag, cell, row, col et la macro \STtag qui permet de faire appel à la valeur de la cellule marquée par la macro fonction tag. \STmakegtag rend les sauvegardes globales. 4 La commande \STeol permet de choisir quel est le marqueur de fin de ligne dans les tableau de spreadtab 5 dans une cellule mixte, les macro fonctions renvoyant du texte ont leur résultat qui prend la place de :={} ------------------------------------------------------------------------------- v0.4a 2011/08/11 1 Un bug corrigé dans \ST@search@hline@i ------------------------------------------------------------------------------- v0.4b 2012/05/13 1 Un bug corrigé dans \ST@func@sum ------------------------------------------------------------------------------- v0.4c 2014/11/06 1 Un bug corrigé dans \ST@analyse@text@frdate et \ST@analyse@text@engdate : si #1 commence par un espace parasite, celui-ci est désormais retiré. 2 Bug corrigé dans \ST@displaynumfields : les macros \ST@current@colnumber et \ST@current@rownumber sont définies avant d'appeler \ST@ifref ------------------------------------------------------------------------------- v0.4d 2018/01/02 1 Bug corrigé dans \STeval@cell : le \edef\ST@dependance@tree corrige le \def ! 2 Ajout d'un message d'erreur si une la fonction sum a une plage qui contient la cellule courante elle-même 3 Nouvelle macro fonction "value" 4 Nettoyage du code ------------------------------------------------------------------------------- v0.5 2019/02/27 1 Choix du moteur de calcul : fp ou xfp ------------------------------------------------------------------------------- v0.51 2020/06/09 1 Bug corrigé : les \if..\fi n'étaient pas équilibrés lors des appels aux macros \STif.. avec le moteur fp ------------------------------------------------------------------------------- v0.51 2023/09/11 1 Bug corrigé : les anciennes macros de xstring \IfInteger et \IfDecimal sont incorporées à spreadtab qui sinon est cassé ------------------------------------------------------------------------------- v0.6 2025/02/27 1 l3fp est désormais le seul moteur de calcul possible donc il est obligatoire d'utiliser une version de LaTeX postérieure à l'intégration de xfp dans le noyau le 2022/06/01. 2 Le support du vieux moteur de calcul fp est définitivement abandonné 3 Abandon des options de package puisqu'il n'y a plus le choix du moteur de calcul 4 l'environnement agit désormais dans un groupe semi simple 5 Le package simplekv est chargé pour utiliser le système de clé/valeur. Mise à disposition de la macro \STset{cles/valeurs} 6 /!\ changement de la syntaxe : l'argument optionnel de \begin{spreadtab} contient désormais *exclusivement* les clés/valeurs et donc - \STsavecell est remplacé par la clé "save list" - \STdebug et \STdisplaytab remplacés par la clé "debug" Cela va *casser* l'utilisation précédente de l'argument optionnel, mais ce changement est requis malheureusement ! 7 Si \STxp est présent dans le tableau la macro provoque le développement maximal de son argument pendant la lecture du tableau 8 Suppression de la macro \STmakegtag, les assignations par la macro- fonction 'tag' sont toujours globales à cause du point 4 9 La fonction tag permet, si la clé "tag to aux" est 'true', d'écrire les assignations dans le fichier '.aux' 10 Clé "aux save list" aaynt pour valeur 'macroA=cellA,macroB=cellB,...' : écrire les assignations dans le fichier '.aux' 11 Les macros \STautoround, \STsetdecimalsep, \STeol, \STsetdisplaymarks et \STmessage, \STnumericfieldmarker, \STtranposechar et \STtextcell bien qu'encore fonctionnelles (mais plus documentées), seront *supprimées* à la prochaine version au profit de \STset{clé=valeur} 12 les macros-fonctions de test ifeq, ifgt et iflt seront supprimées à la prochaine version puisque l3fp dispose de l'opérateur '?' 13 Mise en forme, nettoyage et améliorations du code