%% programmes-cpge.sty %% Les programmes officiels de physique-chimie des classes préparatoires %% scientifiques (MPSI, PCSI, MP, PC, PSI), à citer, parcourir et composer. %% %% Copyright (C) 2026 Antonin Marchand %% %% This work may be distributed and/or modified under the conditions of the %% LaTeX Project Public License, either version 1.3c of this license or (at %% your option) any later version. The latest version of this license is in %% https://www.latex-project.org/lppl.txt %% %% This work has the LPPL maintenance status `maintained'. %% The Current Maintainer of this work is Antonin Marchand. %% %% Les textes des programmes, dans les fichiers programmes-cpge-*.tex, sont %% ceux publiés par le ministère de l'Enseignement supérieur ; voir la %% documentation, section « Les textes et leur licence ». \NeedsTeXFormat{LaTeX2e}[2022-06-01] \ProvidesExplPackage{programmes-cpge}{2026-10-17}{0.1} {Programmes officiels de physique-chimie des CPGE} \RequirePackage{amsmath} % \leqslant, \geqslant : amssymb sous pdfLaTeX ; unicode-math les fournit déjà, et % amssymb chargé après lui redéfinirait ses symboles. \IfPackageLoadedF { unicode-math } { \RequirePackage{amssymb} } \RequirePackage{array} \RequirePackage{booktabs} \RequirePackage{longtable} \RequirePackage{tcolorbox} \tcbuselibrary{skins,breakable} % ------------------------------------------------------------------------ % Messages \msg_new:nnn { programmes-cpge } { cle-inconnue } { La~clé~« #1 »~ne~désigne~rien~dans~les~programmes~chargés. } \msg_new:nnn { programmes-cpge } { filiere-inconnue } { Aucun~fichier~programmes-cpge-#1.tex~:~la~filière~« #1 »~est~inconnue. } \msg_new:nnn { programmes-cpge } { parent-inconnu } { « #1 »~déclare~pour~parent~« #2 »,~qui~n'est~pas~déclaré~avant~lui. } \msg_new:nnn { programmes-cpge } { option-inconnue } { Option~« #1 »~inconnue~pour~\iow_char:N\\#2. } % ------------------------------------------------------------------------ % Stockage : une liste de propriétés par clé, la suite de ses enfants \seq_new:N \g_cpge_filieres_seq \tl_new:N \l__cpge_filiere_tl \tl_new:N \l__cpge_courante_tl \tl_new:N \l__cpge_tmp_tl \seq_new:N \l__cpge_lignes_seq \int_new:N \l__cpge_rang_int \cs_new_protected:Npn \__cpge_creer:nn #1#2 { \prop_gclear_new:c { g__cpge_ #1 _prop } \seq_gclear_new:c { g__cpge_ #1 _enfants_seq } \prop_gput:cnn { g__cpge_ #1 _prop } { genre } {#2} \prop_gput:cnn { g__cpge_ #1 _prop } { cle } {#1} } \prg_new_conditional:Npnn \cpge_si_existe:n #1 { p , T , F , TF } { \prop_if_exist:cTF { g__cpge_ #1 _prop } { \prg_return_true: } { \prg_return_false: } } \cs_new:Npn \cpge_champ:nn #1#2 { \prop_if_exist:cTF { g__cpge_ #1 _prop } { \prop_item:cn { g__cpge_ #1 _prop } {#2} } { \msg_expandable_error:nnn { programmes-cpge } { cle-inconnue } {#1} } } \cs_generate_variant:Nn \cpge_champ:nn { V , e } \prg_new_conditional:Npnn \cpge_si_vide:nn #1#2 { p , T , F , TF } { \tl_if_blank:eTF { \cpge_champ:nn {#1} {#2} } { \prg_return_true: } { \prg_return_false: } } % Le format des fichiers de données --------------------------------------- \NewDocumentCommand \cpgefiliere { m +m } { \__cpge_creer:nn {#1} { filiere } \prop_gset_from_keyval:cn { g__cpge_ #1 _prop } {#2} \prop_gput:cnn { g__cpge_ #1 _prop } { genre } { filiere } \prop_gput:cnn { g__cpge_ #1 _prop } { filiere } {#1} \seq_if_in:NnF \g_cpge_filieres_seq {#1} { \seq_gput_right:Nn \g_cpge_filieres_seq {#1} } \tl_set:Nn \l__cpge_filiere_tl {#1} } \NewDocumentCommand \cpgerubrique { m +m } { \__cpge_noeud:nnn {#1} { rubrique } {#2} } \NewDocumentCommand \cpgeligne { m +m } { \__cpge_noeud:nnn {#1} { ligne } {#2} } \cs_new_protected:Npn \__cpge_noeud:nnn #1#2#3 { \__cpge_creer:nn {#1} {#2} \prop_gset_from_keyval:cn { g__cpge_ #1 _prop } {#3} \prop_gput:cnn { g__cpge_ #1 _prop } { genre } {#2} \prop_gput:cnV { g__cpge_ #1 _prop } { filiere } \l__cpge_filiere_tl \tl_set:Ne \l__cpge_tmp_tl { \prop_item:cn { g__cpge_ #1 _prop } { parent } } \seq_if_exist:cTF { g__cpge_ \l__cpge_tmp_tl _enfants_seq } { \seq_gput_right:cn { g__cpge_ \l__cpge_tmp_tl _enfants_seq } {#1} } { \msg_error:nnnV { programmes-cpge } { parent-inconnu } {#1} \l__cpge_tmp_tl } } \cs_generate_variant:Nn \msg_error:nnnn { nnnV } % Charger une filière ------------------------------------------------------ \cs_new_protected:Npn \cpge_charger:n #1 { \clist_map_inline:nn {#1} { \seq_if_in:NnF \g_cpge_filieres_seq {##1} { \file_if_exist:nTF { programmes-cpge- \str_lowercase:n {##1} .tex } { \group_begin: \ExplSyntaxOff \file_input:n { programmes-cpge- \str_lowercase:n {##1} .tex } \group_end: } { \msg_error:nnn { programmes-cpge } { filiere-inconnue } {##1} } } } } \NewDocumentCommand \cpgecharger { m } { \cpge_charger:n {#1} } % ------------------------------------------------------------------------ % Lire \NewExpandableDocumentCommand \cpgechamp { m m } { \cpge_champ:nn {#1} {#2} } \NewExpandableDocumentCommand \cpgetitre { m } { \cpge_champ:nn {#1} { titre } } \NewExpandableDocumentCommand \cpgenumero { m } { \cpge_champ:nn {#1} { numero } } \NewExpandableDocumentCommand \cpgegenre { m } { \cpge_champ:nn {#1} { genre } } \NewExpandableDocumentCommand \cpgefiliereDe { m } { \cpge_champ:nn {#1} { filiere } } \NewExpandableDocumentCommand \cpgeparent { m } { \cpge_champ:nn {#1} { parent } } \NewExpandableDocumentCommand \cpgesiexiste { m +m +m } { \cpge_si_existe:nTF {#1} {#2} {#3} } \NewExpandableDocumentCommand \cpgesivide { m m +m +m } { \cpge_si_vide:nnTF {#1} {#2} {#3} {#4} } \NewExpandableDocumentCommand \cpgesiligne { m +m +m } { \str_if_eq:eeTF { \cpge_champ:nn {#1} { genre } } { ligne } {#2} {#3} } % \cpgeitem{…}\cpgeitem{…} : deux lexèmes par item, les espaces ne comptent pas \NewExpandableDocumentCommand \cpgenombre { m m } { \int_eval:n { \tl_count:e { \cpge_champ:nn {#1} {#2} } / 2 } } % L'ancêtre d'une clé au niveau voulu (1 : le thème) ; la clé elle-même si elle % est à ce niveau, rien si elle n'en a pas. \cs_new:Npn \cpge_ancetre:nn #1#2 { \str_case_e:nnF { \cpge_champ:nn {#1} { genre } } { { filiere } { } { ligne } { \cpge_ancetre:en { \cpge_champ:nn {#1} { parent } } {#2} } } { \int_compare:nNnTF { \cpge_champ:nn {#1} { niveau } } > {#2} { \cpge_ancetre:en { \cpge_champ:nn {#1} { parent } } {#2} } { \int_compare:nNnT { \cpge_champ:nn {#1} { niveau } } = {#2} {#1} } } } \cs_generate_variant:Nn \cpge_ancetre:nn { e } \NewExpandableDocumentCommand \cpgeancetre { m m } { \cpge_ancetre:nn {#1} {#2} } \NewExpandableDocumentCommand \cpgenombreenfants { m } { \seq_count:c { g__cpge_ #1 _enfants_seq } } \NewExpandableDocumentCommand \cpgeintitule { m } { \cpge_champ:nn {#1} { numero } \cpge_si_vide:nnF {#1} { titre } { \c_space_tl \cpge_champ:nn {#1} { titre } } } % ------------------------------------------------------------------------ % Parcourir \cs_new_protected:Npn \__cpge_lignes:n #1 { \str_if_eq:eeTF { \cpge_champ:nn {#1} { genre } } { ligne } { \seq_put_right:Nn \l__cpge_lignes_seq {#1} } { \seq_map_inline:cn { g__cpge_ #1 _enfants_seq } { \__cpge_lignes:n {##1} } } } \cs_new_protected:Npn \cpge_lignes:Nn #1#2 { \seq_clear:N \l__cpge_lignes_seq \clist_map_inline:nn {#2} { \cpge_si_existe:nTF {##1} { \__cpge_lignes:n {##1} } { \msg_error:nnn { programmes-cpge } { cle-inconnue } {##1} } } \seq_set_eq:NN #1 \l__cpge_lignes_seq } \NewDocumentCommand \cpgepourchaque { m +m } { \cpge_si_existe:nTF {#1} { \seq_map_inline:cn { g__cpge_ #1 _enfants_seq } {#2} } { \msg_error:nnn { programmes-cpge } { cle-inconnue } {#1} } } \NewDocumentCommand \cpgepourchaqueligne { m +m } { \group_begin: \seq_clear_new:c { l__cpge_parcours_ \int_use:N \l__cpge_rang_int _seq } \cpge_lignes:cn { l__cpge_parcours_ \int_use:N \l__cpge_rang_int _seq } {#1} \int_incr:N \l__cpge_rang_int \seq_map_inline:cn { l__cpge_parcours_ \int_eval:n { \l__cpge_rang_int - 1 } _seq } {#2} \group_end: } \cs_generate_variant:Nn \cpge_lignes:Nn { c } % Les rangées d'un tableau fait main : le modèle reçoit la clé en #1, une fois par % ligne du programme. Les rangées sont assemblées d'abord, puis rendues d'un bloc, % si bien que les & et \\ du modèle arrivent dans l'alignement comme s'ils étaient % écrits à la main. \tl_new:N \l__cpge_rangees_tl \cs_new_protected:Npn \cpge_rangees:nn #1#2 { \cs_set:Npn \__cpge_modele:n ##1 {#2} \cpge_lignes:Nn \l__cpge_lignes_seq {#1} \tl_clear:N \l__cpge_rangees_tl \seq_map_inline:Nn \l__cpge_lignes_seq { \tl_put_right:No \l__cpge_rangees_tl { \__cpge_modele:n {##1} } } \tl_use:N \l__cpge_rangees_tl } \NewDocumentCommand \cpgenombrelignes { m } { \cpge_lignes:Nn \l__cpge_lignes_seq {#1} \seq_count:N \l__cpge_lignes_seq } \NewDocumentCommand \cpgerangees { m +m } { \cpge_rangees:nn {#1} {#2} } % ------------------------------------------------------------------------ % Les macros des données, toutes redéfinissables \newcommand \cpgeformatcitation [2] { \textbf{#1}~«~#2~» } \NewDocumentCommand \cpgecite { m } { \cpgeformatcitation { \cpgenumero {#1} } { \cpgetitre {#1} } } \NewDocumentCommand \cpgecettepartie { } { \tl_if_empty:NTF \l__cpge_courante_tl { cette~partie } { \exp_args:NV \cpgecite \l__cpge_courante_tl } } \newcommand \cpgenotion [1] { \textbf{#1} } \newcommand \cpgerenvoi [1] { } \newcommand \cpgevec [1] { \overrightarrow{#1} } \newcommand \cpgegrad { \cpgevec{\operatorname{grad}} } \newcommand \cpgerot { \cpgevec{\operatorname{rot}} } \newcommand \cpgediv { \operatorname{div} } \newcommand \cpgedd { \mathop{}\!\mathrm{d} } \newcommand \cpgeii { \mathrm{i} } \newcommand \cpgejj { \mathrm{j} } \newcommand \cpgeRe { \operatorname{Re} } % Chaque item d'un champ passe par \cpgeitem, dont la forme dépend du contexte. % Les paragraphes se séparent, sans \par après le dernier : une cellule p{…} % de tableau ajouterait sinon une ligne vide sous son contenu. \bool_new:N \l__cpge_premier_bool \bool_set_true:N \l__cpge_premier_bool \cs_new_protected:Npn \__cpge_forme_paragraphes:n #1 { \bool_if:NF \l__cpge_premier_bool { \par } \bool_set_false:N \l__cpge_premier_bool #1 } \cs_new_protected:Npn \__cpge_forme_liste:n #1 { \item #1 } \tl_new:N \l__cpge_separateur_tl \tl_set:Nn \l__cpge_separateur_tl { ~ } \cs_new_protected:Npn \__cpge_forme_ligne:n #1 { \bool_if:NF \l__cpge_premier_bool { \tl_use:N \l__cpge_separateur_tl } \bool_set_false:N \l__cpge_premier_bool #1 } \cs_new_protected:Npn \__cpge_format:n #1 {#1} \cs_new_protected:Npn \cpgeitem #1 { \__cpge_forme:n { \__cpge_format:n {#1} } } \cs_new_eq:NN \__cpge_forme:n \__cpge_forme_paragraphes:n % ------------------------------------------------------------------------ % Composer un champ \tl_new:N \l__cpge_forme_tl \cs_generate_variant:Nn \msg_error:nnnn { nnVn } \keys_define:nn { programmes-cpge / afficher } { forme .choices:nn = { paragraphes , liste , enumeration , ligne } { \tl_set_eq:NN \l__cpge_forme_tl \l_keys_choice_tl } , forme .initial:n = paragraphes , separateur .tl_set:N = \l__cpge_separateur_tl , format .code:n = { \cs_set_protected:Npn \__cpge_format:n ##1 { #1 {##1} } } , unknown .code:n = { \msg_error:nnVn { programmes-cpge } { option-inconnue } \l_keys_key_str { cpgeafficher } } , } \cs_new_protected:Npn \cpge_afficher:nnn #1#2#3 { \group_begin: \keys_set:nn { programmes-cpge / afficher } {#1} \tl_set:Ne \l__cpge_courante_tl {#2} \tl_set:Ne \l__cpge_tmp_tl { \cpge_champ:nn {#2} {#3} } \tl_if_blank:VF \l__cpge_tmp_tl { \str_case:Vn \l__cpge_forme_tl { { paragraphes } { \cs_set_eq:NN \__cpge_forme:n \__cpge_forme_paragraphes:n \bool_set_true:N \l__cpge_premier_bool \tl_use:N \l__cpge_tmp_tl } { liste } { \cs_set_eq:NN \__cpge_forme:n \__cpge_forme_liste:n \begin{itemize} \tl_use:N \l__cpge_tmp_tl \end{itemize} } { enumeration } { \cs_set_eq:NN \__cpge_forme:n \__cpge_forme_liste:n \begin{enumerate} \tl_use:N \l__cpge_tmp_tl \end{enumerate} } { ligne } { \cs_set_eq:NN \__cpge_forme:n \__cpge_forme_ligne:n \bool_set_true:N \l__cpge_premier_bool \tl_use:N \l__cpge_tmp_tl } } } \group_end: } \NewDocumentCommand \cpgeafficher { O{} m m } { \cpge_afficher:nnn {#1} {#2} {#3} } \NewDocumentCommand \cpgechapeau { O{} m } { \cpge_afficher:nnn {#1} {#2} { chapeau } } % Les trois familles de capacités, en paragraphes ou en liste \newcommand \cpgeformatexperimentale [1] { \textbf{#1} } \newcommand \cpgeformatnumerique [1] { \textit{Capacité~numérique~:}~#1 } \keys_define:nn { programmes-cpge / capacites } { experimentales .bool_set:N = \l__cpge_exp_bool , experimentales .initial:n = true , numeriques .bool_set:N = \l__cpge_num_bool , numeriques .initial:n = true , exigibles .bool_set:N = \l__cpge_sf_bool , exigibles .initial:n = true , forme .tl_set:N = \l__cpge_forme_cap_tl , forme .initial:n = paragraphes , } \cs_new_protected:Npn \cpge_capacites:nn #1#2 { \group_begin: \keys_set:nn { programmes-cpge / capacites } {#1} \tl_if_eq:NnTF \l__cpge_forme_cap_tl { liste } { \cpge_si_vide:nnTF {#2} { capacites } { \cpge_si_vide:nnTF {#2} { experimentales } { \cpge_si_vide:nnF {#2} { numeriques } { \__cpge_liste_capacites:n {#2} } } { \__cpge_liste_capacites:n {#2} } } { \__cpge_liste_capacites:n {#2} } } { \__cpge_trois_familles:nn { paragraphes } {#2} } \group_end: } \cs_new_protected:Npn \__cpge_liste_capacites:n #1 { \begin{itemize} \__cpge_trois_familles:nn { liste } {#1} \end{itemize} } \cs_new_protected:Npn \__cpge_trois_familles:nn #1#2 { \cs_set_eq:Nc \__cpge_forme:n { __cpge_forme_ #1 :n } \bool_set_true:N \l__cpge_premier_bool \tl_set:Ne \l__cpge_courante_tl {#2} \bool_if:NT \l__cpge_sf_bool { \cpge_champ:nn {#2} { capacites } } \bool_if:NT \l__cpge_exp_bool { \cs_set_protected:Npn \__cpge_format:n ##1 { \cpgeformatexperimentale {##1} } \cpge_champ:nn {#2} { experimentales } } \bool_if:NT \l__cpge_num_bool { \cs_set_protected:Npn \__cpge_format:n ##1 { \cpgeformatnumerique {##1} } \cpge_champ:nn {#2} { numeriques } } } \NewDocumentCommand \cpgecapacites { O{} m } { \cpge_capacites:nn {#1} {#2} } % ------------------------------------------------------------------------ % Tableaux \keys_define:nn { programmes-cpge / tableau } { largeur .fp_set:N = \l__cpge_largeur_fp , largeur .initial:n = 0.38 , entete .bool_set:N = \l__cpge_entete_bool , entete .initial:n = true , titres .bool_set:N = \l__cpge_titres_bool , titres .initial:n = true , numeros .bool_set:N = \l__cpge_numeros_bool , numeros .initial:n = false , long .bool_set:N = \l__cpge_long_bool , long .initial:n = true , titre~contenus .tl_set:N = \l__cpge_titre_contenus_tl , titre~contenus .initial:n = Notions~et~contenus , titre~capacites .tl_set:N = \l__cpge_titre_capacites_tl , titre~capacites .initial:n = Capacités~exigibles , experimentales .code:n = \tl_put_right:Nn \l__cpge_opt_cap_tl { experimentales = #1 , } , numeriques .code:n = \tl_put_right:Nn \l__cpge_opt_cap_tl { numeriques = #1 , } , exigibles .code:n = \tl_put_right:Nn \l__cpge_opt_cap_tl { exigibles = #1 , } , unknown .code:n = { \msg_error:nnVn { programmes-cpge } { option-inconnue } \l_keys_key_str { cpgetableau } } , } \tl_new:N \l__cpge_opt_cap_tl \tl_new:N \l__cpge_corps_tl \dim_new:N \l__cpge_gauche_dim \dim_new:N \l__cpge_droite_dim \newcommand \cpgeformattitretableau [1] { \textbf{\cpgeintitule{#1}} } \newcommand \cpgeformatnumeroligne [1] { {\footnotesize\textsf{#1}}\par } \cs_new_protected:Npn \cpge_cellule_contenu:n #1 { \bool_if:NT \l__cpge_numeros_bool { \cpgeformatnumeroligne { \cpge_champ:nn {#1} { cle } } } \cpge_afficher:nnn { } {#1} { contenu } } \cs_new_protected:Npn \cpge_cellule_capacites:n #1 { \cpge_capacites:nn { \tl_use:N \l__cpge_opt_cap_tl } {#1} } \cs_generate_variant:Nn \cpge_capacites:nn { e } \cs_set_protected:Npn \cpge_cellule_capacites:n #1 { \cpge_capacites:en { \tl_use:N \l__cpge_opt_cap_tl } {#1} } \cs_new_protected:Npn \__cpge_rangees:n #1 { \str_if_eq:eeTF { \cpge_champ:nn {#1} { genre } } { ligne } { \tl_put_right:Nn \l__cpge_corps_tl { \cpge_cellule_contenu:n {#1} & \cpge_cellule_capacites:n {#1} \tabularnewline \midrule } } { \bool_if:NT \l__cpge_titres_bool { \str_if_eq:eeF { \cpge_champ:nn {#1} { genre } } { filiere } { \tl_put_right:Nn \l__cpge_corps_tl { \multicolumn{2}{@{}p{\linewidth}@{}}{\cpgeformattitretableau {#1}} \tabularnewline \midrule } } } \seq_map_inline:cn { g__cpge_ #1 _enfants_seq } { \__cpge_rangees:n {##1} } } } \cs_new_protected:Npn \cpge_tableau:nn #1#2 { \group_begin: \tl_clear:N \l__cpge_opt_cap_tl \keys_set:nn { programmes-cpge / tableau } {#1} \tl_clear:N \l__cpge_corps_tl \clist_map_inline:nn {#2} { \cpge_si_existe:nTF {##1} { \__cpge_rangees:n {##1} } { \msg_error:nnn { programmes-cpge } { cle-inconnue } {##1} } } \dim_set:Nn \l__cpge_gauche_dim { \fp_to_dim:n { \l__cpge_largeur_fp * \linewidth } - 2\tabcolsep } \dim_set:Nn \l__cpge_droite_dim { \fp_to_dim:n { (1 - \l__cpge_largeur_fp) * \linewidth } - 2\tabcolsep } \bool_if:NTF \l__cpge_long_bool { \__cpge_tableau_long: } { \__cpge_tableau_court: } \group_end: } \cs_new_protected:Npn \__cpge_preambule: { @{} >{\raggedright\arraybackslash}p{\l__cpge_gauche_dim} @{\hspace{2\tabcolsep}} >{\raggedright\arraybackslash}p{\l__cpge_droite_dim} @{} } \cs_new:Npn \__cpge_entete: { \bool_if:NT \l__cpge_entete_bool { \toprule \textit{\l__cpge_titre_contenus_tl} & \textit{\l__cpge_titre_capacites_tl} \tabularnewline \midrule } } \cs_new_protected:Npx \__cpge_tableau_long: { \exp_not:N \begin{longtable} { \exp_not:o { \__cpge_preambule: } } \exp_not:N \__cpge_entete: \exp_not:N \endhead \exp_not:N \tl_use:N \exp_not:N \l__cpge_corps_tl \exp_not:N \end{longtable} } \cs_new_protected:Npx \__cpge_tableau_court: { \exp_not:N \par \exp_not:N \noindent \exp_not:N \begin{tabular} { \exp_not:o { \__cpge_preambule: } } \exp_not:N \__cpge_entete: \exp_not:N \tl_use:N \exp_not:N \l__cpge_corps_tl \exp_not:N \end{tabular} \exp_not:N \par } \NewDocumentCommand \cpgetableau { O{} m } { \cpge_tableau:nn {#1} {#2} } % ------------------------------------------------------------------------ % Boîtes \tcbset { cpge~boite/.style = { enhanced , breakable , colback = white , colframe = black!60 , colbacktitle = black!8 , coltitle = black , fonttitle = \bfseries , boxrule = 0.4pt , arc = 1mm , left = 2mm , right = 2mm , } , } \keys_define:nn { programmes-cpge / boite } { lignes .bool_set:N = \l__cpge_boite_lignes_bool , lignes .initial:n = false , chapeau .bool_set:N = \l__cpge_boite_chapeau_bool , chapeau .initial:n = true , titre .tl_set:N = \l__cpge_boite_titre_tl , tcolorbox .tl_set:N = \l__cpge_boite_options_tl , experimentales .code:n = \tl_put_right:Nn \l__cpge_opt_cap_tl { experimentales = #1 , } , numeriques .code:n = \tl_put_right:Nn \l__cpge_opt_cap_tl { numeriques = #1 , } , exigibles .code:n = \tl_put_right:Nn \l__cpge_opt_cap_tl { exigibles = #1 , } , unknown .code:n = { \msg_error:nnVn { programmes-cpge } { option-inconnue } \l_keys_key_str { cpgeboite } } , } \newcommand \cpgeformattitreboite [1] { \cpgesiligne {#1} { \cpgefiliereDe{#1}~\cpgenumero{#1} } { \cpgeintitule{#1} } } \cs_new_protected:Npn \cpge_boite:nn #1#2 { \group_begin: \tl_clear:N \l__cpge_opt_cap_tl \tl_clear:N \l__cpge_boite_titre_tl \tl_clear:N \l__cpge_boite_options_tl \keys_set:nn { programmes-cpge / boite } {#1} \tl_if_empty:NT \l__cpge_boite_titre_tl { \tl_set:Nn \l__cpge_boite_titre_tl { \cpgeformattitreboite {#2} } } \tl_set:Ne \l__cpge_tmp_tl { cpge~boite , title = { \exp_not:V \l__cpge_boite_titre_tl } , \exp_not:V \l__cpge_boite_options_tl } \use:e { \exp_not:N \tcolorbox [ \exp_not:V \l__cpge_tmp_tl ] } \str_if_eq:eeTF { \cpge_champ:nn {#2} { genre } } { ligne } { \__cpge_boite_ligne:n {#2} } { \__cpge_boite_rubrique:n {#2} } \endtcolorbox \group_end: } \cs_new_protected:Npn \__cpge_boite_ligne:n #1 { \cpge_afficher:nnn { } {#1} { contenu } \tcblower \cpge_capacites:en { forme = liste , \tl_use:N \l__cpge_opt_cap_tl } {#1} } \cs_new_protected:Npn \__cpge_boite_rubrique:n #1 { \bool_if:NT \l__cpge_boite_chapeau_bool { \cpge_afficher:nnn { } {#1} { chapeau } } \bool_if:NT \l__cpge_boite_lignes_bool { \cpge_lignes:Nn \l__cpge_lignes_seq {#1} \seq_map_inline:Nn \l__cpge_lignes_seq { \par\medskip \cpge_afficher:nnn { forme = ligne } {##1} { contenu } \cpge_capacites:en { forme = liste , \tl_use:N \l__cpge_opt_cap_tl } {##1} } } } \NewDocumentCommand \cpgeboite { O{} m } { \cpge_boite:nn {#1} {#2} } % ------------------------------------------------------------------------ % Options du paquet : les filières à charger \clist_new:N \g__cpge_options_clist \keys_define:nn { programmes-cpge / paquet } { toutes .code:n = \clist_gput_right:Nn \g__cpge_options_clist { MPSI , PCSI , MP , PC , PSI } , unknown .code:n = \clist_gput_right:NV \g__cpge_options_clist \l_keys_key_str , } \ProcessKeyOptions [ programmes-cpge / paquet ] \clist_if_empty:NT \g__cpge_options_clist { \clist_gset:Nn \g__cpge_options_clist { MPSI , PCSI , MP , PC , PSI } } \cs_generate_variant:Nn \cpge_charger:n { V } \cpge_charger:V \g__cpge_options_clist