1

This is a followup question to Class with keyval option consisting of lists of keyval pairs. In an answer to that question, Skillmon showed how to write a class that would provide a command, \people, which takes a comma-separated list of groups of key-value pairs and then typeset that information in a list.

I'd like to additionally define two new macros, \specialpersonone and an optional \specialpersontwo to be typeset at the beginning of that list, as well as in a table on another page. These would have the same key-value pairs as \people in addition to another key-value pair field.

Here's a non-working minimal example. I tried to follow what Skillmon did before, but I don't totally understand how expkv processes arguments. It seems that the arguments aren't being processed before I try to typeset them.

non-working mwe

myclass.cls

\NeedsTeXFormat{LaTeX2e}
\ProvidesClass{myclass}

\LoadClass[oneside,letterpaper]{memoir}

\RequirePackage{expkv-def}

\ekvdefinekeys{myclass/people}
  {
     long store title = \myclass@people@title
    ,long store role  = \myclass@people@role
    ,long store name  = \myclass@people@name
  }
\ekvsetdef\myclass@people@options{myclass/people}
\newcommand\people[1]{\edef\myclass@people{\unexpanded{#1}}}

\ekvdefinekeys{myclass/special-person-one}
  {
     long store title = \myclass@specialpersonone@title
    ,long store role = \myclass@specialpersonone@role
    ,long store name = \myclass@specialpersonone@name
    ,long store field = \myclass@specialpersonone@field
  }
\ekvsetdef\myclass@specialpersonone@options{myclass/special-person-one}
\newcommand\specialpersonone[1]{\edef\myclass@specialpersoone{\unexpanded{#1}}}

\ekvdefinekeys{myclass/special-person-two}
  {
     long store title = \myclass@specialpersontwo@title
    ,long store role = \myclass@specialpersontwo@role
    ,long store name = \myclass@specialpersontwo@name
    ,long store field = \myclass@specialpersontwo@field
  }
\ekvsetdef\myclass@specialpersontwo@options{myclass/special-person-two}
\newcommand\specialpersontwo[1]{\edef\myclass@specialpersotwo{\unexpanded{#1}}}

\newcommand*\mypeople[1]
  {%
    \unless\ifx\myclass@people\@empty
      \begin{itemize}
        \item \myclass@specialpersonone@title\space\myclass@specialpersonone@name, Special Role 1
        \unless\ifx\myclass@specialpersontwo@\empty
          \item \myclass@specialpersontwo@title\space\myclass@specialpersontwo@name, Special Role 2
        \fi
        \expandafter\ekvcsvloop\expandafter\mypeople@aux\expandafter{#1}%
      \end{itemize}%
    \fi
  }
\newcommand\mypeople@aux[1]
  {%
    \item
    \begingroup
      \myclass@people@options{#1}%
      \unless\ifx\myclass@people@title\@empty
        \myclass@people@title\space
      \fi
      \myclass@people@name
      \unless\ifx\myclass@people@role\@empty
        , \myclass@people@role
      \fi
    \endgroup
  }

\newcommand{\makemypage}{%
  \pagestyle{empty}
  \thispagestyle{empty}
  \begin{titlingpage}
    \begin{center}
      \mypeople{\myclass@people}
    \end{center}
  \end{titlingpage}}
  
\newcommand{\makemytablepage}{%
  \pagestyle{empty}
  \thispagestyle{empty}
  \begin{titlingpage}
    \begin{center}
      \begin{tabular}{ll}
        Special people ~ & \myclass@specialpersonone@name\\
        ~ & \myclass@specialpersonone@field\\
        ~ &~\\
        \unless\ifx\myclass@specialpersontwo@\empty
          ~ & \myclass@specialpersontwo@name\\
          ~ & \myclass@specialpersontwo@field\\
          ~ & ~\\
        \fi
      \end{tabular}
    \end{center}
  \end{titlingpage}}

\endinput

myfile.tex

\documentclass[]{myclass}
\specialpersonone{title={Doctor}, name={Special Person}, field={Field 1}}
\specialpersontwo{title={Doctor}, name={Second Person}, field={Field 2}}
\people{
  {title={Doctor},name={John Smith},role={Role}},
  {title={Doctor},name={Jane Smith},role={}}}

\begin{document}
  \makemypage
  \makemytablepage
\end{document}
2
  • What should happen if a special person has a field and a role? Or is that forbidden?
    – Skillmon
    Jan 15, 2022 at 10:53
  • A maybe better interface would be to have the people list in the format <name>={<key=val>}, so for your example you'd use \people{John Smith={title=Doctor, role=Role}, Jane Smith={title=Doctor}}. That'd give a more natural interface, imho (same could be done for the special persons). Leave a comment if you're interested in this. Also, instead of limiting the special persons to two, a different interface could be used to make them scalable to arbitrarily many as well.
    – Skillmon
    Jan 15, 2022 at 11:46

1 Answer 1

1

expkv parses the option at the place in which you call \myclass@people@options or \myclass@specialpersonone@options or \myclass@specialpersontwo@options with the key=value list as the argument (or \ekvset{<set>}, \ekvsetdef\<macro>{<set>} just defines a shortcut for that (which indeed is a bit faster than doing \ekvset{<set>}); this is the same for all the key=value interfaces, you have to call the frontend macro to actually set values). The code in my last answer did this step inside \mypeople@aux. Since you never do this for the special persons they won't have the values assigned to the internals.

Below are two versions with slightly different input syntax (I'd prefer the second, but the choice is yours). The advantage is less complicated input (one key less which was never really optional, every person should have a name!).

CSV-List version

But storing the complete key=value list inside a macro (the way you do with \specialpersonone and \specialpersontwo) isn't necessary for those since that's not a csv-list that you want to parse way down the road. Hence I changed them to do the key=value parsing on the spot. The assigned values are then smuggled out of the group via a temporary auxiliary macro \myclass@tmp.

Also, in order to reduce duplicated code, I changed the way the key=value setup is structured. Now there is a set for the special persons, and if an unknown key is used it falls through to the general set for people (via unknown redirect).

I've also created two convenience macros to reduce the amount of \ifx\@empty tests, and a convenience macro for the output of special persons.

Class file:

\NeedsTeXFormat{LaTeX2e}
\ProvidesClass{myclass}

\LoadClass[oneside,letterpaper]{memoir}

\RequirePackage{expkv-def}
\RequirePackage{booktabs}

\ekvdefinekeys{myclass/people}
  {
     long store title = \myclass@people@title
    ,long store role  = \myclass@people@role
    ,long store name  = \myclass@people@name
  }
\ekvsetdef\myclass@people@options{myclass/people}

\ekvdefinekeys{myclass/specialpeople}
  {
     long store field = \myclass@people@field
    ,unknown redirect = myclass/people
  }
\ekvsetdef\myclass@specialpeople@options{myclass/specialpeople}

% initialise macros
\newcommand*\myclass@people{}
\newcommand\people[1]{\edef\myclass@people{\unexpanded{#1}}}

% special persons are handled differently, the options are evaluated on the spot
% and then stored in other macros via \myclass@copyspecialperson calls
\newcommand\specialpersonone{\specialperson@aux{A}}
\newcommand\specialpersontwo{\specialperson@aux{B}}
\newcommand\specialperson@aux[2]
  {%
    % group to keep the assignments to \myclass@people@role etc. local
    \begingroup
      \myclass@specialpeople@options{#2}%
      % smuggle out the settings inside of \myclass@tmp (with
      % \myclass@copyspecialperson)
      \def\myclass@tmp{}%
      \ekvcsvloop{\myclass@copyspecialperson{#1}}{title,role,name,field}%
      % close the group after expanding the temporary macro once
      \expandafter
    \endgroup
    \myclass@tmp
  }
\newcommand\myclass@copyspecialperson[2]
  {%
    \edef\myclass@tmp
      {%
        \unexpanded\expandafter{\myclass@tmp}% keep old contents
        \def
        \unexpanded\expandafter{\csname myclass@specialperson@#1@#2\endcsname}%
        {%
          \unexpanded\expandafter\expandafter\expandafter
            {\csname myclass@people@#2\endcsname}%
        }%
      }%
  }
% to detect whether the special persons were used we initialise the @name macros
\newcommand*\myclass@specialperson@A@name{}
\newcommand*\myclass@specialperson@B@name{}

\newcommand\myclass@unlessempty[3]
  {%
    % #1: macro
    % #2: pre
    % #3: post
    \unless\ifx\@empty#1%
      \expandafter\@secondoftwo
    \fi
    \@gobble{#2#1#3}%
  }
\newcommand\myclass@unlessempty@special[1]
  {\expandafter\myclass@unlessempty\csname myclass@specialperson@#1\endcsname}

\newcommand\myclass@specialperson@output[1]
  {%
    \myclass@unlessempty@special{#1@name}
      {%
        \item
        \myclass@unlessempty@special{#1@title}{}{ }%
      }%
      {%
        \myclass@unlessempty@special{#1@role}{, }{}%
        % \myclass@unlessempty@special{#1@field}{, }{}% TODO: is this needed?
      }%
  }

\newcommand*\mypeople[1]
  {%
    \unless\ifx\myclass@people\@empty
      \begin{itemize}
        \myclass@specialperson@output{A}%
        \myclass@specialperson@output{B}%
        \ekvcsvloop\mypeople@aux{#1}%
      \end{itemize}%
    \fi
  }
\newcommand\mypeople@aux[1]
  {%
    \item
    \begingroup
      \myclass@people@options{#1}%
      \myclass@unlessempty\myclass@people@title{}{ }%
      \myclass@people@name
      \myclass@unlessempty\myclass@people@role{, }{}%
    \endgroup
  }

\newcommand{\makemypage}{%
  \pagestyle{empty}
  \thispagestyle{empty}
  \begin{titlingpage}
    \begin{center}
      \expandafter\mypeople\expandafter{\myclass@people}
    \end{center}
  \end{titlingpage}}
  
\newcommand{\makemytablepage}
  {%
    % check whether at least one of the two special persons was used
    \begingroup
      \edef\myclass@tmp
        {%
          \unexpanded\expandafter{\myclass@specialperson@A@name}%
          \unexpanded\expandafter{\myclass@specialperson@B@name}%
        }%
      \expandafter
    \endgroup
    \unless\ifx\myclass@tmp\@empty
      \pagestyle{empty}
      \thispagestyle{empty}
      \begin{titlingpage}
        \begin{center}
          \begin{tabular}{ll}
            Special people
              \myclass@unlessempty@special{A@name}{&}%
                {%
                  \\
                  \myclass@unlessempty@special{A@field}{&}{\\}%
                  \addlinespace
                }%
              \myclass@unlessempty@special{B@name}{&}%
                {%
                  \\
                  \myclass@unlessempty@special{B@field}{&}{\\}%
                }%
          \end{tabular}
        \end{center}
      \end{titlingpage}%
    \fi
  }

\endinput

Document:

\documentclass[]{myclass}

\specialpersonone{title={Doctor}, name={Special Person}, field={Field 1}}
\specialpersontwo{title={Doctor}, name={Second Person}, field={Field 2}}
\people{
  {title={Doctor},name={John Smith},role={Role}},
  {title={Doctor},name={Jane Smith}}}

\begin{document}
  \makemypage

  \makemytablepage
\end{document}

Output:

enter image description here

and

enter image description here


Name={key=val} version

Since the changes necessary to use the suggested <name>={<key=val>} interface are minimal, I decided to just provide it. If a <name> should contain a comma you can use {<name>}={<key=val>} to protect that name. And if the name doesn't require a title or role you can simply use it without ={<key=val>}.

Class file:

\NeedsTeXFormat{LaTeX2e}
\ProvidesClass{myclass}

\LoadClass[oneside,letterpaper]{memoir}

\RequirePackage{expkv-def}
\RequirePackage{booktabs}

\ekvdefinekeys{myclass/people}
  {
     long store title = \myclass@people@title
    ,long store role  = \myclass@people@role
  }
\ekvsetdef\myclass@people@options{myclass/people}

\ekvdefinekeys{myclass/specialpeople}
  {
     long store field = \myclass@people@field
    ,unknown redirect = myclass/people
  }
\ekvsetdef\myclass@specialpeople@options{myclass/specialpeople}

% initialise macros
\newcommand*\myclass@people{}
\newcommand\people[1]{\edef\myclass@people{\unexpanded{#1}}}

% special persons are handled differently, the options are evaluated on the spot
% and then stored in other macros via \myclass@copyspecialperson calls
\newcommand\specialpersonone{\specialperson@aux{A}}
\newcommand\specialpersontwo{\specialperson@aux{B}}
\newcommand\specialperson@aux[3]
  {%
    \expandafter\def\csname myclass@specialperson@#1@name\endcsname{#2}%
    % group to keep the assignments to \myclass@people@role etc. local
    \begingroup
      \myclass@specialpeople@options{#3}%
      % smuggle out the settings inside of \myclass@tmp (with
      % \myclass@copyspecialperson)
      \def\myclass@tmp{}%
      \ekvcsvloop{\myclass@copyspecialperson{#1}}{title,role,field}%
      % close the group after expanding the temporary macro once
      \expandafter
    \endgroup
    \myclass@tmp
  }
\newcommand\myclass@copyspecialperson[2]
  {%
    \edef\myclass@tmp
      {%
        \unexpanded\expandafter{\myclass@tmp}% keep old contents
        \def
        \unexpanded\expandafter{\csname myclass@specialperson@#1@#2\endcsname}%
        {%
          \unexpanded\expandafter\expandafter\expandafter
            {\csname myclass@people@#2\endcsname}%
        }%
      }%
  }
% to detect whether the special persons were used we initialise the @name macros
\newcommand*\myclass@specialperson@A@name{}
\newcommand*\myclass@specialperson@B@name{}

\newcommand\myclass@unlessempty[3]
  {%
    % #1: macro
    % #2: pre
    % #3: post
    \unless\ifx\@empty#1%
      \expandafter\@secondoftwo
    \fi
    \@gobble{#2#1#3}%
  }
\newcommand\myclass@unlessempty@special[1]
  {\expandafter\myclass@unlessempty\csname myclass@specialperson@#1\endcsname}

\newcommand\myclass@specialperson@output[1]
  {%
    \myclass@unlessempty@special{#1@name}
      {%
        \item
        \myclass@unlessempty@special{#1@title}{}{ }%
      }%
      {%
        \myclass@unlessempty@special{#1@role}{, }{}%
        % \myclass@unlessempty@special{#1@field}{, }{}% TODO: is this needed?
      }%
  }

\newcommand*\mypeople[1]
  {%
    \unless\ifx\myclass@people\@empty
      \begin{itemize}
        \myclass@specialperson@output{A}%
        \myclass@specialperson@output{B}%
        \ekvparse{\item\@firstofone}\mypeople@aux{#1}%
      \end{itemize}%
    \fi
  }
\newcommand\mypeople@aux[2]
  {%
    \item
    \begingroup
      \myclass@people@options{#2}%
      \myclass@unlessempty\myclass@people@title{}{ }%
      #1%
      \myclass@unlessempty\myclass@people@role{, }{}%
    \endgroup
  }

\newcommand{\makemypage}{%
  \pagestyle{empty}
  \thispagestyle{empty}
  \begin{titlingpage}
    \begin{center}
      \expandafter\mypeople\expandafter{\myclass@people}
    \end{center}
  \end{titlingpage}}
  
\newcommand{\makemytablepage}
  {%
    % check whether at least one of the two special persons was used
    \begingroup
      \edef\myclass@tmp
        {%
          \unexpanded\expandafter{\myclass@specialperson@A@name}%
          \unexpanded\expandafter{\myclass@specialperson@B@name}%
        }%
      \expandafter
    \endgroup
    \unless\ifx\myclass@tmp\@empty
      \pagestyle{empty}
      \thispagestyle{empty}
      \begin{titlingpage}
        \begin{center}
          \begin{tabular}{ll}
            Special people
              \myclass@unlessempty@special{A@name}{&}%
                {%
                  \\
                  \myclass@unlessempty@special{A@field}{&}{\\}%
                  \addlinespace
                }%
              \myclass@unlessempty@special{B@name}{&}%
                {%
                  \\
                  \myclass@unlessempty@special{B@field}{&}{\\}%
                }%
          \end{tabular}
        \end{center}
      \end{titlingpage}%
    \fi
  }

\endinput

Document:

\documentclass[]{myclass}

\specialpersonone{Special Person}{title={Doctor}, field={Field 1}}
\specialpersontwo{Second Person}{title={Doctor}, field={Field 2}}
\people
  {
     John Smith={title={Doctor},role={Role}}
    ,Jane Smith={title={Doctor}}
    ,John Doe
  }

\begin{document}
  \makemypage

  \makemytablepage
\end{document}

Output: Like above (except for the additional bullet point "• John Doe").

You must log in to answer this question.

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