On an applicative level, this question follows a previous one about quizzes (MCQs) with OCGs. But it seems (I suspect) that the issue might come from expansion concept and/or LaTeX3 syntax on how to pass a list of arguments, and therefore address a more general subject. Unfortunatly, for the moment being, I'm not comfortable at all with these topics.
I. − Overview
Thanks to AlexG, the ocgx2
package has been recently updated in order to support OCMD PDF feature: Optional Content Membership Dictionaries:
With a straightforward interface, the ocgx2
package can be used to manage quizzes/MCQs, see AlexG's answer to:
(1) ocgx2: verify and reset of Multiple Choice Questions with OCGs
Nevertheless, if one has many quizzes to offer, it can quickly become tedious to manage all the different references/Ids. Thus, the aim is to automate the references setting. This is done easily by wrapping the MCQ proposal in a question environment and assigning an unique ID to each proposal within the question by mean of counters.
II. − Automatic setting of identifiers
Borrowing the definition of the \quizButton
command given in (1), we obtain:
The loaded packages are (see MWE): ocgx2
(so xparse
), fontawesome5
, xcolor
, calc
\newcounter{quizquestion}% Question counter within a quiz
\newcounter{mcqproposal}[quizquestion]% Proposal counter to choose within a question
%- Defining a multiple solution MCQ environment
\NewDocumentEnvironment{quizquestion}{}{%
\stepcounter{quizquestion}%
\setcounter{mcqproposal}{0}%
\vspace*{20pt}%
\textbf{Question~\thequizquestion}\par\vspace*{8pt}%
}{}
%- Defining a proposition for multiple solution MCQ
\NewDocumentCommand{\mcqproposal}{m}{%
% Syntax − #1: choice id for answering proposal
\stepcounter{mcqproposal}%
\fcolorbox{gray}{white}{%
\parbox[t]{16pt}{%
\hspace*{2pt}%
\quizButton%
{\faIcon[regular]{square}}%
{verify:\thequizquestion}%
{mcqproposalref:\thequizquestion:\themcqproposal}
}%
\parbox[t]{\linewidth-16pt}{mcqproposalref:\thequizquestion:\themcqproposal\quad #1}%
}%
}
III. − Splitting and passing the arguments to automate OCMD rules
The next step is to manage (verify) all the correct and incorrect possible answers by mean of the \verifyButton
command given in (1). But the goal is also to simplify the interface within a question to only typeset the ID numbers in comma-separated lists both for the correct and incorrect propositions, such as \mcqverify{1,3,4}{2,5}
.
To do this, we must split the lists of identifiers, add the automatic naming of the proposals and finally reintroduce the new identifiers as OCMDs arguments. The issue occurs in this last stage and leads to bad results or compilation errors.
There are many ways to split a comma-separated list of arguments. A few examples are given below.
To retrieve a list of arguments separated by a delimiter with adequate first and last items, one can refer to the following links:
(2) Package xparse \SplitList last token
(3) pgffor: Special treatment for last item in \foreach-list
a) With a loop: pure LaTeX way without package
(4) Can I automatically draw my cards from a list? → Bordaigorl's answer
\makeatletter
\newcommand{\mcqSplitLoop}[1]{%
\@for\@c:=#1\do{%
\ifnum\numexpr\@c=1%
mcqproposalref:\thequizquestion:\@c%
\else%
,mcqproposalref:\thequizquestion:\@c%
\fi%
}%
}
\makeatother
b) With a loop: pgffor
package
(5) pgffor: Special treatment for last item in \foreach-list → Camandir's answer
\NewDocumentCommand{\mcqSplitDef}{m}{%
\foreach \n [count=\ni] in {#1} {%
\ifnum\ni=1%
mcqproposalref:\thequizquestion:\n%
\else%
,mcqproposalref:\thequizquestion:\n%
\fi%
}%
}
c) With \SplitList
from xparse
package
(6) Package xparse \SplitList last token → egreg's answer second solution
\NewDocumentCommand{\mcqSplitArguments}{>{\SplitList{,}} m}{%
{\ProcessList{#1}{\mcqSplitItems}}%
}
\newcommand{\mcqSplitItems}[1]{%
mcqproposalref:\thequizquestion:#1\let\mcqSplitItems\mcqSplitItemsAdd%
}
\newcommand{\mcqSplitItemsAdd}[1]{,mcqproposalref:\thequizquestion:#1}
d) With LaTeX3 syntax
(7) Package xparse \SplitList last token → egreg's answer third solution
\newcommand{\ejazzArgsIds}[1]{%
mcqproposalref:\thequizquestion:#1%
}
\ExplSyntaxOn
\NewDocumentCommand\mcqejazzList{m}{
\ejazz_process_list:n {#1}
}
% define a sequence for storing the "massaged" items
\seq_new:N \l_ejazz_items_seq
\cs_new_protected:Npn \ejazz_process_list:n #1{
% clear the sequence
\seq_clear:N \l_ejazz_items_seq
% cycle through the arguments, storing "\ejazzArgsIds{<arg>}" in the sequence
\clist_map_inline:nn { #1 }{
\seq_put_right:Nn \l_ejazz_items_seq { \ejazzArgsIds{##1} }
}
% output the sequence putting "," in between elements
\seq_use:Nnnn \l_ejazz_items_seq { , } { , } { , }
}
\ExplSyntaxOff
IV. Results
Other methods have been tested, even some stupid ones. All of these approaches give a correct display of the list of arguments, but also fail to compile when trying a syntax like: \mcqverify{\newarglist{<correct-number-ids>}}{\newarglist{<wrong-number-ids>}}
, with \mcqverify
adaptated from \verifyButton
given in (1).
They also do not compile if the list of arguments is modified inside the definition of the \mcqverify
command.
\NewDocumentCommand{\mcqverify}{m m}{%
% Syntax − #1: list of OCG ids of correct/required answer(s), comma separated
% #2: list of OCG ids of wrong answer(s), comma separated
\showocg{verify:\thequizquestion}{\fbox{\strut Verify}}
\begin{ocg}{verify:\thequizquestion}{verify:\thequizquestion}{off}% verification layer
\makebox[0pt][l]{%
\begin{ocmd}{\Not{\And{#1,\Not{\Or{#2}}}}}% "wrong" layer (OCMD)
\hspace*{0.25em}\textcolor{red!60!black}{\faTimes}%Wrong.
\end{ocmd}%
}%
\begin{ocmd}{\And{#1,\Not{\Or{#2}}}}% "correct" layer (OCMD)
\hspace*{0.25em}\textcolor{green!60!black}{\faCheck}%Correct.
\end{ocmd}%
\end{ocg}%
}
V. Conclusion
Althougth I am not familiar with the LaTeX3 syntax, with respect to the definition of the \resetButton
command from (1) and ideas borrowed to the link (8) below, some "empirical" adaptations have been tried with no success.
(8) Expand macro into xparse \SplitList macro → egreg's answer
In short, I think the issue is how to store the list of arguments in LaTeX3. It's a shame but it's beyond my current skill limits...
So, is this hypothesis correct and how can we get a working solution?
MWE
\documentclass{article}
\usepackage{ocgx2}% loads `xparse' package
\usepackage{fontawesome5}
\usepackage{xcolor}
\usepackage{calc} %\widthof{...}
\usepackage{pgffor}
%-- New OCMDs implementation: `ocgx2` package
%--------------------------------------------
%- Quiz button
\NewDocumentCommand{\quizButton}{o m m m}{%
% Syntax − #1: optional: radio button group
% #2: button shape: \faCircleThin, \faSquareO
% #3: verification id
% #4: choice id
% action on click: toggle myself (choice layer), hide verifcation layer
\makebox[0pt][l]{\actionsocg{#4}{}{#3}{#2}}%
\begin{ocg}[\IfValueT{#1}{radiobtngrp=#1}]{#4}{#4}{off}% choice layer
\makebox[\widthof{#2}]{\textcolor{green!60!black}{\faCheck}}%
\end{ocg}%
}
%- Verify button
\NewDocumentCommand{\verifyButton}{m m m}{%
% Syntax − #1: verification id
% #2: list of OCG ids of correct/required answer(s), comma separated
% #3: list of OCG ids of wrong answer(s), comma separated
\showocg{#1}{\fbox{\strut Verify}}
\begin{ocg}{#1}{#1}{off}% verification layer
\makebox[0pt][l]{%
\begin{ocmd}{\Not{\And{#2,\Not{\Or{#3}}}}}% "wrong" layer (OCMD)
Wrong.
\end{ocmd}%
}%
\begin{ocmd}{\And{#2,\Not{\Or{#3}}}}% "correct" layer (OCMD)
Correct.
\end{ocmd}%
\end{ocg}%
}
%- Reset button
\ExplSyntaxOn
\NewDocumentCommand{\resetButton}{m}{%
% Syntax − #1: list of OCG ids, comma separated
\clist_set:Nn\l_tmpa_clist{#1} % save OCG ids as L3 clist variable
\hideocg{\clist_use:Nn\l_tmpa_clist{~}}{\fbox{\strut Reset}}%
}
\ExplSyntaxOff
%-- Automated setting of references/IDs
%--------------------------------------
\newcounter{quizquestion}% Question counter within a quiz
\newcounter{mcqproposal}[quizquestion]% Proposal counter to choose within a question
%- Defining a multiple solutions MCQ environment
\NewDocumentEnvironment{quizquestion}{}{%
% Syntax − #1: optional title
\stepcounter{quizquestion}%
\setcounter{mcqproposal}{0}%
\vspace*{20pt}%
\textbf{Question~\thequizquestion}\par\vspace*{8pt}%
}{}
%- Defining a proposition for multiple solutions MCQ
\NewDocumentCommand{\mcqproposal}{m}{%
% Syntax − #1: choice id for answering proposal
\stepcounter{mcqproposal}%
\fcolorbox{gray}{white}{%
\parbox[t]{16pt}{%
\hspace*{2pt}%
\quizButton%
{\faIcon[regular]{square}}%
{verify:\thequizquestion}%
{mcqproposalref:\thequizquestion:\themcqproposal}
}%
\parbox[t]{\linewidth-16pt}{mcqproposalref:\thequizquestion:\themcqproposal\quad #1}%
}%
}
% https://tex.stackexchange.com/questions/146979/can-i-automatically-draw-my-cards-from-a-list
\makeatletter
\newcommand{\mcqSplitLoop}[1]{%
\@for\@c:=#1\do{%
\ifnum\numexpr\@c=1%
mcqproposalref:\thequizquestion:\@c%
\else%
,mcqproposalref:\thequizquestion:\@c%
\fi%
}%
}
\makeatother
% https://tex.stackexchange.com/questions/16198/pgffor-special-treatment-for-last-item-in-foreach-list
\NewDocumentCommand{\mcqSplitDef}{m}{%
\foreach \n [count=\ni] in {#1} {%
\ifnum\ni=1%
mcqproposalref:\thequizquestion:\n%
\else%
,mcqproposalref:\thequizquestion:\n%
\fi%
}%
}
% https://tex.stackexchange.com/questions/110898/package-xparse-splitlist-last-token
%
% xparse
\NewDocumentCommand{\mcqSplitArguments}{>{\SplitList{,}} m}{%
{\ProcessList{#1}{\mcqSplitItems}}%
}
\newcommand{\mcqSplitItems}[1]{%
mcqproposalref:\thequizquestion:#1\let\mcqSplitItems\mcqSplitItemsAdd%
}
\newcommand{\mcqSplitItemsAdd}[1]{,mcqproposalref:\thequizquestion:#1}
%
% LaTeX3
\newcommand{\ejazzArgsIds}[1]{%
mcqproposalref:\thequizquestion:#1%
}
\ExplSyntaxOn
\NewDocumentCommand\mcqejazzList{m}{
\ejazz_process_list:n {#1}
}
% define a sequence for storing the "massaged" items
\seq_new:N \l_ejazz_items_seq
\cs_new_protected:Npn \ejazz_process_list:n #1{
% clear the sequence
\seq_clear:N \l_ejazz_items_seq
% cycle through the arguments, storing "\ejazzArgsIds{<arg>}" in the sequence
\clist_map_inline:nn { #1 }{
\seq_put_right:Nn \l_ejazz_items_seq { \ejazzArgsIds{##1} }
}
% output the sequence putting "," in between elements
\seq_use:Nnnn \l_ejazz_items_seq { , } { , } { , }
}
\ExplSyntaxOff
% https://tex.stackexchange.com/questions/19746/cunning-latex-tricks
\newcommand{\mcqsplitlist}[2][,]{%
\def\itemdelim{\def\itemdelim{#1}}% Item delimiter delayed by one cycle
\renewcommand*{\do}[1]{{\itemdelim mcqproposalref:\thequizquestion:##1\itemdelim}}% How to process each item
\docsvlist{#2}% Process list
}
% https://tex.stackexchange.com/questions/166006/expand-macro-into-xparse-splitlist-macro
\newcommand{\argsIds}[1]{%
\ifnum\numexpr#1=1
mcqproposalref:\thequizquestion:#1%
\else
,mcqproposalref:\thequizquestion:#1%
\fi
}
\ExplSyntaxOn
\NewDocumentCommand\mcqSplitExpl{O{,} m O{\argsIds}}
{
\seq_set_split:Nnn \l_ejazzsplit_input_seq { #1 } { #2 }
\seq_map_function:NN \l_ejazzsplit_input_seq #3
}
\seq_new:N \l_ejazzsplit_input_seq
\ExplSyntaxOff
% Verify button splitting the comma separated lists of arguments
\ExplSyntaxOn
\NewDocumentCommand{\mcqverify}{m m}{%
% Syntax − #1: list of OCG ids of correct/required answer(s), comma separated
% #2: list of OCG ids of wrong answer(s), comma separated
%\clist_set:Nn\l_correct_clist{\mcqSplitDef{#1}} % save OCMD ids as L3 clist variable
%\clist_set:Nn\l_wrong_clist{\mcqSplitDef{#2}} % save OCMD ids as L3 clist variable
\showocg{verify:\thequizquestion}{\fbox{\strut Verify}}
\begin{ocg}{verify:\thequizquestion}{verify:\thequizquestion}{off}% verification layer
\makebox[0pt][l]{%
%\begin{ocmd}{\Not{\And{\clist_use:Nn\l_correct_clist{~},\Not{\Or{\clist_use:Nn\l_wrong_clist{~}}}}}}% "wrong" layer (OCMD)
\begin{ocmd}{\Not{\And{#1,\Not{\Or{#2}}}}}% "wrong" layer (OCMD)
\hspace*{0.25em}\textcolor{red!60!black}{\faTimes}%Wrong.
\end{ocmd}%
}%
%\begin{ocmd}{\And{\clist_use:Nn\l_correct_clist{~},\Not{\Or{\clist_use:Nn\l_wrong_clist{~}}}}}% "correct" layer (OCMD)
\begin{ocmd}{\And{#1,\Not{\Or{#2}}}}% "correct" layer (OCMD)
\hspace*{0.25em}\textcolor{green!60!black}{\faCheck}%Correct.
\end{ocmd}%
\end{ocg}%
}
\ExplSyntaxOff
\parindent=0pt
\begin{document}
%-- Manual setting of IDs
Which are the colour components of an RGB image?\\
Multiple required assertions.\\[8pt]
\quizButton{\faIcon[regular]{square}}{vrfyRGBCol}{magenta} Magenta.\\
\quizButton{\faIcon[regular]{square}}{vrfyRGBCol}{green2} Green.\\
\quizButton{\faIcon[regular]{square}}{vrfyRGBCol}{cyan} Cyan.\\
\quizButton{\faIcon[regular]{square}}{vrfyRGBCol}{blue} Blue.\\
\quizButton{\faIcon[regular]{square}}{vrfyRGBCol}{red2} Red.\\
\quizButton{\faIcon[regular]{square}}{vrfyRGBCol}{black2} Black.\\
\quizButton{\faIcon[regular]{square}}{vrfyRGBCol}{yellow2} Yellow.\\[8pt]
\verifyButton{vrfyRGBCol}{red2,green2,blue}{cyan,magenta,yellow2,black2}\hspace{0.3\linewidth}
\resetButton{vrfyRGBCol,red2,green2,blue,cyan,magenta,yellow2,black2}
%-- Automatic setting of IDs
\begin{quizquestion}
Which are the colour components of an RGB image?\\
Multiple required assertions.\\[8pt]
\mcqproposal{Green.}\\[2pt]
\mcqproposal{Cyan.}\\[2pt]
\mcqproposal{Blue.}\\[2pt]
\mcqproposal{Red.}\\[2pt]
\mcqproposal{Yellow.}\\[8pt]
%- Full typesetting the lists of ids ← Works (obvious: `ocgx2` package interface)
%\mcqverify{mcqproposalref:\thequizquestion:1,mcqproposalref:\thequizquestion:3,mcqproposalref:\thequizquestion:4}{mcqproposalref:\thequizquestion:2,mcqproposalref:\thequizquestion:5}%
%\verifyButton{verify:\thequizquestion}{mcqproposalref:\thequizquestion:1,mcqproposalref:\thequizquestion:3,mcqproposalref:\thequizquestion:4}{mcqproposalref:\thequizquestion:2,mcqproposalref:\thequizquestion:5}
%- Manual splitting of the lists of IDs (rather stupid) ← Compiles but does not work
\def\ocmdlistcorrect{mcqproposalref:\thequizquestion:1,mcqproposalref:\thequizquestion:3,mcqproposalref:\thequizquestion:4}%
\def\ocmdlistwrong{mcqproposalref:\thequizquestion:2,mcqproposalref:\thequizquestion:5}%
% Does not work: checking all correct answers with any wrong answer give a correct result
%\mcqverify{mcqproposalref:\thequizquestion:1,mcqproposalref:\thequizquestion:3,mcqproposalref:\thequizquestion:4}{\ocmdlistwrong}
% Does not work: checking empty, one or more correct answer(s) give a correct result
%\mcqverify{\ocmdlistcorrect}{mcqproposalref:\thequizquestion:2,mcqproposalref:\thequizquestion:5}
% Does not work: checking any/all answers give a correct result
%\mcqverify{\ocmdlistcorrect}{\ocmdlistwrong}
%- Good list of arguments, but fails to compile
%{\scriptsize Correct: \mcqSplitDef{1,3,4}}\par
%{\scriptsize Wrong: \mcqSplitDef{2,5}}\par
%\mcqverify{\mcqSplitDef{1,3,4}}{\mcqSplitDef{2,5}}
%{\scriptsize Correct: \mcqSplitExpl{1,3,4}}\par
%{\scriptsize Correct: \mcqSplitLoop{1,3,4}}\par
%{\scriptsize Wrong : \mcqSplitExpl{\ocmdlistwrong}}\par%
%\mcqverify{\mcqSplitExpl{1,3,4}}{\mcqSplitExpl{2,5}}
\mcqejazzList{1,3,4}
%\mcqverify{1,3,4}{2,5} ← Interface to reach
%\hspace{0.3\linewidth}\mcqreset{1,2,3,4,5}
%or \mcqreset (with a loop and a counter or anything else)
\end{quizquestion}
\end{document}