% \iffalse meta-comment
%
% File: expkv-opt.dtx Copyright (C) 2020-2024 Jonathan P. Spratte
%
% This work  may be  distributed and/or  modified under  the conditions  of the
% LaTeX Project Public License (LPPL),  either version 1.3c  of this license or
% (at your option) any later version.  The latest version of this license is in
% the file:
%
%   http://www.latex-project.org/lppl.txt
%
% ------------------------------------------------------------------------------
%
%<*driver>^^A>>=
\def\expkvDocNoGenerate{}
\input expkv-bundle.ins
\generate{\file{expkv-opt.sty}{\from{expkv-opt.dtx}{pkg}}}
\endbatchfile
%</driver>^^A=<<
% \fi
%
% \section{\expkvo}
%
% \gobbledocstriptag
%<*pkg>
%
% First we check whether the \LaTeXe\ kernel supports raw options. If it doesn't
% we check whether a specific version was requested, and if that's not the case
% we manually run |\pkgcls@parse@date@arg| with the last version that supported
% non-raw options.
%    \begin{macrocode}
\IfFormatAtLeastTF{2021/05/01}
  {}
  {%
    \ifx\pkgcls@targetlabel\@empty
      \ifnum\requestedLaTeXdate=\pkgcls@targetdate
        \pkgcls@parse@date@arg{=v0.1}%
      \fi
    \fi
  }
%    \end{macrocode}
% Then we tell \LaTeXe\ where to find which release so that the package rollback
% code of \LaTeXe\ can do its thing.
%    \begin{macrocode}
\DeclareRelease{v0.1}{2020/10/10}{expkv-opt-2020-10-10.sty}
\DeclareCurrentRelease{v0.2}{2021/04/04}
%    \end{macrocode}
% 
% Start the package with the typical \LaTeX\ standards.
% 
% \begin{macro}{\ekvoVersion,\ekvoDate}
%   Store the package's version and date in two macros.
%    \begin{macrocode}
\newcommand*\ekvoVersion{1.1}
\newcommand*\ekvoDate{2024-12-16}
%    \end{macrocode}
% \end{macro}
% And we report who we are and what we need.
%    \begin{macrocode}
\ProvidesPackage{expkv-opt}
  [%
    \ekvoDate\space v\ekvoVersion\space
    parse class and package options with expkv%
  ]
\RequirePackage{expkv}
%    \end{macrocode}
%
%
% \subsection{Loop}
%
% \begin{macro}[internal]
%   {\ekvo@CurrentOption@loop,\ekvo@CurrentOption@loop@,\ekvo@end@loop}
%   We'll need some loop which can iterate over a comma separated list. The loop
%   is very basic and only works for commas of category~12. First we insert the
%   delimiters for the actual loop. The |\ekv@set@other| is necessary to get a
%   functional |\ekvmorekv| in this loop.
%    \begin{macrocode}
\protected\long\def\ekvo@CurrentOption@loop#1#2%
  {%
    \ekvo@CurrentOption@loop@#2\ekv@set@other\ekv@mark#1,\ekv@stop,\ekvo@tail
  }
%    \end{macrocode}
%   The actual loop checks whether the final element has been read and if so
%   ends the loop. Else blank elements are ignored, |\CurrentOption| is set and
%   the macro which parses the list elements called. Then call the next
%   iteration.
%    \begin{macrocode}
\long\def\ekvo@CurrentOption@loop@#1\ekv@set@other#2,%
  {%
    \ekv@gobble@from@mark@to@stop#2\ekvo@end@loop\ekv@stop
    \ekv@ifblank{#2}%
      {}%
      {%
        \edef\CurrentOption{\unexpanded\expandafter{\ekv@gobble@mark#2}}%
        #1{#2}%
      }%
    \ekvo@CurrentOption@loop@#1\ekv@set@other\ekv@mark
  }
\long\def\ekvo@end@loop#1\ekvo@tail{}
%    \end{macrocode}
% \end{macro}
%
%
% \subsection{Tests}
%
% \begin{macro}[internal]{\ekvo@ifx@TF,\ekvo@ifx@T,\ekvo@ifx@F}
%   We'll need branching |\ifx| tests so that user input containing unbalanced
%   \TeX\ ifs doesn't break (at least not because of us, everything else is the
%   fault of \LaTeXe).
%    \begin{macrocode}
\def\ekvo@ifx@TF#1#2{\ifx#1#2\ekv@fi@firstoftwo\fi\@secondoftwo}
\def\ekvo@ifx@T#1#2{\ifx#1#2\ekv@fi@firstofone\fi\@gobble}
\def\ekvo@ifx@F#1#2{\ifx#1#2\ekv@fi@gobble\fi\@firstofone}
%    \end{macrocode}
% \end{macro}
% 
% \begin{macro}[internal]{\ekvo@do@with@set,\ekvo@name,\ekvo@setname}
%   This test checks whether the \set\ is defined.  If it is we store it in
%   |\ekvo@setname| and set |\ekvo@name| to a short cut to get the \key's
%   callback name. Next we execute the code in |#2|, if the \set\ isn't defined
%   |#2| is gobbled.
%    \begin{macrocode}
\protected\def\ekvo@do@with@set#1#2%
  {%
    \ekvifdefinedset{#1}%
      {%
        \expandafter
        \let\expandafter\ekvo@name\csname\ekv@undefined@set{#1}\endcsname
        \def\ekvo@setname{#1}%
        #2%
      }%
      {\ekvo@err@undefined@set{#1}}%
  }
%    \end{macrocode}
% \end{macro}
%
%
% \subsection{Key handlers}
%
% \expkvo\ uses handlers specifying what happens if a parsed \key\ is defined or
% undefined.
%
% \begin{macro}[internal]
%   {\ekvo@handle@undefined@k@pkg,\ekvo@handle@undefined@kv@pkg}
%   The case for undefined keys in a local list of a package is easy, just throw
%   appropriate errors.
%    \begin{macrocode}
\protected\long\def\ekvo@handle@undefined@k@pkg#1%
  {%
    \ekv@ifdefined{\ekvo@name{#1}}%
      {\ekvo@err@value@required{#1}}%
      {\ekvo@err@undefined@key{#1}}%
  }
\def\ekvo@handle@undefined@kv@pkg#1#2%
  {%
    \ekv@ifdefined{\ekvo@name{#1}N}%
      {\ekvo@err@value@forbidden{#1}}%
      {\ekvo@err@undefined@key{#1}}%
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[internal]
%   {
%     \ekvo@addto@unused@one,\ekvo@addto@unused@two,
%     \ekvo@rmfrom@unused@one,\ekvo@rmfrom@unused@two
%   }
%   These macros will add or remove the |\CurrentOption| to or from the list of
%   unused global options. Since |\ekvo@do@unusedoptionlist| will have some
%   overhead before calling the list changing macro in filtering the current
%   option, we use an optimization here in that we check whether the list is
%   empty before calling the |rmfrom| function.
%    \begin{macrocode}
\long\def\ekvo@addto@unused@one#1{\ekvo@do@unusedoptionlist\ekvo@addnewto@list}
\long\def\ekvo@addto@unused@two#1#2{\ekvo@do@unusedoptionlist\ekvo@addnewto@list}
\long\def\ekvo@rmfrom@unused@one#1%
  {%
    \ekvo@ifx@F\@unusedoptionlist\@empty
      {\ekvo@do@unusedoptionlist\ekvo@rmfrom@list}%
  }
\long\def\ekvo@rmfrom@unused@two#1#2%
  {%
    \ekvo@ifx@F\@unusedoptionlist\@empty
      {\ekvo@do@unusedoptionlist\ekvo@rmfrom@list}%
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[internal]
%   {
%     \ekvo@do@unusedoptionlist,
%     \ekvo@prepare@unusedoption,
%     \ekvo@prepare@unusedoption@a,
%     \ekvo@prepare@unusedoption@b,
%     \ekvo@prepare@unusedoption@c
%   }
%   The way the new \LaTeXe\ kernel handles the unused option list changed. Now
%   not the entire |\CurrentOption| is listed, but just everything up to the
%   first equals sign, and spaces got zapped, doesn't matter whether the raw
%   option list gets used or not. So we have to zap spaces and remove everything
%   from the first equals sign onwards. The code used here will fail if the
%   current option contains an |\ekv@mark| or |\ekv@stop| before the first
%   equals sign (this seems rather unlikely).
%    \begin{macrocode}
\protected\def\ekvo@do@unusedoptionlist#1%
  {%
    \let\ekvo@unpreparedCurrentOption\CurrentOption
    \edef\CurrentOption
      {\expandafter\ekvo@prepare@unusedoption\CurrentOption=\ekv@mark}%
    #1\@unusedoptionlist
    \let\CurrentOption\ekvo@unpreparedCurrentOption
  }
\def\ekvo@prepare@unusedoption{\ekvo@prepare@unusedoption@a\@empty}
\def\ekvo@prepare@unusedoption@a#1%
  {%
    \long\def\ekvo@prepare@unusedoption@a##1=##2\ekv@mark
      {%
        \ekvo@prepare@unusedoption@b##1\ekv@stop
          \ekv@mark\ekvo@prepare@unusedoption@b
          #1\ekv@mark\ekvo@prepare@unusedoption@c
      }%
  }
\ekvo@prepare@unusedoption@a{ }
\long\def\ekvo@prepare@unusedoption@b#1 #2\ekv@mark#3{#3#1#2\ekv@mark#3}
\long\def\ekvo@prepare@unusedoption@c
    #1\ekv@stop
    \ekv@mark\ekvo@prepare@unusedoption@b\ekv@mark\ekvo@prepare@unusedoption@c
  {\unexpanded\expandafter{#1}}
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[internal]
%   {
%     \ekvo@set@handlers@local,\ekvo@set@handlers@global,
%     \ekvo@set@handlers@list
%   }
%   These macros are boring. They just set up the handlers to respect the rules
%   documented earlier.
%    \begin{macrocode}
\protected\def\ekvo@set@handlers@local
  {%
%    \end{macrocode}
%   In case a class has the same option list as the global class, and there are
%   already options in the unused options list, it should remove known keys
%   from the unused options list.
%    \begin{macrocode}
    \ifx\@currext\@clsextension
      \unless\ifx\@classoptionslist\relax
        \expandafter
        \ifx
            \csname @raw@opt@\@currname.\@currext\endcsname
            \@raw@classoptionslist
          \unless\ifx\@unusedoptionlist\@empty
            \let\ekvo@handle@defined@k\ekvo@rmfrom@unused@one
            \let\ekvo@handle@defined@kv\ekvo@rmfrom@unused@two
          \fi
        \fi
      \fi
    \fi
%    \end{macrocode}
%   The normal unused handlers for a class:
%    \begin{macrocode}
    \ekvo@if@need@handlers
      {%
        \ifx\@currext\@clsextension
          \ifx\@classoptionslist\relax
            \let\ekvo@handle@undefined@k\@gobble
            \let\ekvo@handle@undefined@kv\@gobbletwo
          \else
            \expandafter
            \ifx
                \csname @raw@opt@\@currname.\@currext\endcsname
                \@raw@classoptionslist
              \let\ekvo@handle@undefined@k\ekvo@addto@unused@one
              \let\ekvo@handle@undefined@kv\ekvo@addto@unused@two
            \else
              \let\ekvo@handle@undefined@k\@gobble
              \let\ekvo@handle@undefined@kv\@gobbletwo
            \fi
          \fi
        \else
          \let\ekvo@handle@undefined@k\ekvo@handle@undefined@k@pkg
          \let\ekvo@handle@undefined@kv\ekvo@handle@undefined@kv@pkg
        \fi
      }%
  }
\protected\def\ekvo@set@handlers@global
  {%
    \unless\ifx\@unusedoptionlist\@empty
      \let\ekvo@handle@defined@k\ekvo@rmfrom@unused@one
      \let\ekvo@handle@defined@kv\ekvo@rmfrom@unused@two
    \fi
    \ekvo@if@need@handlers
      {%
        \let\ekvo@handle@undefined@k\@gobble
        \let\ekvo@handle@undefined@kv\@gobbletwo
      }%
  }
\protected\def\ekvo@set@handlers@list
  {%
    \ekvo@if@need@handlers
      {%
        \let\ekvo@handle@undefined@k\@gobble
        \let\ekvo@handle@undefined@kv\@gobbletwo
      }%
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[internal]{\ekvo@if@need@handlers,\ekvo@dont@need@handlers}
%   If the user specifies handlers this macro will be let to
%   |\ekvo@dont@need@handlers|, which will act like |\@gobble| and also let it
%   to |\@firstofone| afterwards.
%    \begin{macrocode}
\let\ekvo@if@need@handlers\@firstofone
\protected\long\def\ekvo@dont@need@handlers#1%
  {%
    \let\ekvo@if@need@handlers\@firstofone
  }%
%    \end{macrocode}
% \end{macro}
%
% We have to set the default for the handlers of defined keys, because they
% don't necessarily get defined before a list is parsed.
%    \begin{macrocode}
\let\ekvo@handle@defined@k\@gobble
\let\ekvo@handle@defined@kv\@gobbletwo
%    \end{macrocode}
%
%
% \subsection{Processing list elements}
%
% \begin{macro}[internal]{\ekvo@process@common}
%   All the key processing frontend macros use the same basic structure. |#1|
%   will be a simple test, deciding whether the list will really be parsed or
%   not, |#3| will be the \set, and |#2| will be the individual code of the
%   frontend macro which should be executed if both the test in |#1| is true and
%   the \set\ is defined.
%    \begin{macrocode}
\protected\def\ekvo@process@common#1#2#3%
  {%
    #1{\ekvo@do@with@set{#3}{#2}}%
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[internal]{\ekvo@process@list}
%   This macro only expands the list holding macro and forwards it to the
%   loop macro.
%    \begin{macrocode}
\protected\def\ekvo@process@list#1%
  {%
    \expandafter\ekvo@CurrentOption@loop\expandafter{#1}\ekvo@parse
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[internal]
%   {
%     \ekvo@parse,
%     \ekvo@parse@kv,\ekvo@parse@kv@,\ekvo@expansion@kv,\ekvo@expansion@kv@,
%     \ekvo@parse@k, \ekvo@parse@k@, \ekvo@expansion@k, \ekvo@expansion@k@
%   }
%   This macro calls internals of \expkv\ such that the input is split at an
%   equals sign of category other.
%    \begin{macrocode}
\long\def\ekvo@parse#1%
  {%
    \ekv@eq@other#1\ekv@nil\ekv@mark\ekvo@parse@kv
      =\ekv@mark\ekvo@parse@k
  }
%    \end{macrocode}
%   If there was an equals sign, this will be called and remove the remainder of
%   the split. Afterwards the \expnotation\ is checked and possibly executed,
%   and the \kv\ pair is set. While reinsertion works via the |Rr|
%   \expansion-rule, it might affect the unused global option list.
%    \begin{macrocode}
\long\def\ekvo@parse@kv#1\ekv@stop#2\ekv@nil=\ekv@mark\ekvo@parse@k
  {\ekv@strip{#2}{\ekv@strip{#1}\ekvo@parse@kv@}}
\long\def\ekvo@parse@kv@#1#2%
  {%
    \ekv@ifexp{#1}%
      {\ekvo@expansion@kv{#2}}%
      {\ekvo@set@kv{#1}{#2}}%
  }
%    \end{macrocode}
%   Check for \expansion-rules was true, now we need to execute them, and use
%   the result in |\ekvo@set@kv|. Also we have to handle the key as if it was a
%   defined key if the |\r| \expansion-rule was found (which potentially removes
%   it from the list of global unused options).
%    \begin{macrocode}
\long\def\ekvo@expansion@kv#1#2#3#4%
  {%
    \ekv@expansion@rule@{#1}#2\ekv@mark\ekv@stop{#3}{}%
      {\ekvo@handle@defined@kv{#3}{#1}\ekvmorekv}%
    \ekvo@expansion@kv@
  }
\long\def\ekvo@expansion@kv@#1#2{\ekvo@set@kv{#1}{#2}}
%    \end{macrocode}
%   And basically the same two biggish steps, but for the case that no equals
%   sign is found.
%    \begin{macrocode}
\long\def\ekvo@parse@k#1\ekv@nil\ekv@mark\ekvo@parse@kv\ekv@stop\ekv@mark
  {\ekv@strip{#1}\ekvo@parse@k@}
\long\def\ekvo@parse@k@#1%
  {%
    \ekv@ifexp{#1}%
      \ekvo@expansion@k
      {\ekvo@set@k{#1}}%
  }
\long\def\ekvo@expansion@k#1#2#3%
  {%
    \ekv@expansion@rule@{#2}#1\ekv@mark\ekv@stop{}{}%
      {\ekvo@handle@defined@k{#2}\ekvmorekv}%
    \ekvo@expansion@k@
  }
\long\def\ekvo@expansion@k@#1#2{\ekvo@set@k{#2}}
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[internal]{\ekvo@set@k,\ekvo@set@kv}
%   These two macros check whether the key is defined and if so call the handler
%   for defined keys and execute the key, else the handler for undefined keys is
%   called.
%    \begin{macrocode}
\protected\def\ekvo@set@k#1%
  {%
    \ekv@ifdefined{\ekvo@name{#1}N}%
      {%
        \ekvo@handle@defined@k{#1}%
        \csname\ekvo@name{#1}N\endcsname
      }%
      {\ekvo@handle@undefined@k{#1}}%
  }
\protected\def\ekvo@set@kv#1#2%
  {%
    \ekv@ifdefined{\ekvo@name{#1}}%
      {%
        \ekvo@handle@defined@kv{#1}{#2}%
        \csname\ekvo@name{#1}\endcsname{#2}%
      }%
      {\ekvo@handle@undefined@kv{#1}{#2}}%
  }
%    \end{macrocode}
% \end{macro}
%
%
% \subsection{List variable helpers}
%
% \begin{macro}[internal]{\ekvo@addto@list}
%   This macro is rather simple. If the list to which the |\CurrentOption|
%   should be added is empty we can just let the list to the |\CurrentOption|.
%   Else we have to expand the list once and the |\CurrentOption| once.
%    \begin{macrocode}
\protected\def\ekvo@addto@list#1%
  {%
    \ekvo@ifx@TF#1\@empty
      {\let#1\CurrentOption}%
      {%
        \edef#1%
          {%
            \unexpanded\expandafter{#1},%
            \unexpanded\expandafter{\CurrentOption}%
          }%
      }%
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[internal]{\ekvo@addnewto@list}
%   This works just like |\ekvo@addto@list|, but it only adds elements which are
%   not yet part of the list, hence we check if the option is already in the
%   list.
%    \begin{macrocode}
\protected\def\ekvo@addnewto@list#1%
  {%
    \ekvo@ifx@TF#1\@empty
      {\let#1\CurrentOption}%
      {%
        \ekvo@if@in@list#1%
          {}%
          {%
            \edef#1%
              {%
                \unexpanded\expandafter{#1},%
                \unexpanded\expandafter{\CurrentOption}%
              }%
          }%
      }%
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[internal]
%   {\ekvo@if@in@list,\ekvo@if@in@list@,\ekvo@if@in@list@result}
%   This is a slow but robust test whether some option is already contained in a
%   list. Using argument gobbling logic would be faster, but that way wouldn't
%   be robust for elements containing |{|, |}|, or |#|. Just loop over the code
%   and compare each element, end early if a match is found.
%    \begin{macrocode}
\protected\def\ekvo@if@in@list#1%
  {%
    \ekvo@ifx@TF#1\@empty
      \@secondoftwo
      {%
        \let\ekvo@curropt\CurrentOption
        \let\ekvo@if@in@list@result\@secondoftwo
        \expandafter\ekvo@CurrentOption@loop\expandafter{#1}\ekvo@if@in@list@
        \let\CurrentOption\ekvo@curropt
        \ekvo@if@in@list@result
      }%
  }
\protected\long\def\ekvo@if@in@list@#1%
  {%
    \ekvo@ifx@T\ekvo@curropt\CurrentOption
      {%
        \let\ekvo@if@in@list@result\@firstoftwo
        \ekvo@end@loop
      }%
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[internal]{\ekvo@rmfrom@list,\ekvo@rmfrom@list@}
%   This works by looping over every list item and comparing it to
%   |\ekvo@curropt| which stores the real |\CurrentOption|. This is
%   comparatively slow, but works for items containing braces unlike what
%   \LaTeXe\ does. We could be faster for items not containing braces, though.
%    \begin{macrocode}
\protected\def\ekvo@rmfrom@list#1%
  {%
    \ekvo@ifx@F#1\@empty
      {%
        \let\ekvo@tmp@list\@empty
        \let\ekvo@curropt\CurrentOption
        \expandafter\ekvo@CurrentOption@loop\expandafter{#1}\ekvo@rmfrom@list@
        \let\CurrentOption\ekvo@curropt
        \let#1\ekvo@tmp@list
      }%
  }
\protected\long\def\ekvo@rmfrom@list@#1%
  {%
    \ekvo@ifx@F\CurrentOption\ekvo@curropt
      {\ekvo@addto@list\ekvo@tmp@list}%
  }
%    \end{macrocode}
% \end{macro}
%
%
% \subsection{Errors}
%
% \begin{macro}[internal]
%   {
%     \ekvo@err@undefined@key,\ekvo@err@value@required,
%     \ekvo@err@value@forbidden,\ekvo@err@undefined@set
%   }
%   Just some macros to throw errors in the few cases an error has to be thrown.
%    \begin{macrocode}
\protected\def\ekvo@err@undefined@key#1%
  {%
    \ekvo@pkg@cls@error
      {Undefined option `\detokenize{#1}'}%
      {The used option was not defined. Perhaps you misspelled it?}%
  }
\protected\def\ekvo@err@value@required#1%
  {%
    \ekvo@pkg@cls@error
      {Missing value for option `\detokenize{#1}'}%
      {The used option requires a value or you misspelled its name.}%
  }
\protected\def\ekvo@err@value@forbidden#1%
  {%
    \ekvo@pkg@cls@error
      {Unwanted value for option `\detokenize{#1}'}%
      {The used option doesn't support a value or you misspelled its name.}%
  }
\protected\def\ekvo@err@undefined@set#1%
  {%
    \PackageError{expkv-opt}%
      {Undefined set `#1'}%
      {The set for which you try to parse options isn't defined in expkv.}%
  }
\protected\def\ekvo@pkg@cls@error
  {%
    \ekvo@ifx@TF\@currext\@clsextension
      \ClassError
      \PackageError
        \@currname
  }
%    \end{macrocode}
% \end{macro}
%
%
% \subsection{User Interface}
%
% The user interface macros just put together the bits and pieces.
%
% \begin{macro}{\ekvoProcessOptions}
%   First we check if user-defined handlers were used. If that's the case we
%   need to store them and restore them for each list processor. Else just call
%   them sequentially.
%    \begin{macrocode}
\protected\def\ekvoProcessOptions#1%
  {%
    \ekvo@if@need@handlers
      {%
        \ekvoProcessGlobalOptions{#1}%
        \ekvoProcessLocalOptions {#1}%
        \ekvoProcessFutureOptions{#1}%
        \@gobbletwo
      }%
    \@firstofone
      {%
        \let\ekvo@tmpa\ekvo@handle@undefined@k
        \let\ekvo@tmpb\ekvo@handle@undefined@kv
        \ekvoUseUnknownHandlers\ekvo@tmpa\ekvo@tmpb
        \ekvoProcessGlobalOptions{#1}%
        \ekvoUseUnknownHandlers\ekvo@tmpa\ekvo@tmpb
        \ekvoProcessLocalOptions {#1}%
        \ekvoUseUnknownHandlers\ekvo@tmpa\ekvo@tmpb
        \ekvoProcessFutureOptions{#1}%
        \let\ekvo@tmpa\ekvo@undefined
        \let\ekvo@tmpb\ekvo@undefined
      }%
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\ekvoProcessLocalOptions}
%    \begin{macrocode}
\protected\def\ekvoProcessLocalOptions
  {%
    \ekvo@process@common
      {\ekv@ifdefined{@raw@opt@\@currname.\@currext}\@firstofone\@gobble}%
      {%
        \ekvo@set@handlers@local
        \expandafter
        \ekvo@process@list\csname @raw@opt@\@currname.\@currext\endcsname
        \AtEndOfPackage{\let\@unprocessedoptions\relax}%
      }%
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\ekvoProcessGlobalOptions}
%    \begin{macrocode}
\protected\def\ekvoProcessGlobalOptions
  {%
    \ekvo@process@common{\ekvo@ifx@F\@classoptionslist\relax}%
      {%
        \ekvo@set@handlers@global
        \ekvo@process@list\@raw@classoptionslist
        \let\ekvo@handle@defined@k\@gobble
        \let\ekvo@handle@defined@kv\@gobbletwo
      }%
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\ekvoProcessFutureOptions}
%   Parsing future options (without patching kernel internals) is only possible
%   with kernel versions after 2022-11-01.
%   The user macro needs to store the user-set handlers for later if
%   |\ekvoUseUnknownHandlers| was used. Then it stores the correct code to
%   process future options in the |opt@handler@| variable.
%    \begin{macrocode}
\IfFormatAtLeastTF{2022/11/01}
  {%
    \protected\def\ekvoProcessFutureOptions#1%
      {%
        \ekvo@if@need@handlers\@gobbletwo
        \@firstofone
          {%
            \expandafter\let
              \csname ekvo@future@undefined@k@\@currname.\@currext\endcsname
              \ekvo@handle@undefined@k
            \expandafter\let
              \csname ekvo@future@undefined@kv@\@currname.\@currext\endcsname
              \ekvo@handle@undefined@kv
          }%
        \protected\expandafter
        \xdef\csname opt@handler@\@currname.\@currext\endcsname
          {\ekv@unexpanded{\ekvo@future{#1}}}%
      }
%    \end{macrocode}
% \begin{macro}[internal]{\ekvo@future,\ekvo@set@handlers@future}
%   Key parsing is pretty similar to |\ekvoProcessLocalOptions|, only the
%   handlers are defined differently, namely it is checked whether they are
%   user-specified, if not we reuse the undefined handlers for packages (which
%   will throw an error if an unknown key is found).
%    \begin{macrocode}
    \protected\def\ekvo@future
      {%
        \ekvo@process@common
          {\ekv@ifdefined{@raw@opt@\@currname.\@currext}\@firstofone\@gobble}%
          {%
            \ekvo@set@handlers@future
            \expandafter\ekvo@process@list
              \csname @raw@opt@\@currname.\@currext\endcsname
            \AtEndOfPackage{\let\@unprocessedoptions\relax}%
          }%
      }
    \protected\def\ekvo@set@handlers@future
      {%
        \expandafter\let\expandafter
          \ekvo@handle@undefined@k
          \csname
            \ekv@ifdefined{ekvo@future@undefined@k@\@currname.\@currext}%
              {ekvo@future@undefined@k@\@currname.\@currext}%
              {ekvo@handle@undefined@k@pkg}%
          \endcsname
        \expandafter\let\expandafter
          \ekvo@handle@undefined@kv
          \csname
            \ekv@ifdefined{ekvo@future@undefined@kv@\@currname.\@currext}%
              {ekvo@future@undefined@kv@\@currname.\@currext}%
              {ekvo@handle@undefined@kv@pkg}%
          \endcsname
      }
%    \end{macrocode}
% \end{macro}
%    \begin{macrocode}
  }
  {%
    \protected\def\ekvoProcessFutureOptions#1%
      {%
        \PackageWarning{expkv-opt}%
          {LaTeX-kernel is too old to process future options.\@gobbletwo}%
        \protected\gdef\ekvoProcessFutureOptions##1{}%
      }%
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\ekvoProcessOptionsList}
%    \begin{macrocode}
\protected\def\ekvoProcessOptionsList#1%
  {%
    \ekvo@process@common{\ekvo@ifx@F#1\@empty}%
      {%
        \ekvo@set@handlers@list
        \ekvo@process@list#1%
      }%
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\ekvoUseUnknownHandlers}
% \begin{macro}[internal]{\ekvoUseUnknownHandlers@n,\ekvoUseUnknownHandlers@s}
%    \begin{macrocode}
\protected\def\ekvoUseUnknownHandlers
  {%
    \let\ekvo@if@need@handlers\ekvo@dont@need@handlers
    \@ifstar\ekvoUseUnknownHandlers@s\ekvoUseUnknownHandlers@n
  }
\protected\def\ekvoUseUnknownHandlers@s
  {%
    \long\def\ekvo@handle@undefined@k##1%
      {%
        \ekv@ifdefined{\ekvo@name{}uN}%
          {%
            \csname\ekvo@name{}uN\expandafter\endcsname\expandafter
              {\detokenize{##1}}{##1}%
          }%
          {}%
      }%
    \long\def\ekvo@handle@undefined@kv##1##2%
      {%
        \ekv@ifdefined{\ekvo@name{}u}%
          {%
            \csname\ekvo@name{}u\ekv@expanded{\endcsname
              {\ekv@unexpanded{##2}}{\detokenize{##1}}}{##1}%
          }%
          {}%
      }%
  }
\protected\def\ekvoUseUnknownHandlers@n#1#2%
  {%
    \let\ekvo@handle@undefined@k#1\relax
    \let\ekvo@handle@undefined@kv#2\relax
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% All user interface macros should be only used in the preamble.
%    \begin{macrocode}
\@onlypreamble\ekvoProcessOptions
\@onlypreamble\ekvoProcessLocalOptions
\@onlypreamble\ekvoProcessGlobalOptions
\@onlypreamble\ekvoProcessFutureOptions
\@onlypreamble\ekvoProcessOptionsList
\@onlypreamble\ekvoUseUnknownHandlers
%    \end{macrocode}
% 
% \gobbledocstriptag
%</pkg>