summaryrefslogtreecommitdiff
path: root/macros/latex/contrib/sdaps/sdapslayout.dtx
diff options
context:
space:
mode:
Diffstat (limited to 'macros/latex/contrib/sdaps/sdapslayout.dtx')
-rw-r--r--macros/latex/contrib/sdaps/sdapslayout.dtx790
1 files changed, 790 insertions, 0 deletions
diff --git a/macros/latex/contrib/sdaps/sdapslayout.dtx b/macros/latex/contrib/sdaps/sdapslayout.dtx
new file mode 100644
index 0000000000..6788e55c5a
--- /dev/null
+++ b/macros/latex/contrib/sdaps/sdapslayout.dtx
@@ -0,0 +1,790 @@
+% \iffalse meta-comment
+%
+% Copyright (C) 2014 by Henry Menke <henrimenke@gmail.com>
+% Copyright (C) 2015-2016 by Benjamin Berg <benjamin@sipsolutions.net>
+%
+% This work may be distributed and/or modified under the
+% conditions of the LaTeX Project Public License, either version 1.3
+% of this license or (at your option) any later version.
+% The latest version of this license is in
+% http://www.latex-project.org/lppl.txt
+% and version 1.3 or later is part of all distributions of LaTeX
+% version 2005/12/01 or later.
+%
+% This work has the LPPL maintenance status `maintained'.
+%
+% The Current Maintainer of this work is Benjamin Berg.
+%
+% \fi
+%
+% \iffalse
+%<*driver>
+\ProvidesFile{sdapslayout.dtx}
+%</driver>
+%<package>\NeedsTeXFormat{LaTeX2e}[1999/12/01]
+%<package>\ProvidesPackage{sdapslayout}
+%<*package>
+ [2015/04/10 v0.1 Initial version of SDAPS layout package]
+%</package>
+%
+%<*driver>
+\documentclass{l3doc}
+\usepackage{sdapslayout}[2015/04/10]
+\EnableCrossrefs
+%\CodelineIndex
+\RecordChanges
+\begin{document}
+ \DocInput{sdapslayout.dtx}
+\end{document}
+%</driver>
+% \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_var_tl
+\tl_new:N \l_sdaps_choicearray_text_tl
+\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,
+ var .tl_set:N = \l_sdaps_choicearray_var_tl,
+ text .tl_set:N = \l_sdaps_choicearray_text_tl,
+ 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:nn #1#2
+{
+ \keys_set_known:nnN { sdaps / choicearray } { #1 } \l_sdaps_choicearray_extra_tl
+
+ \tl_if_empty:NTF \l_sdaps_choicearray_text_tl {
+ \sdaps_qobject_begin:nnn { choicearray } { Head } { #2 }
+ } {
+ \sdaps_qobject_begin:nnV { choicearray } { Head } \l_sdaps_choicearray_text_tl
+ }
+
+ \tl_if_empty:NF \l_sdaps_choicearray_var_tl {
+ \sdaps_qobject_append_var:V \l_sdaps_choicearray_var_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:nn { Vn }
+
+\cs_new_protected_nopar:Npn \_sdaps_choicearray_postprocess:
+{
+ \sdaps_qobject_end:n { choicearray }
+}
+
+\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_var_tl
+\tl_new:N \l_sdaps_rangearray_text_tl
+\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
+
+\keys_define:nn { sdaps / rangearray }
+{
+ var .tl_set:N = \l_sdaps_rangearray_var_tl,
+ text .tl_set:N = \l_sdaps_rangearray_text_tl,
+ 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 = { },
+ 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:nn #1#2
+{
+ \keys_set_known:nnN { sdaps / rangearray } { #1 } \l_sdaps_rangearray_extra_tl
+
+ \tl_if_empty:NTF \l_sdaps_rangearray_text_tl {
+ \sdaps_qobject_begin:nnn { rangearray } { Head } { #2 }
+ } {
+ \sdaps_qobject_begin:nnV { rangearray } { Head } \l_sdaps_rangearray_text_tl
+ }
+
+ \sdaps_checkbox_set_type:n { singlechoice }
+
+ \tl_if_empty:NF \l_sdaps_rangearray_var_tl {
+ \sdaps_qobject_append_var:V \l_sdaps_rangearray_var_tl
+ }
+}
+\cs_generate_variant:Nn \_sdaps_rangearray_preprocess:nn { Vn }
+
+\cs_new_protected_nopar:Npn \_sdaps_rangearray_postprocess:
+{
+ \sdaps_qobject_end:n { rangearray }
+}
+
+% 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 } [ 2 ] []
+{
+ \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:Vn \l_tmpa_tl { #2 }
+ % 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:
+ % Process keys
+ \_sdaps_choicearray_postprocess:
+
+ \group_end:
+}
+
+\newenvironment { optionarray } [ 2 ] []
+{
+ \choicearray[singlechoice,#1] { #2 }
+}
+{
+ \endchoicearray
+}
+
+\newenvironment { rangearray } [ 2 ] []
+{
+ \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:Vn \l_tmpa_tl { #2 }
+
+ \newcommand \range [ 1 ] [] {
+ \_sdaps_rangearray_process_question:nw { ##1 }
+ }
+
+ \group_begin:
+
+ \tl_set:Nx \l_tmpb_tl {keepenv,align=\l_sdaps_rangearray_align_tl,\l_sdaps_rangearray_extra_tl}
+ \expandafter\sdapsarray\expandafter[\l_tmpb_tl]
+}
+{
+ \endsdapsarray
+ \group_end:
+ % Process keys
+ \_sdaps_rangearray_postprocess:
+
+ \group_end:
+}
+
+
+\ExplSyntaxOff
+
+%
+% \end{macrocode}
+%
+% \iffalse
+% \PrintChanges
+% \PrintIndex
+% \fi
+%
+% \Finale
+\endinput