% Ce fichier contient le code de l'extension "spreadtab"
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                    %
\def\STname                   {spreadtab}                            %
\def\STver                       {0.61}                              %
%                                                                    %
\def\STdate                   {2025/03/14}                           %
%                                                                    %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                    %
% 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
	\catcode0=\the\catcode0 \relax
}
\catcode`\_ 11
\catcode0 12
%-------------------------------
%----- Macros généralistes -----
%-------------------------------
\chardef\ST_stop0
\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}
\def\ST_swaptwo#1#2{#2#1}
\def\ST_e_after#1#2{\expandafter\ST_swaptwo\expandafter{#2}{#1}}% #1#2 -> #1*#2
\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_take_first_char#1#2{% #1= argument #2=macro qui reçoit le 1er car détokénisé de #1
	\expandafter\ST_take_first_char_a\detokenize{#1}\_nil#2%
}
\def\ST_take_first_char_a#1#2\_nil#3{\def#3{#1}}
\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,numtofrshortdate,numtoengshortdate,numtofrlongdate,gcd,lcm,%
	numtoenglongdate,numtofrmonth,numtoengmonth,numtofrday,numtoengday,test%
}

\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é
	gcd,lcm,value,test%
}

\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,test%
}

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

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

\def\STxp{%
	\PackageError\STname{%
		\string\STxp\space remains after tab scan^^J
		probably because inside braces.%
	}\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_unsafe#1#2#3{% Dans la sc #1, substitue la première occurrence du pattern #2 par le pattern #3
	\def\ST_subst_once_unsafe_a##1#2##2\_nil{\def#1{##1#3##2}}%
	\ST_if_instr#1{\empty#2}
		{\expandafter\ST_subst_once_unsafe_a#1\_nil}
		{}%
}

\def\ST_esubst_once_unsafe#1#2#3{% Dans la sc #1, substitue la première occurrence du pattern #2 par le pattern \romannumeral#3
	\def\ST_esubst_once_unsafe_a##1#2##2\_nil{\ST_e_after{\ST_esubst_once_unsafe_b##1}{\romannumeral#3##2}\_nil#1}%
	\ST_if_instr#1{\empty#2}
		{\expandafter\ST_esubst_once_unsafe_a#1\_nil}
		{}%
}
\def\ST_esubst_once_unsafe_b#1\_nil#2{%
	\def#2{#1}%
}

\def\ST_esubst_once#1#2#3{% Dans la sc #1, substitue la première occurrence du pattern #2 par le pattern \romannumeral#3
	\StrSubstitute[1]{#1}{\empty#2}{\empty\romannumeral#3}[#1]%
}


\def\ST_subst_unsafe#1#2#3{% Dans la sc #1, substitue le pattern #2 par le pattern #3
	\def\ST_subst_unsafe_a##1#2##2\_nnil{%
		\ST_ifempty{##2}
			{%
			\def#1{##1}%
			\ST_remove_to_nnil
			}
			{%
			\ST_subst_unsafe_a
			}%
		##1#3##2\_nnil
	}%
	\expandafter\ST_subst_unsafe_a#1#2\_nnil
}

\def\ST_eargs_subst_unsafe#1#2#3{%
	\ST_exp_two_args{\ST_subst_unsafe#1}{#2}{#3}%
}

\def\ST_subst#1#2#3{% Dans la sc #1, substitue le pattern #2 par le pattern #3
	\StrSubstitute{#1}{\empty#2}{\empty#3}[#1]%
}
\def\ST_eargs_subst#1#2#3{%
	\StrSubstitute{#1}{#2}{#3}[#1]%
}

\def\ST_remove_sp#1{%
	\ST_subst_unsafe#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 {<formule}
		\exploregroups
		\let\ST_temp_b\ST_numeric_mark
		\ST_eadd_tomacro\ST_temp_b\ST_temp_a
		\StrSubstitute[1]\ST_current_cell\ST_temp_b\ST_numeric_mark[\ST_current_cell]% on remplace :={<formule>} 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
					\ST_e_second{\def\ST_add_to_copylist}{\ST_copyformula^^00}% \ST_add_to_copylist est le nouvel élément à ajouter à la copylist
					\ST_xadd_tomacro\ST_add_to_copylist{[\number\ST_col_cnt,\number\ST_row_cnt](\ST_hoffest,\ST_voffest)}%
					\ST_eadd_tomacro\ST_add_to_copylist\ST_copy_list% ajoute la copylist à la fin
					\let\ST_copy_list\ST_add_to_copylist% et l'assigne à copylist
					\ST_shift_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_shift_formula#1#2#3#4{%
	\def\ST_addcol{#1}%
	\def\ST_addrow{#2}%
	\let\ST_temp_formula#3%
	\StrCount{\ST_temp_formula}{\ST_copy_char}[\ST_occurs_copy_char]%
	\ST_ifnum{\ST_occurs_copy_char>1 }
		{%
		\let#4\empty
		\ST_shift_formula_a#4%
		}
		{%
		\let\ST_shifted_formula\empty
		\ST_shift_formula_b
		\let#4\ST_shifted_formula
		}%
}

\def\ST_shift_formula_a#1{%
	\StrBefore\ST_temp_formula\ST_copy_char[\ST_temp_formula_before]%
	\StrBehind[2]\ST_temp_formula\ST_copy_char[\ST_temp_formula_after]%
	\StrBetween[1,2]\ST_temp_formula\ST_copy_char\ST_copy_char[\ST_temp_formula]%
	\let\ST_shifted_formula\empty
	\ST_shift_formula_b
	\ST_xadd_tomacro#1{%
		\unexpanded\expandafter{\ST_temp_formula_before}%
		\unexpanded\expandafter{\ST_shifted_formula}%
		}%
	\StrCount{\ST_temp_formula}{\ST_copy_char}[\ST_occurs_copy_char]%
	\ST_ifnum{\ST_occurs_copy_char>1 }
		{%
		\let\ST_temp_formula\ST_temp_formula_after
		\ST_shift_formula_a#1%
		}
		{%
		\ST_eadd_tomacro#1\ST_temp_formula_after
		}%
}

\def\ST_shift_formula_b{%
	\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_shifted_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_shifted_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_shifted_formula{[\ST_addcol_,\number\numexpr\ST_rel_num-\ST_addrow_]}}%
						{\ST_illegal_relativeref}%
					}%
					\ST_illegal_relativeref
				}
				{%
				\ST_eadd_tomacro\ST_shifted_formula\ST_firstcar
				}%
			}%
		\ST_shift_formula_b
	}%
}

% 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{<arg1>}{<arg2>} 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^^00[#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_shift_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_unsafe\ST_copy_list{#1^^00[#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^^00
			{}
			{\ST_exp_two_args{\ST_subst_once_unsafe#1}\ST_ref_found{\ST_ref_found^^00}}%
	\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_rownumber0>0 % si ce nombre est non vide et plus grand que 0 -> référence valide BUGFIX 0.61
				\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_unsafe\csname formula_#1_#2\endcsname{\ST_ref_found^^00}\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_unsafe\ST_coord _,% coordonnées appelées
						\ST_multicol_cell
					\fi
					}%
					{%
					\edef\ST_temp_callcell{#1,#2}% coordonnées de la cellule appelante
					\ST_subst_unsafe\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,}%
						{}%
						{\ST_fpeval\ST_function_argfound\ST_function_argfound}% doit-on calculer l'argument de cette fonction ?
					\csname ST_func_\ST_function_namefound\endcsname\ST_function_argfound\ST_result_func% puis on évalue la fonctionfound
					\IfSubStr{\ST_functions_with_textresult,}{\ST_function_namefound,}% si macro-fonction renvoie un texte
						{%
						\ST_exp_two_args{\ST_esubst_once\ST_current_formula}\ST_to_subst\ST_result_func% on replace dans l'alias en développant <resultat><reste formule>
						\ST_csdef{code_#1_#2}{0}% neutralise tout le restant de la cellule
						\ST_cslet{text_#1_#2}\ST_current_formula% on met tout dans le champ texte
						\ST_cslet{formula_#1_#2}\empty
						}
						{%
						\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}}%
						}%
					}
					{%
					\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^^00}\ST_current_formula
						\ST_subst\ST_to_subst^^00{}%
						\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_unsafe\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_unsafe\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_unsafe\ST_to_subst^^00{}%
						\csname ST_func_\ST_function_namefound\endcsname\ST_current_formula\ST_result_func% puis on évalue la fonction
						\expandafter\ST_eargs_subst_unsafe\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_unsafe\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 <plage de cellules>
		{%
		\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_\number\ST_cnt\endcsname{\number\numexpr\ST_target_colnumber-\ST_mat_firstcol}%
			\expandafter\edef\csname ST_vectorrow_\number\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_\number\ST_cnt\endcsname _\number\numexpr\ST_row_cnt_a+\csname ST_vectorrow_\number\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_unsafe\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%
		}%
}

% 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{\noexpand\ST_stop\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{\noexpand\ST_stop\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{\noexpand\ST_stop\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{\noexpand\ST_stop\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{\noexpand\ST_stop\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{\noexpand\ST_stop\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{\noexpand\ST_stop\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{\noexpand\ST_stop\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
		\ST_e_second{\ST_subst_unsafe#3}{\ST_e}e%
		\ST_subst_unsafe#3\'{}%
		\ST_e_second{\ST_subst_unsafe#3}{\ST_u}u%
		\ST_subst_unsafe#3\^{}%
		\lowercase\expandafter{\expandafter\def\expandafter#3\expandafter{#3}}% BUGFIX 0.61
		\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
		}%
}

\def\ST_func_test#1#2{% #1=csv de tests #2=sc recevant le résultat
	\expandafter\ST_func_test_a\expandafter#2\expandafter1#1,1,\_nil
}
\def\ST_func_test_a#1#2#3,{% #1=macro recevant le résultat #2=compteur de test, #3=test en cours
	\ST_ifnum{\fpeval{#3}=1 }
		{\ST_first_tonil{\def#1{\ST_func_test_b#2}}}
		{\ST_e_second{\ST_func_test_a#1}{\number\numexpr#2+1\relax}}%
}

\def\ST_func_test_b#1(#2){% #1=index voulu  #2=csv textes
	\ST_e_second{\ST_func_test_c1}{\number\numexpr#1}#2,\ST_quark,%
}
\def\ST_func_test_c#1#2#3,{% #1=index courant #2=index voulu #3=texte courant
	\ST_ifx{\ST_quark#3}
		{%
		\ST_stop
		}
		{%
		\ST_ifnum{#1=#2 }
			{\ST_func_test_d{#3}}
			{\ST_e_second\ST_func_test_c{\number\numexpr#1+1}{#2}}%
		}%
}
\def\ST_func_test_d#1#2\ST_quark,{\ST_stop#1}

%----------------------------------------------
%----- 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 "<<ref>>" 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 <<ref>> 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_unsafe\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
	\STset{debug={}}% BUGFIX 0.61
}

\def\STrep{%
	\STrep_a1\_nil%
}
\def\STrep_a#1\_nil#2#3{%
	\ST_ifnum{#1>#2 }
		{}
		{\expandafter\STrep_a\the\numexpr#1+1\relax\_nil{#2}{#3}#3}%
}

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

\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_take_first_char{#1!}\ST_tranpose_char,
	copy char     = \ST_take_first_char{#1"}\ST_copy_char,
}
\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  = {},
	copy char     = ",
}

%------------------------------
%----- 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
\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{<caractère>}
    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 <<ref>>
    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 <lettre><nbre> 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<code tableau>\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 :={<formule>}
-------------------------------------------------------------------------------
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
-------------------------------------------------------------------------------
v0.61       2025/03/14
   1   Correction d'un bug dans \ST_return_ref_in_formula_a : si après une
       lettre, rien n'est trouvé (\ST_target_rownumber est vide), on met 0 et
       donc la formule "a1+pi" ne provoque plus de bug
   2   Correction d'un bug qui fait que le débogage est persistent : rajout de
       \STset{debug={}} après sortie du groupe et suppression de
       \restoreKV[STdebug]
   3   Correction d'un bug : si un macro-fonction donnant du texte est évaluée,
       le travail sur la cellule cesse et elle passe comme cellule textuelle.
       Le bug "numtofrmonth(0)2025" est évité (mars2025 -> ST ne cherche plus
       la  cellule de coordonnées 's2025')
   4   Correction d'un bug : il ne faut pas appeler \ST_subst lorsque le
       pattern a des accolades, par exemple "{&&}", puisque \ST_subst_a plante
       avec un # qui se retrouve dans la nature. Pareil avec \ST_esubst_once_a.
       On revient donc aux macros de xstring, + lentes mais + sûres et ajout de
       "_unsafe" aux macros non sûres (utilisées à bon escient ailleurs)
   5   Correction d'un bug dans \ST_analyse_text_frdate
   6   suppression des macros-fonctions de comparaison iflt, ifgt et ifeq
   7   suppression des macros \STautoround, \STsetdecimalsep, \STeol,
       \STsetdisplaymarks, \STmessage, \STnumericfieldmarker, \STtranposechar
       et \STtextcell. Tout se passe désormais par \STset{clé=valeur}
   8   Nouvelle macro-fonction 'test' qui retourne des textes selon l'issue de
       tests en cascade
   9   Caractère d'échappement «"» pour délimiter la partie qui doit être
       décalée lors d'une copie. Si absence de 2 «"», toute la cellule est
       prise en compte. À utiliser avec les macros qui retournent du texte
   10  Clé "copy char" pour choisir le délimiteur du point précédent
   11  Création d'une macro \STrep{<n>}{<arg>}, purement développable,
       qui reproduit <n> fois l'<arg> et qui a vocation à se trouver dans 
       l'argument de \STxp