2

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:

(0) CTAN Up­date: ocgx2

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}

2 Answers 2

4

Let's look at the expl3 variant:

\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

As you probably realized, this outputs the right thing. So why doesn't it work in \mcqverify? There you try e.g.

\clist_set:Nn\l_correct_clist{\mcqSplitDef{#1}}

Here the clist is literally set to the single element list with the element \mcqSplitDef{#1}, because there is no comma in the argument. Especially expl3 does not expand \mcqSplitDef on it's own. If you want \mcqSplitDef to be expanded, you need to use \clist_set:Nf instead of \clist_set:Nn (:Nx would work too, but :Nf is enough here). If you try this, it still doesn't work. Why? \mcqSplitDef is not "expandable". (This can be seen by the \cs_new_protected instead of \cs_new. Just removing _protected doesn't help either, because the macros used in there are not expandable. ) So there are two options: Write a fully expandable version of \ejazz_process_list:n (hard) or find another way to pass the resulting list without using it as a expansion result. For example, you can do the \clist_set:Nf directly inside \ejazz_process_list, there \seq_use:Nnnn is expandable. It is even easier if we convert \l_correct_clist into \l_correct_seq, the seq can be used directly in \ejazz_process_list:n. Of course, now \ejazz_process_list:n has to support different sequences, so we add an additional parameter:

\cs_new_protected:Npn \ejazz_process_list:Nn #1 #2{
  % clear the sequence
  \seq_clear:N #1
  % cycle through the arguments, storing "\ejazzArgsIds{<arg>}" in the sequence
  \clist_map_inline:nn { #2 }{
    \seq_put_right:Nx #1 { \ejazzArgsIds{##1} }% Here :Nx instead of :Nn ensures that \ejazzArgsId is actually evaluated instead of passed as-is
  }
}

How do we use this inside of \mcqverify? First we declare our sequences

\seq_new:N \l_correct_seq
\seq_new:N \l_wrong_seq

then we can set them inside of mcqverify using our \ejazz_process_list:Nn:

\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
  \ejazz_process_list:Nn \l_correct_seq { #1 } % save OCMD ids as L3 seq variable
  \ejazz_process_list:Nn \l_wrong_seq { #2 } % save OCMD ids as L3 seq variable

Now we can access the comma-separated values of \l_correct_seq and \l_wrong_seq using \seq_use:Nn \l_correct_seq {,} and \seq_use:Nn \l_wrong_seq {,}. This is expandable, but the argument of ocmd is not a expl3 argument, so we can't force expansion by replacing a :n with a :f. This makes things more complicated. So let's us a workaround: We just use a local macro \ejazz__verify:nn which takes already expanded lists as argument. (This makes \ejazz__verify:nn easier to define because we do not have to worry about expansion) Now we use

  \ejazz__verify:ff { \seq_use:Nn \l_correct_seq {,} } { \seq_use:Nn \l_wrong_seq {,} }
}

to tell expl3 to expand both arguments fully. Now \ejazz__verify:nn is streight forward:

\cs_new_protected:Npn \ejazz__verify:nn #1 #2 {
  \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}%
}

Do you remember the part about just using \ejazz__verify:ff if we want to expand the arguments for \ejazz__verify:nn? It isn't quite as easy, we have to tell expl3 that we want to use \ejazz__verify:ff, such that expl3 can prepare this macro. So we add

\cs_generate_variant:Nn \ejazz__verify:nn {ff}

Ok, let's put it all together:

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

%
% LaTeX3
\newcommand{\ejazzArgsIds}[1]{%
  mcqproposalref:\thequizquestion:#1%
}
\ExplSyntaxOn
% define a sequence for storing the "massaged" items
\cs_new_protected:Npn \ejazz_process_list:Nn #1 #2{
  % clear the sequence
  \seq_clear:N #1
  % cycle through the arguments, storing "\ejazzArgsIds{<arg>}" in the sequence
  \clist_map_inline:nn { #2 }{
    \seq_put_right:Nx #1 { \ejazzArgsIds{##1} }
  }
}
\ExplSyntaxOff

% Verify button splitting the comma separated lists of arguments
\ExplSyntaxOn
\seq_new:N \l_correct_seq
\seq_new:N \l_wrong_seq
\cs_new_protected:Npn \ejazz__verify:nn #1 #2 {
  \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}%
}
\cs_generate_variant:Nn \ejazz__verify:nn {ff}
\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
  \ejazz_process_list:Nn \l_correct_seq { #1 } % save OCMD ids as L3 seq variable
  \ejazz_process_list:Nn \l_wrong_seq { #2 } % save OCMD ids as L3 seq variable
  \ejazz__verify:ff { \seq_use:Nn \l_correct_seq {,} } { \seq_use:Nn \l_wrong_seq {,} }
}
\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]

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

This has to be compiled at least twice to fix all OCGs.

Result seen on screen with a typical wrong and a correct answer

enter image description here

4
  • @KJO Thank you! Jun 3, 2019 at 23:19
  • @Marcel Krüger Thank you for your prompt reply. That works like a charm! It was what I suspected, but I would have been unable to find a solution. So, many thanks also for the really informative explanations (at least for me). Their understanding has been an opportunity to take my fragile first steps in the "feared" world of the LaTeX3 syntax... A kind of personal "cultural revolution" :-)
    – ejazz
    Jun 4, 2019 at 7:07
  • @MarcelKrüger Thanks from my side too. I have a question about the ff variant of \ejazz__verify:nn. As xx seems to work as well, I am curious about the reason why you prefer ff.
    – AlexG
    Jun 4, 2019 at 9:33
  • @AlexG That's mostly just a personal preference: I often try to make my expl3 macros expandable (sometimes because I need them to be, sometimes just for the challenge). Then :x can't be used, so I got used to only apply it if it is necessary. (Actually I thought about using \seq_put_right:Nf instead of \seq_put_right:Nx as well, but I wanted it to be more robust against changes to \ejazzArgsIds.) The only difference I am aware of is that :ff will apply additional expansion steps to the first token of the list, but that is a m from \ejazzArgsIds, so it shouldn't make a difference. Jun 4, 2019 at 9:57
3

Beginning with version [0.54 2022/04/01] of pkg ocgx2, the ocmd environment expands its argument, allowing for macros and commands that expand to comma-separated lists of OCG ids. This removes the need for wrapping the ocmd environment in an outer command with expanding argument:

\def\correctlist{A,B,C,E}
\def\wronglist{D,F}
\begin{ocmd}{\And{\correctlist,\Not{\Or{\wronglist}}}
✔
\end{ocmd}
\begin{ocmd}{\Not{\And{\correctlist,\Not{\Or{\wronglist}}}}
✘
\end{ocmd}

Example of the question:

\documentclass{article}

\usepackage{ocgx2}[2022/04/01] % loads `xparse' package
\usepackage{fontawesome5}
\usepackage{xcolor}
\usepackage{calc} %\widthof{...}

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

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

\ExplSyntaxOn % LaTeX3

% defines and fills an L3 sequence with OCG ids obtained from comma-separated list of answer ids
\cs_new_protected_nopar:Nn\ejazz_process_list:Nnn{% args: L3 seq variable, question number, comma list of answer ids
  % create/clear the sequence
  \seq_clear_new:N#1
  % cycle through comma list (3rd argument)
  \clist_map_inline:nn{#3}{\seq_put_right:Nn#1{mcqproposalref:#2:##1}}
}

% Verify button splitting the comma separated lists of arguments
\NewDocumentCommand{\mcqverify}{m m}{%
  % Syntax − #1: list of correct/required answer id(s), comma separated
  %          #2: list of wrong answer id(s), comma separated
  \ejazz_process_list:Nnn\l_correct_seq{\thequizquestion}{#1} % save "correct" OCG ids as L3 seq variable
  \ejazz_process_list:Nnn\l_wrong_seq{\thequizquestion}{#2} % save "wrong" OCG ids as L3 seq variable
  \showocg{verify:\thequizquestion}{\fbox{\strut Verify}}
  \begin{ocg}{verify:\thequizquestion}{verify:\thequizquestion}{off}% verification layer
    \makebox[0pt][l]{%
      % ocmd evironment now expanding its argument :-)
      \begin{ocmd}{\Not{\And{\seq_use:Nn\l_correct_seq{,},\Not{\Or{\seq_use:Nn\l_wrong_seq{,}}}}}}% "wrong" layer (OCMD)
        \hspace*{0.25em}\textcolor{red!60!black}{\faTimes}%Wrong.
      \end{ocmd}%
    }%
    \begin{ocmd}{\And{\seq_use:Nn\l_correct_seq{,},\Not{\Or{\seq_use:Nn\l_wrong_seq{,}}}}}% "correct" layer (OCMD)
      \hspace*{0.25em}\textcolor{green!60!black}{\faCheck}%Correct.
    \end{ocmd}%
  \end{ocg}%
}

\ExplSyntaxOff

\parindent=0pt

\begin{document}

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

\mcqverify{1,3,4}{2,5}

\end{quizquestion}

\end{document}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .