0

I am working with the xkeyval package.

Sometimes I want to check by hand if a key is contained in a list of keys.

That is, I want a if-command like the following:

\contains@key{<family>}{<key value pairs>}{<key>}
{<execute if <key> is contained in <key value pairs>>}
{<execute if key is not contained>}

Are there already commands in xkeyval to achieve this?

2 Answers 2

2

The following defines a fully expandable macro that checks whether the key is in the list. It uses expkv to loop over the list of key=value pairs and pdftexcmds to make \pdf@strcmp available in all engines. The key names are not expanded in any way in this process. The macro needs exactly two steps of expansion to do its job.

\documentclass{article}

\usepackage{expkv}
\usepackage{pdftexcmds}

\makeatletter
\newcommand\ifkeyinlistTF[1]
  {\romannumeral\expandafter\ifkeyinlistTF@\detokenize{#1}\relax}
\long\def\ifkeyinlistTF@#1\relax#2%
  {%
    \ekvparse{\ifkeyinlistTF@k{#1}}{\ifkeyinlistTF@v{#1}}{#2}%
    \ifkeyinlistTF@false
  }
\newcommand\ifkeyinlistTF@k[2]
  {\if0\pdf@strcmp{#1}{\unexpanded{#2}}\ifkeyinlistTF@true\fi}
\newcommand\ifkeyinlistTF@v[3]
  {\if0\pdf@strcmp{#1}{\unexpanded{#2}}\ifkeyinlistTF@true\fi}
\newcommand\ifkeyinlistTF@false[2]{\z@#2}
\long\def\ifkeyinlistTF@true\fi#1\ifkeyinlistTF@false#2#3{\fi\z@#2}
\makeatother

\begin{document}
\edef\foo
  {%
    \ifkeyinlistTF{key}{a,b=c,d,e=f,g,h=i}{.}{a}%
    \ifkeyinlistTF{key}{a,b=c,d,e=f,g,h=i,key}{i}{.}%
    \ifkeyinlistTF{key}{a,b=c,d,e=f,g,h=i,keya}{.}{q}%
    \ifkeyinlistTF{key}{a,b=c,d,e=f,g,h=i,key=a}{u}{.}%
    \ifkeyinlistTF{key}{a,b=c,d,e=f,g,h=i,key=}{i}{.}%
    \ifkeyinlistTF{key}{a,b=c,d,e=f,g,h=i, key=}{t}{.}%
    \ifkeyinlistTF{\key}{a,b=c,d,e=f,g,h=i, \key =}{a}{.}%
  }
\texttt{\meaning\foo}
\end{document}

Output:

enter image description here

3
  • What is meant by a fully expandable macro and what is the advantage of having such one?
    – aiquita
    Mar 21, 2022 at 17:32
  • 1
    @aiquita see e.g. tex.stackexchange.com/questions/66118/… for some explanations.
    – Skillmon
    Mar 21, 2022 at 20:21
  • @aiquita also, my code is 7 times faster on the key=value lists used in my answer (with the last one changed from using \key to key). And my code is alignment safe, whereas the xkeyval internals are not (oh oh, next complicated TeX-term; explanation: Your code can't be used inside tabular and friends if either the key or one of the values contains & outside of an additional set of braces).
    – Skillmon
    Mar 22, 2022 at 14:17
1

I figured out myself how to achieve a command that takes a key, a list of key-value pairs, and executes some of two code blocks depending on whether the key occurs in the list.

The solution is build upon low level commands of the xkeyval package, thus well-suited if you already use the package. At least I use the command along with setting the keys in xkeyval.

For comparision I use the ifthen package which may be replaced by any other sufficient mechanism.

The solution is as follows:

% !TEX TS-program = pdflatex
\documentclass{article}

\usepackage{xkeyval}
\usepackage{ifthen}

\begin{document}
    
    
    \makeatletter
        
        \newcommand{\if@contains@key}[4]{%
            \XKV@checksanitizea{#2}\XKV@resb%
            \newif\ifkey@found\key@foundfalse% set to true right after the key was found, otherwise remains false
            \XKV@for@o\XKV@resb\XKV@tempa{% loop over the key-value pairs
                % the following two lines extract the key from the pair and remove trailing whitespace (same solution as in \setkeys of xkeyval)
                \expandafter\XKV@g@tkeyname\XKV@tempa=\@nil\XKV@tempa%
                \expandafter\KV@@sp@def\expandafter\XKV@tempa\expandafter{\XKV@tempa}%
                \ifthenelse{\equal{#1}{\XKV@tempa}}%
                    {\key@foundtrue}{}%
            }%
            \ifkey@found%
                #3%
            \else%
                #4%
            \fi%
        }
        
    
        \if@contains@key{welt}{hallo={}, welt={}}
            {Hallo}
            {Welt}
        
    \makeatother

\end{document}

Note that the solution works on the list of key-value pairs directly such that it does not check whether the key belongs to a certain family of keys.

1
  • 1
    In your code, move \newif\ifkey@found outside of the code of \if@contains@key (but keep the \key@foundfalse). You don't need to redefine the conditional on each call of your macro.
    – Skillmon
    Mar 22, 2022 at 6:19

You must log in to answer this question.

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