% \iffalse meta-comment % % Copyright (C) 2014 by Henry Menke % Copyright (C) 2019 by Benjamin Berg % % 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 % http://www.latex-project.org/lppl.txt % % This work has the LPPL maintenance status `maintained'. % % The Current Maintainer of this work is Benjamin Berg. % % \fi % % \iffalse %<*driver> \ProvidesFile{sdapslayout.dtx} % %\NeedsTeXFormat{LaTeX2e}[1999/12/01] %\ProvidesPackage{sdapslayout} %<*package> [2015/04/10 v0.1 Initial version of SDAPS layout package] % % %<*driver> \documentclass{l3doc} \usepackage{sdapslayout}[2015/04/10] \EnableCrossrefs %\CodelineIndex \RecordChanges \begin{document} \DocInput{sdapslayout.dtx} \end{document} % % \fi % % \CheckSum{0} % % \CharacterTable % {Upper-case \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z % Lower-case \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z % Digits \0\1\2\3\4\5\6\7\8\9 % Exclamation \! Double quote \" Hash (number) \# % Dollar \$ Percent \% Ampersand \& % Acute accent \' Left paren \( Right paren \) % Asterisk \* Plus \+ Comma \, % Minus \- Point \. Solidus \/ % Colon \: Semicolon \; Less than \< % Equals \= Greater than \> Question mark \? % Commercial at \@ Left bracket \[ Backslash \\ % Right bracket \] Circumflex \^ Underscore \_ % Grave accent \` Left brace \{ Vertical bar \| % Right brace \} Tilde \~} % % % \changes{v0.1}{2015/01/14}{Initial version} % % \GetFileInfo{sdapslayout.dtx} % % \DoNotIndex{\newcommand,\newenvironment} % % % \title{The \textsf{sdapslayout} package\thanks{This document % corresponds to \textsf{sdapslayout}~\fileversion, dated \filedate.}} % \author{Benjamin Berg \\ \texttt{benjamin@sipsolutions.net}} % % \maketitle % % \section{Documentation} % % Please refer to \url{https://sdaps.org/class-doc} for documentation. % % \section{Implementation} % % This package uses the \LaTeX3 language internally, so we need to enable it. % \begin{macrocode} % We need at least 2011-08-23 for \keys_set_known:nnN \RequirePackage{expl3}[2011/08/23] %\RequirePackage{xparse} \ExplSyntaxOn % \end{macrocode} % % And we need a number of other packages. % \begin{macrocode} \ExplSyntaxOff \RequirePackage{sdapsbase} \RequirePackage{sdapsarray} \RequirePackage{xparse} \ExplSyntaxOn % \end{macrocode} % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % \subsection{Choice Question Layout} % % \subsubsection{Choice Question Matrix Layout} % % The following macros provide the funcitonality to layout choice questions in % a matrix like fashion. % % \begin{macrocode} \tl_new:N \l_sdaps_choicearray_qobject_type_tl \bool_new:N \l_sdaps_choicearray_horizontal_bool \tl_new:N \l_sdaps_choicearray_layouter_tl \tl_new:N \l_sdaps_choicearray_align_tl \tl_new:N \l_sdaps_choicearray_extra_tl \tl_new:N \l_sdaps_choicearray_type_tl \tl_new:N \l_sdaps_choice_var_tl \tl_new:N \l_sdaps_choice_val_tl \tl_new:N \l_sdaps_choice_text_tl \tl_new:N \l_sdaps_question_var_tl \tl_new:N \l_sdaps_question_text_tl \clist_new:N \l_sdaps_question_range_clist \int_new:N \g_sdaps_choices_box_int \seq_new:N \g_sdaps_choices_filter_seq \seq_new:N \g_sdaps_choices_cell_seq \seq_new:N \g_sdaps_choices_text_seq \keys_define:nn { sdaps / choicearray } { horizontal .bool_set:N = \l_sdaps_choicearray_horizontal_bool, horizontal .default:n = true, horizontal .initial:n = true, vertical .bool_set_inverse:N = \l_sdaps_choicearray_horizontal_bool, vertical .default:n = true, layouter .tl_set:N = \l_sdaps_choicearray_layouter_tl, layouter .initial:n = default, align .tl_set:N = \l_sdaps_choicearray_align_tl, align .initial:n = { }, type .choices:nn = { multichoice, singlechoice } { \tl_set:Nx \l_sdaps_choicearray_type_tl { \l_keys_choice_tl } }, type .initial:n = { multichoice }, singlechoice .meta:n = { type=singlechoice }, multichoice .meta:n = { type=multichoice }, noalign .meta:n = { align= }, } \keys_define:nn { sdaps / choicearray / choice } { var .tl_set:N = \l_sdaps_choice_var_tl, val .tl_set:N = \l_sdaps_choice_val_tl, text .tl_set:N = \l_sdaps_choice_text_tl, } \keys_define:nn { sdaps / choicearray / question } { var .tl_set:N = \l_sdaps_question_var_tl, text .tl_set:N = \l_sdaps_question_text_tl, range .clist_set:N = \l_sdaps_question_range_clist, range .initial:n = {...} } \cs_new_protected_nopar:Npn \_sdaps_choicearray_preprocess:n #1 { \keys_set_known:nnN { sdaps / choicearray } { #1 } \l_sdaps_choicearray_extra_tl \sdaps_checkbox_set_type:V \l_sdaps_choicearray_type_tl \tl_if_eq:VnTF \l_sdaps_choicearray_type_tl { multichoice } { \tl_set:Nn \l_sdaps_choicearray_qobject_type_tl { Choice } } { \tl_set:Nn \l_sdaps_choicearray_qobject_type_tl { Option } } } \cs_generate_variant:Nn \_sdaps_choicearray_preprocess:n { V } \cs_new_protected_nopar:Npn \_sdaps_choicearray_process_choice_insert_tail_after:w { \bgroup \group_insert_after:N \_sdaps_choicearray_process_choice_tail: \sdaps_array_nested_alignenv: \tex_let:D\next= } \cs_new_protected_nopar:Nn \_sdaps_choicearray_process_choice_tail: { \ignorespaces } \cs_new_nopar:Nn \_sdaps_choicearray_grab_choice:n { \seq_gput_right:Nn \g_sdaps_choices_text_seq { #1 } \group_begin: \sdaps_array_nested_alignenv: #1 \group_end: \_sdaps_choicearray_process_choice_tail: } \cs_new_nopar:Npn \_sdaps_choicearray_process_choice:nw #1 { % This modifies grouping so it has to be at the start \sdaps_array_alignment: \leavevmode \tl_clear:N \l_sdaps_choice_var_tl \tl_clear:N \l_sdaps_choice_val_tl \tl_clear:N \l_sdaps_choice_text_tl \keys_set:nn { sdaps / choicearray / choice } { #1 } \int_gincr:N \g_sdaps_choices_box_int \tl_if_empty:NT \l_sdaps_choice_var_tl { % Prefix with _ to force prefixing with generated variable \tl_set:Nx \l_sdaps_choice_var_tl { \int_use:N \g_sdaps_choices_box_int } } \tl_if_empty:NT \l_sdaps_choice_val_tl { \tl_set:Nx \l_sdaps_choice_val_tl { \int_use:N \g_sdaps_choices_box_int } } \tl_if_eq:VnTF \l_sdaps_choicearray_type_tl { multichoice } { \seq_gput_right:NV \g_sdaps_choices_filter_seq \l_sdaps_choice_var_tl } { \seq_gput_right:NV \g_sdaps_choices_filter_seq \l_sdaps_choice_val_tl } \seq_gput_right:Nx \g_sdaps_choices_cell_seq { \exp_not:n { \sdaps_checkbox:nn } { _ \l_sdaps_choice_var_tl } { \l_sdaps_choice_val_tl } } \tl_if_empty:NTF \l_sdaps_choice_text_tl { % We need to leave a command in the stream that grabs the next parameter % and outputs it immediately \cs_set_eq:NN \l_tmpa_token \_sdaps_choicearray_grab_choice:n } { % Nothing else to do \seq_gput_right:NV \g_sdaps_choices_text_seq { \l_sdaps_choice_text_tl } \cs_set_eq:NN \l_tmpa_token \_sdaps_choicearray_process_choice_insert_tail_after:w } \l_tmpa_token } \cs_generate_variant:Nn \_sdaps_choicearray_process_choice:nw { Vw } \cs_new_protected_nopar:Nn \_sdaps_choicearray_process_question_grab:n { \tl_set:Nn \l_sdaps_question_text_tl { #1 } \_sdaps_choicearray_process_question_head: \group_begin: \sdaps_array_nested_alignenv: #1 \group_end: \_sdaps_choicearray_process_question_tail: } \cs_new_protected_nopar:Npn \_sdaps_choicearray_process_question_inline:w { \_sdaps_choicearray_process_question_head: \bgroup \group_insert_after:N \_sdaps_choicearray_process_question_tail: \sdaps_array_nested_alignenv: \tex_let:D\next= } \cs_new_protected_nopar:Nn \_sdaps_choicearray_process_question_head: { \sdaps_qobject_begin:nnV { choicearray_question } \l_sdaps_choicearray_qobject_type_tl \l_sdaps_question_text_tl \tl_if_empty:NF \l_sdaps_question_var_tl { \sdaps_qobject_append_var:V \l_sdaps_question_var_tl } } \cs_new_protected_nopar:Nn \_sdaps_choicearray_process_question_tail: { \seq_gclear:N \g_tmpa_seq % l_tmpa_bool is whether we are in a run (i.e. ...) \bool_set_false:N \l_tmpa_bool \clist_gset_eq:NN \g_tmpa_clist \l_sdaps_question_range_clist \clist_gpop:NN \g_tmpa_clist \l_tmpa_tl \seq_map_inline:Nn \g_sdaps_choices_filter_seq { \tl_if_eq:VnT \l_tmpa_tl { ... } { \bool_set_true:N \l_tmpa_bool \clist_gpop:NN \g_tmpa_clist \l_tmpa_tl } \tl_if_eq:VnTF \l_tmpa_tl { ##1 } { \seq_gput_right:Nn \g_tmpa_seq { \c_true_bool } \bool_set_false:N \l_tmpa_bool \clist_gpop:NN \g_tmpa_clist \l_tmpa_tl } { % Append if we are handling a run of items and the current item is not % the last one. \bool_if:NTF \l_tmpa_bool { \seq_gput_right:Nn \g_tmpa_seq { \c_true_bool } } { % Otherwise, ignore this item \seq_gput_right:Nn \g_tmpa_seq { \c_false_bool } } } } \seq_gset_eq:NN \g_tmpb_seq \g_tmpa_seq \seq_map_inline:Nn \g_sdaps_choices_text_seq { \seq_gpop_left:NN \g_tmpa_seq \l_tmpa_tl \tl_if_eq:VnT \l_tmpa_tl { \c_true_bool } { \sdaps_answer:f { ##1 } } } \seq_map_inline:Nn \g_sdaps_choices_cell_seq { \sdaps_array_alignment: \seq_gpop_left:NN \g_tmpb_seq \l_tmpa_tl \tl_if_eq:VnT \l_tmpa_tl { \c_true_bool } { ##1 } } \sdaps_qobject_end:n { choicearray_question } \ignorespaces } \cs_new_nopar:Npn \_sdaps_choicearray_process_question:nw #1 { \sdaps_array_newline: \leavevmode \keys_set:nn { sdaps / choicearray / question } { #1 } \tl_if_empty:NTF \l_sdaps_question_text_tl { % We need to leave a command in the stream that grabs the next parameter, % outputs it again, and finishes the question. \cs_set_eq:NN \l_tmpa_token \_sdaps_choicearray_process_question_grab:n } { % Insert the question around the next argument \cs_set_eq:NN \l_tmpa_token \_sdaps_choicearray_process_question_inline:w } \l_tmpa_token } \cs_generate_variant:Nn \_sdaps_choicearray_process_question:nw { Vw } % % \end{macrocode} % % % \subsection{Range Question Layout} % % \subsubsection{Range Question Matrix Layout} % % The following macros provide the functionality to layout range/option % questions in a matrix like fashion. % % \begin{macrocode} \tl_new:N \l_sdaps_rangearray_align_tl \tl_new:N \l_sdaps_rangearray_extra_tl \int_new:N \l_sdaps_rangearray_rangecount_int \bool_new:N \l_sdaps_rangearray_other_bool \tl_new:N \g_sdaps_question_var_tl \tl_new:N \g_sdaps_question_text_tl \tl_new:N \g_sdaps_question_lowertext_tl \tl_new:N \g_sdaps_question_uppertext_tl \tl_new:N \g_sdaps_question_othertext_tl \msg_new:nnn { sdapslayout } { option_not_supported } { The~#1~option~is~not~supported~by~this~environment. } \keys_define:nn { sdaps / rangearray } { count .int_set:N = \l_sdaps_rangearray_rangecount_int, count .initial:n = 5, align .tl_set:N = \l_sdaps_rangearray_align_tl, align .initial:n = { }, % Override and disallow flipping; it does not work currently flip .code:n = { \msg_error:nnn { sdapslayout } { option_not_supported } { flip } }, other .bool_set:N = \l_sdaps_rangearray_other_bool, other .default:n = true, other .initial:n = false, } \keys_define:nn { sdaps / rangearray / question } { var .tl_gset:N = \g_sdaps_question_var_tl, text .tl_gset:N = \g_sdaps_question_text_tl, upper .tl_gset:N = \g_sdaps_question_uppertext_tl, lower .tl_gset:N = \g_sdaps_question_lowertext_tl, other .tl_gset:N = \g_sdaps_question_othertext_tl, } \cs_new_protected_nopar:Npn \_sdaps_rangearray_preprocess:n #1 { \keys_set_known:nnN { sdaps / rangearray } { #1 } \l_sdaps_rangearray_extra_tl \sdaps_checkbox_set_type:n { singlechoice } } \cs_generate_variant:Nn \_sdaps_rangearray_preprocess:n { V } % Before/After the different parts \cs_new_protected_nopar:Nn \_sdaps_rangearray_process_question_before_question: { \sdaps_array_newline: % Note: This needs to be after sdaps_array_newline as the command may be % discarded otherwise (i.e. it does not make it into the output stream) % We also need to leave vmode here \leavevmode \sdaps_qobject_begin:nnV { rangearray_question } { Range } \g_sdaps_question_text_tl \tl_if_empty:NF \g_sdaps_question_var_tl { \sdaps_qobject_append_var:V \g_sdaps_question_var_tl } \ignorespaces } \cs_new_protected_nopar:Nn \_sdaps_rangearray_process_question_before_lower: { % right align \sdaps_array_alignment: \leavevmode \sdaps_range:nnV { lower } { 0 } \g_sdaps_question_lowertext_tl \sdaps_if_rtl:F { \hfill } \ignorespaces } \cs_new_protected_nopar:Nn \_sdaps_rangearray_process_question_before_upper: { \sdaps_array_alignment: \leavevmode \sdaps_range:nnV { upper } { \l_sdaps_rangearray_rangecount_int - 1 } \g_sdaps_question_uppertext_tl \sdaps_if_rtl:T { \hfill } \ignorespaces } \cs_new_protected_nopar:Nn \_sdaps_rangearray_process_question_before_other: { % Insert an extra empty column for further spacing \sdaps_array_alignment: \sdaps_array_alignment: \leavevmode \sdaps_answer:V \g_sdaps_question_othertext_tl \sdaps_if_rtl:TF { \hfill } { \sdaps_checkbox:nn { } { 0 } {} ~ {} } \ignorespaces } \cs_new_protected_nopar:Nn \_sdaps_rangearray_process_question_after_question: { % Insert an extra empty column for further spacing \sdaps_array_alignment: \tl_if_empty:NTF \g_sdaps_question_lowertext_tl { \cs_set_eq:NN \l_tmpa_token \_sdaps_rangearray_process_question_grab_lower:n } { \cs_set_eq:NN \l_tmpa_token \_sdaps_rangearray_process_question_inline_lower:w } \l_tmpa_token } \cs_new_protected_nopar:Nn \_sdaps_rangearray_process_question_after_lower: { % Insert the option checkbox column \sdaps_if_rtl:T { \hfill\kern 0pt } \sdaps_array_alignment: \leavevmode % Seems like right to left writing mode is not started without a paragraph \sdaps_if_rtl:T { \beginR } % Assume we have at least one checkbox \sdaps_checkbox:nn { } { 1 } \int_step_inline:nnnn { 2 } { 1 } { \l_sdaps_rangearray_rangecount_int } { \hspace{1em} \sdaps_checkbox:nn { } { ##1 } } \sdaps_if_rtl:T { \endR } \tl_if_empty:NTF \g_sdaps_question_uppertext_tl { \cs_set_eq:NN \l_tmpa_token \_sdaps_rangearray_process_question_grab_upper:n } { \cs_set_eq:NN \l_tmpa_token \_sdaps_rangearray_process_question_inline_upper:w } \l_tmpa_token } \cs_new_protected_nopar:Nn \_sdaps_rangearray_process_question_after_upper: { \sdaps_if_rtl:F { \hfill\kern 0pt } \bool_if:NTF \l_sdaps_rangearray_other_bool { \tl_if_empty:NTF \g_sdaps_question_othertext_tl { \cs_set_eq:NN \l_tmpa_token \_sdaps_rangearray_process_question_grab_other:n } { \cs_set_eq:NN \l_tmpa_token \_sdaps_rangearray_process_question_inline_other:w } } { \cs_set_eq:NN \l_tmpa_token \_sdaps_rangearray_process_question_finish: } \l_tmpa_token } \cs_new_protected_nopar:Nn \_sdaps_rangearray_process_question_after_other: { \sdaps_if_rtl:TF { {} ~ \sdaps_checkbox:nn { } { 0 } {} } { \hfill\kern 0pt } \ignorespaces } \cs_new_protected_nopar:Nn \_sdaps_rangearray_process_question_finish: { \sdaps_qobject_end:n { rangearray_question } \ignorespaces } % Processors for inline processing/grabbing the argument \cs_new_protected_nopar:Nn \_sdaps_rangearray_process_question_grab_question:n { \tl_gset:Nn \g_sdaps_question_text_tl { #1 } \_sdaps_rangearray_process_question_before_question: \group_begin: \sdaps_array_nested_alignenv: #1 \group_end: \_sdaps_rangearray_process_question_after_question: } \cs_new_protected_nopar:Nn \_sdaps_rangearray_process_question_grab_lower:n { \tl_gset:Nn \g_sdaps_question_lowertext_tl { #1 } \_sdaps_rangearray_process_question_before_lower: \group_begin: \sdaps_array_nested_alignenv: #1 \group_end: \_sdaps_rangearray_process_question_after_lower: } \cs_new_protected_nopar:Nn \_sdaps_rangearray_process_question_grab_upper:n { \tl_gset:Nn \g_sdaps_question_uppertext_tl { #1 } \_sdaps_rangearray_process_question_before_upper: \group_begin: \sdaps_array_nested_alignenv: #1 \group_end: \_sdaps_rangearray_process_question_after_upper: } \cs_new_protected_nopar:Nn \_sdaps_rangearray_process_question_grab_other:n { \tl_gset:Nn \g_sdaps_question_othertext_tl { #1 } % If the text is empty, assume that this particular question does not have % an alternative choice. Note that this column might not exist and this % macro will not even be called in that case. % If we skip the optional "other" option then we still need to insert the % alignment to create the column. \tl_if_empty:NTF \g_sdaps_question_othertext_tl { \sdaps_array_alignment: \leavevmode \ignorespaces } { \_sdaps_rangearray_process_question_before_other: \group_begin: \sdaps_array_nested_alignenv: #1 \group_end: \_sdaps_rangearray_process_question_after_other: } \_sdaps_rangearray_process_question_finish: } \cs_new_protected_nopar:Npn \_sdaps_rangearray_process_question_inline_question:w { \_sdaps_rangearray_process_question_before_question: \bgroup \group_insert_after:N \_sdaps_rangearray_process_question_after_question: \sdaps_array_nested_alignenv: \tex_let:D\next= } \cs_new_protected_nopar:Npn \_sdaps_rangearray_process_question_inline_lower:w { \_sdaps_rangearray_process_question_before_lower: \bgroup \group_insert_after:N \_sdaps_rangearray_process_question_after_lower: \tex_let:D\next= } \cs_new_protected_nopar:Npn \_sdaps_rangearray_process_question_inline_upper:w { \_sdaps_rangearray_process_question_before_upper: \bgroup \group_insert_after:N \_sdaps_rangearray_process_question_after_upper: \tex_let:D\next= } \cs_new_protected_nopar:Npn \_sdaps_rangearray_process_question_inline_other:w { \_sdaps_rangearray_process_question_before_other: % If we reach this macro then a text has been set for the other item. This % means we never need to ignore the "other" parameter at this point. \bgroup \group_insert_after:N \_sdaps_rangearray_process_question_after_other: \group_insert_after:N \_sdaps_rangearray_process_question_finish: \tex_let:D\next= } \cs_new_nopar:Npn \_sdaps_rangearray_process_question:nw #1 { % Is there a better way other than clearing these before parsing? \tl_gclear:N \g_sdaps_question_var_tl \tl_gclear:N \g_sdaps_question_text_tl \tl_gclear:N \g_sdaps_question_uppertext_tl \tl_gclear:N \g_sdaps_question_lowertext_tl \tl_gclear:N \g_sdaps_question_othertext_tl \keys_set:nn { sdaps / rangearray / question } { #1 } \tl_if_empty:NTF \g_sdaps_question_text_tl { % We need to leave a command in the stream that grabs the next parameter, % outputs it again, and finishes the question. \cs_set_eq:NN \l_tmpa_token \_sdaps_rangearray_process_question_grab_question:n } { % We need to generate the question after the next group stops \cs_set_eq:NN \l_tmpa_token \_sdaps_rangearray_process_question_inline_question:w } \l_tmpa_token } \cs_generate_variant:Nn \_sdaps_rangearray_process_question:nw { Vw } % % \end{macrocode} % % % % \subsection{Export user facing environments} % % \begin{macrocode} % \newenvironment { choicearray } [ 1 ] [] { \group_begin: \sdaps_context_get:nN { choicearray } \l_tmpa_tl \tl_if_eq:NNT \l_tmpa_tl \q_no_value { \tl_set:Nn \l_tmpa_tl {} } \tl_if_empty:nF { #1 } { \tl_if_empty:NTF \l_tmpa_tl { \tl_set:Nn \l_tmpa_tl { #1 } } { \tl_set:Nf \l_tmpa_tl { \l_tmpa_tl, #1 } } } \_sdaps_choicearray_preprocess:V \l_tmpa_tl % Clear the variables \seq_gclear:N \g_sdaps_choices_filter_seq \seq_gclear:N \g_sdaps_choices_cell_seq \seq_gclear:N \g_sdaps_choices_text_seq \int_gzero:N \g_sdaps_choices_box_int % Define new commands \newcommand \choice [ 1 ] [] { \_sdaps_choicearray_process_choice:nw { ##1 } } \newcommand \question [ 1 ] [] { \_sdaps_choicearray_process_question:nw { ##1 } } \group_begin: \tl_set:Nx \l_tmpb_tl {keepenv,layouter=\tl_use:N\l_sdaps_choicearray_layouter_tl,align=\l_sdaps_choicearray_align_tl\bool_if:NF\l_sdaps_choicearray_horizontal_bool{,flip},\l_sdaps_choicearray_extra_tl} \expandafter\sdapsarray\expandafter[\l_tmpb_tl] } { \endsdapsarray \group_end: \group_end: } \newenvironment { optionarray } [ 1 ] [] { \choicearray[singlechoice,#1] } { \endchoicearray } \newenvironment { rangearray } [ 1 ] [] { \group_begin: \sdaps_context_get:nN { rangearray } \l_tmpa_tl \tl_if_eq:NNT \l_tmpa_tl \q_no_value { \tl_set:Nn \l_tmpa_tl {} } \tl_if_empty:nF { #1 } { \tl_if_empty:NTF \l_tmpa_tl { \tl_set:Nn \l_tmpa_tl { #1 } } { \tl_set:Nf \l_tmpa_tl { \l_tmpa_tl, #1 } } } \_sdaps_rangearray_preprocess:V \l_tmpa_tl \newcommand \range [ 1 ] [] { \_sdaps_rangearray_process_question:nw { ##1 } } \group_begin: \tl_set:Nx \l_tmpb_tl {keepenv,align=\l_sdaps_rangearray_align_tl,no_header,\l_sdaps_rangearray_extra_tl} \expandafter\sdapsarray\expandafter[\l_tmpb_tl] } { \endsdapsarray \group_end: \group_end: } \ExplSyntaxOff % % \end{macrocode} % % \iffalse % \PrintChanges % \PrintIndex % \fi % % \Finale \endinput