我是 LaTeX 新手。我学习并使用article
类。我很难学习使用keyval
包:
我尝试编写\macro
仅使用一个强制参数的宏,用户可以在其中提供键值列表。
可能的键应该是keyA
和keyB
。
keyA
可以取值 true/yes/on/1(是分支)和 false/no/off/0(无分支)。keyA
默认应该是“是”分支。
我想keyB
取一个任意值,并将该值附加到表示是分支的宏的括号之间\foo
,keyA
并将该值附加到表示否分支的宏的方括号之间\bar
。keyA
除此之外,
我希望每次调用时keyA
都必须在键值列表中,并且它们只能在键值列表中出现一次 - 更频繁地设置其中一个键或根本不设置 → 错误消息。keyB
\macro
我不知道该怎么做,因为当\setkeys
逐个处理键和值对时,不知道其他键值对后面跟着什么,并且键值对的顺序也没有明确规定。
我尝试了这个但没有成功:
\documentclass{article}
\usepackage{keyval}
\def\foo{}
\def\bar{}
\newif\ifkeyAtrue\keyAtruefalse
\makeatletter
\define@key{MyFamily}{keyA}[true]{\keyAtruetrue}
\define@key{MyFamily}{keyB}{
\ifkeyAtrue
\def\foo{\foo(#1)}
\else
\def\bar{\bar[#1]}
\fi
}
\makeatother
\newcommand\macro[1]{
\setkeys{MyFamily}{#1}
\keyAtruefalse
}
\begin{document}
% This almost works but there is '\foo in \foo'-recursion.
\macro{keyA=true,keyB=first value}
\show\foo\show\bar
% This almost works but there is '\foo in \foo'-recursion and
% '\bar in \bar'-recursion
\macro{keyB=second value}
\show\foo\show\bar
% This dos not work at all. There is recursions and added to \foo, not to \bar.
\macro{keyA=false,keyB=third value}
\show\foo\show\bar
\end{document}
谢谢你的帮助。
答案1
一些与问题无关但可能有用的说法:
在逐行和逐字符读取/处理 .tex 输入文件时,(La)TeX 通常会先删除输入行右端的所有空格字符 (ASCII 32),然后在该输入行的右端附加一个回车符 (ASCII 13)。这是因为整数参数
\endlinechar
通常具有值 13。然后 (La)TeX 将逐个字符地“查看”该行输入,并据此将该行作为一组指令,用于将标记 (字符标记和控制序列标记) 附加到标记流。回车符通常具有类别代码 5(行尾)。在标记化花括号 ({
或}
) 后,(La)TeX 的读取装置通常处于状态 M(行中)。 (一般来说,(La)TeX 的读取装置在对控制符号标记(除控制空间外;控制符号标记是名称由单个字符组成的控制序列标记,该字符不具有类别代码 11(字母))或除空格标记以外的字符标记进行标记后,将处于状态 M。)当 (La)TeX 在 M 状态下遇到类别代码为 5(行尾)的字符时,(La)TeX 会将空格标记(类别代码为 10(空格)和字符代码为 32 的字符标记)附加到标记流中。
在(受限)水平模式下,这样的空格标记可能会产生可见的水平粘连。因此:确保输入行(其中在读取和标记化过程中应附加到标记流的最后一项是控制符号标记(控制序列标记,其名称由单个字符组成,该字符不是类别代码 11(字母))或字符标记)以注释字符(
%
)结尾,以防这种水平粘连是不可取的。
根据经验法则,确保输入行(其中应附加到标记流的最后一项是一些括号标记({
类别代码 1 的 -character-token(开始组)或}
类别代码 2 的 -character-token(结束组)))以注释字符(%
)结尾。\bar
在 LaTeX2e 中已经定义。我建议不要覆盖它。因此在下面的示例中,对于相应宏的名称,我使用全大写变体\FOO
和\BAR
。
与该问题相关的一些评论可能会有用:
使用 keyval 包,您不能让 keyB 底层的“函数”依赖于 keyA 的值/依赖于执行 keyA 底层的“函数”的结果。
正如你自己所说:
这些“功能”执行的顺序是不可预测的。
同样,这些“功能”是否会被执行或执行不止一次也是不可预测的。
相反,您可以定义一个具有底层“函数”的键系列来设置一些标志(这是一种存储真/假值或 0/1 值的方法)并将在评估标志时/之后要使用的其他值存储为宏。
然后,您可以定义一个宏,用于\setkeys
设置标志和存储值,然后评估标志以打印错误消息,如果没有打印任何错误消息,则相应地处理存储的值。
在下面的例子中,我使用通过 创建的开关作为标志\newif
。我这样做是因为在这个例子中,我不希望 LaTeX 加载大量的包。对于设置标志,包旗帜和位集,均为 Heiko Oberdiek 的作品,您可能会感兴趣。
处理逗号分隔的键值列表就是处理宏参数。
括号需要与宏参数匹配。
因此,我假设 keyB 的“任意值”是括号匹配的标记序列。;-)
当将这种(几乎)任意的标记序列存储为宏时,您需要确保哈希值(#
)在存储过程中加倍。原因是:您可以通过扩展这些宏来检索这些标记序列,而在宏扩展期间,两个哈希值(##
)将合并为一个,这意味着在宏扩展期间哈希值的数量将减半。
为了实现哈希值加倍,可以利用以下事实:当\the
在 期间传递令牌寄存器的内容时\edef
,其中的哈希值将加倍。
在下面的示例中,\toks@
根据模式使用了LaTeX 2e 的保留暂存令牌寄存器。\toks@{⟨tokens⟩}...\edef\macro{\the\toks@}
如果有 e-TeX 扩展可用,你可以 按照以下方式操作\edef\macro{\unexpanded{⟨tokens⟩}}
菲利佩·奥莱尼克\unexpanded
在 期间执行时也会使哈希值加倍\edef
。
您还需要根据短语/根据作为 keyA 的值提供的标记序列进行分叉:
在下面的示例中,您可以找到\DetectYesNo
通过分隔的宏参数进行分叉的方法。
您希望将(几乎)任意的内容附加到宏的定义文本中\foo
。
当这样做的时候,你需要确保 的新的定义文本还包含通过顶层扩展\foo
形成 的旧定义文本的标记。\foo
\foo
你可能会想这么做:
\expandafter\def\expandafter\foo\expandafter{\foo⟨tokens to append⟩}
但你需要小心使用这种方法,因为在扩展时哈希值的数量将减半\foo
。这可能会导致问题。
例如,如果\foo
通过以下方式定义
\def\foo{%
\def\bas##1{argument of bas: ##1.}%
}
你也是:
\expandafter\def\expandafter\foo\expandafter{%
\foo
\def\bat##1{argument of bat: ##1.}%
}
,那么这不会产生
\def\foo{%
\def\bas##1{argument of bas: ##1.}%
\def\bat##1{argument of bat: ##1.}%
}
,但会产生:
\def\foo{%
\def\bas#1{argument of bas: #1.}%
\def\bat##1{argument of bat: ##1.}%
}
这将出现问题,因此会产生一条错误消息,因为\foo
不处理参数,而\foo
定义文本包含#1
。
为了实现哈希加倍,您可以再次\the
在标记寄存器上应用 -expansion(没有 e-TeX 扩展)或\unexpanded
(这需要 e-TeX 扩展)在 期间\edef
,但这次您需要将其与 结合起来\expandafter
以获得 的扩展\foo
:
如果没有 e-TeX 扩展,使用 LaTeX 2e 的临时标记寄存器\toks@
,您可以执行以下操作:
\def\foo{%
\def\bas##1{argument of bas: ##1.}%
}%
% Now let's apply \expandafter for obtaining the expansion of `\foo`:
\toks@\expandafter{%
\foo
\def\bat#1{argument of bat: #1.}%
}%
\edef\foo{\the\toks@}%
\show\foo
但是暂存令牌寄存器\toks@
是为 LaTeX 2e 内核保留的。这意味着在执行内核中定义的任何令牌之前,应该重置它。这可以通过添加一些\exchange
技巧来实现:
\long\def\exchange#1#2{#2#1}%
\def\foo{%
\def\bas##1{argument of bas: ##1.}%
}%
\expandafter\exchange\expandafter{%
\expandafter\toks@\expandafter{\the\toks@}%
}{%
% Now let's apply \expandafter for obtaining the expansion of `\foo`:
\toks@\expandafter{%
\foo
\def\bat#1{argument of bat: #1.}%
}%
\edef\foo{\the\toks@}%
}%
\show\foo
当 e-TeX 扩展可用时,你可以使用 和 的组合\edef
,\expandafter
如\unexpanded
Phelype Oleinik 的回答:
\def\foo{%
\def\bas##1{argument of bas: ##1.}%
}%
\edef\foo{%
\unexpanded\expandafter{%
\foo
\def\bat#1{argument of bat: #1.}%
}%
}%
\show\foo
\documentclass{article}
\usepackage{keyval}
\makeatletter
%%----------------------------------------------------------------------
\newcommand\UD@firstofone[1]{#1}%
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@Exchange[2]{#2#1}%
%%----------------------------------------------------------------------
%% Check whether argument is empty:
%%......................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is empty>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is not empty>}%
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
\newcommand\UD@CheckWhetherNull[1]{%
\romannumeral0\expandafter\UD@secondoftwo\string{\expandafter
\UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
\UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{%
\expandafter\UD@secondoftwo\string}\expandafter\expandafter
\UD@firstoftwo{ }{}\UD@secondoftwo}{\expandafter\expandafter
\UD@firstoftwo{ }{}\UD@firstoftwo}%
}%
%%
%% A variant using e-TeX extensions could be:
%%
%%\newcommand\UD@CheckWhetherNull[1]{%
%% \romannumeral0\if\relax\detokenize{#1}\relax
%% \expandafter\UD@firstoftwo\else\expandafter\UD@secondoftwo\fi
%% {\UD@firstoftwo{\expandafter}{} \UD@firstoftwo}%
%% {\UD@firstoftwo{\expandafter}{} \UD@secondoftwo}%
%%}%
%%----------------------------------------------------------------------
%% \AppendTokensToMacro{tokens}{\macro}%
%% Appends tokens to definition text of \macro.%
%% (\toks@ is a reserved scratch-token-register in LaTeX 2e.
%% Thus when using it it must be ensured to reset it afterwards.)
%%----------------------------------------------------------------------
\newcommand\AppendTokensToMacro[2]{%
\expandafter\UD@Exchange\expandafter{%
\expandafter\toks@\expandafter{\the\toks@}%
}{%
\toks@\expandafter{#2#1}%
\edef#2{\the\toks@}%
}%
}%
%%
%% A variant using e-TeX extensions could be:
%%
%%\newcommand\AppendTokensToMacro[2]{%
%% \edef#2{\unexpanded\expandafter{#2#1}}%
%%}%
%%----------------------------------------------------------------------
%% Macros \FOO and \BAR
%%----------------------------------------------------------------------
\newcommand*\FOO{}%
\newcommand*\BAR{}%
%%----------------------------------------------------------------------
%% Flags / \if-switches
%%----------------------------------------------------------------------
%% Flag: Is there a need to print an error-message about keyA not
%% being set?
\newif\ifDeliverErrMsgKeyANotProvided
\DeliverErrMsgKeyANotProvidedtrue
%% Flag: Is there a need to print an error-message about keyB not
%% being set?
\newif\ifDeliverErrMsgKeyBNotProvided
\DeliverErrMsgKeyBNotProvidedtrue
%% Flag: Is there a need to print an error-message about keyA
%% being provided multiple times?
\newif\ifDeliverErrMsgKeyAProvidedSeveralTimes
\DeliverErrMsgKeyAProvidedSeveralTimesfalse
%% Flag: Is there a need to print an error-message about keyB
%% being provided multiple times?
\newif\ifDeliverErrMsgKeyBProvidedSeveralTimes
\DeliverErrMsgKeyBProvidedSeveralTimesfalse
%% Flag: Is there a need to print an error-message about keyA
%% having a value which is not in the true/false-spectrum?
\newif\ifDeliverErrMsgKeyANeitherTrueNorFalse
\DeliverErrMsgKeyANeitherTrueNorFalsefalse
%% Flag: Has keyA the value true?
\newif\ifKeyAsValueIsTrue
\KeyAsValueIsTruetrue
%%----------------------------------------------------------------------
%% Place-holders for values that are to be used after flag-evaluation:
%%----------------------------------------------------------------------
\newcommand*\MyKeyBvalue{}%
%%----------------------------------------------------------------------
%% Error-messages:
%%----------------------------------------------------------------------
%% \PreambleMacroError
%%......................................................................
%% This macro takes three arguments:
%% A macro name. An error message. The help information.
%% It displays the error message, and sets the error help (the result of
%% typing h to the prompt).
%%----------------------------------------------------------------------
\newcommand*\PreambleMacroError[3]{%
\GenericError{%
\space\space\space\@spaces\@spaces\@spaces
}{%
LaTeX Error: Inapproriate usage of macro \string#1\on@line.\MessageBreak
(\string#1 is defined in the document's preamble.)\MessageBreak
Problem: #2%
}{%
Have a look at the comments in the preamble of this document.%
}{#3}%
}%
%%----------------------------------------------------------------------
%% Error-message in case a flag-key does not have a value of the
%% true/false-spectrum:
%%----------------------------------------------------------------------
\newcommand\DeliverErrMsgKeyNeitherTrueNorFalse[1]{%
\PreambleMacroError{\macro}{Invalid value for #1}%
%\PackageError{MyPackage}{\string\macro: Invalid value for #1\on@line}%
%\@latex@error{\string\macro: Invalid value for #1\on@line}%
{%
#1 must have one of the following values:%
\MessageBreak true/yes/on/1 or false/no/off/0.%
}%
}%
%%----------------------------------------------------------------------
%% Error-message in case a mandatory key is not set at all:
%%----------------------------------------------------------------------
\newcommand\DeliverErrMsgKeyNotProvided[1]{%
\PreambleMacroError{\macro}{Setting for #1 is missing}%
%\PackageError{MyPackage}{\string\macro: Setting for #1 is missing\on@line}%
%\@latex@error{\string\macro: Setting for #1 is missing\on@line}%
{%
Setting #1 cannot be omitted.%
}%
}%
%%----------------------------------------------------------------------
%% Error-message in case a keys is set more times than once:
%%----------------------------------------------------------------------
\newcommand\DeliverErrMsgKeyProvidedSeveralTimes[1]{%
\PreambleMacroError{\macro}{More than one value for #1}%
%\PackageError{MyPackage}{\string\macro: More than one value for #1\on@line}%
%\@latex@error{\string\macro: More than one value for #1\on@line}%
{%
For the sake of unambiguity provide a value for #1 exactly once.%
}%
}%
%%----------------------------------------------------------------------
%% \DetectYesNo detects whether value is either one of
%% true/yes/on/1 or one of false/no/off/0
%% \DetectYesNo{<value>}%
%% {<tokens if value neither is "yes" nor is "no">}%
%% {<tokens if value is "yes">}%
%% {<tokens if value is "no">}
%%----------------------------------------------------------------------
%% Check whether argument contains no exclamation mark which is not
%% nested in braces:
%%......................................................................
%% \UD@CheckWhetherNoExclam{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that
%% argument contains no exclamation mark>}%
%% {<Tokens to be delivered in case that
%% argument contains exclamation mark>}%
%%
\newcommand\UD@GobbleToExclam{}\long\def\UD@GobbleToExclam#1!{}%
\newcommand\UD@CheckWhetherNoExclam[1]{%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@GobbleToExclam#1!}%
}%
\newcommand\TrueFalseFork{}
\long\def\TrueFalseFork#1!!true!yes!on!1!false!no!off!0!#2#3!!!!{#2}%
\newcommand\DetectYesNo[1]{\lowercase{\InnerDetectYesNo{#1}}}%
\newcommand\InnerDetectYesNo[4]{%
\romannumeral0\UD@CheckWhetherNoExclam{#1}{%
\TrueFalseFork!#1!true!yes!on!1!false!no!off!0!{ #2}%<-case #1 is empty/has no tokens
!!#1!yes!on!1!false!no!off!0!{ #3}%<-case #1 = true
!!true!#1!on!1!false!no!off!0!{ #3}%<-case #1 = yes
!!true!yes!#1!1!false!no!off!0!{ #3}%<-case #1 = on
!!true!yes!on!#1!false!no!off!0!{ #3}%<-case #1 = 1
!!true!yes!on!1!#1!no!off!0!{ #4}%<-case #1 = false
!!true!yes!on!1!false!#1!off!0!{ #4}%<-case #1 = no
!!true!yes!on!1!false!no!#1!0!{ #4}%<-case #1 = off
!!true!yes!on!1!false!no!off!#1!{ #4}%<-case #1 = 0
!!true!yes!on!1!false!no!off!0!{ #2}%<-case #1 something else without exclamation mark
!!!!%
}{ #2}%<-case #1 = something else with exclamation-mark.
}%
%%----------------------------------------------------------------------
%% Use the keys of the family MyFamily to change flags and to
%% save values that are to be used later
%%----------------------------------------------------------------------
\define@key{MyFamily}{keyA}[true]{%
\DeliverErrMsgKeyANotProvidedfalse
\define@key{MyFamily}{keyA}[true]{%
\DeliverErrMsgKeyAProvidedSeveralTimestrue
}%
\DetectYesNo{#1}%
{\DeliverErrMsgKeyANeitherTrueNorFalsetrue}%
{\KeyAsValueIsTruetrue}%
{\KeyAsValueIsTruefalse}%
}%
\define@key{MyFamily}{keyB}{%
\DeliverErrMsgKeyBNotProvidedfalse
\define@key{MyFamily}{keyB}{%
\DeliverErrMsgKeyBProvidedSeveralTimestrue
}%
\AppendTokensToMacro{#1}{\MyKeyBvalue}%
}%
%%----------------------------------------------------------------------
%% Now the macro with evaluation and error-messages ad nauseam ;-) :
%%----------------------------------------------------------------------
\newcommand\macro[1]{%
\begingroup
\setkeys{MyFamily}{#1}%
%
% Print Error-Messages if necessary:
%
\ifDeliverErrMsgKeyANeitherTrueNorFalse\DeliverErrMsgKeyNeitherTrueNorFalse{keyA}\fi
\ifDeliverErrMsgKeyANotProvided\DeliverErrMsgKeyNotProvided{keyA}\fi
\ifDeliverErrMsgKeyBNotProvided\DeliverErrMsgKeyNotProvided{keyB}\fi
\ifDeliverErrMsgKeyAProvidedSeveralTimes\DeliverErrMsgKeyProvidedSeveralTimes{keyA}\fi
\ifDeliverErrMsgKeyBProvidedSeveralTimes\DeliverErrMsgKeyProvidedSeveralTimes{keyB}\fi
%
% Perform the adding to \FOO or \BAR in case no error-messages were
% printed:
%
\ifDeliverErrMsgKeyANotProvided\expandafter\UD@secondoftwo\else\expandafter\UD@firstofone\fi{%
\ifDeliverErrMsgKeyBNotProvided\expandafter\UD@secondoftwo\else\expandafter\UD@firstofone\fi{%
\ifDeliverErrMsgKeyAProvidedSeveralTimes\expandafter\UD@secondoftwo\else\expandafter\UD@firstofone\fi{%
\ifDeliverErrMsgKeyBProvidedSeveralTimes\expandafter\UD@secondoftwo\else\expandafter\UD@firstofone\fi{%
\ifDeliverErrMsgKeyANeitherTrueNorFalse\expandafter\UD@secondoftwo\else\expandafter\UD@firstoftwo\fi{%
\ifKeyAsValueIsTrue\KeyAsValueIsTruetrue\expandafter\UD@firstoftwo\else\expandafter\UD@secondoftwo\fi{%
\expandafter\endgroup\expandafter\AppendTokensToMacro\expandafter{\expandafter(\MyKeyBvalue)}{\FOO}%
}{%
\expandafter\endgroup\expandafter\AppendTokensToMacro\expandafter{\expandafter[\MyKeyBvalue]}{\BAR}%
}%
}%
}%
}%
}%
}%
\endgroup
}%
\makeatother
\begin{document}
\noindent
\verb|\macro{keyA=true, keyB=Value in first call}| - now you have:\\
\macro{keyA=true, keyB=Value in first call}%
\texttt{\string\FOO: \meaning\FOO\\\string\BAR: \meaning\BAR\\}%
%
\null\hrulefill\null\\
\verb|\macro{keyA=yes, keyB=Value in second call}| - now you have:\\
\macro{keyA=yes, keyB=Value in second call}%
\texttt{\string\FOO: \meaning\FOO\\\string\BAR: \meaning\BAR\\}%
%
\null\hrulefill\null\\
\verb|\macro{keyA=0, keyB=Value in third call}| - now you have:\\
\macro{keyA=0, keyB=Value in third call}%
\texttt{\string\FOO: \meaning\FOO\\\string\BAR: \meaning\BAR\\}%
%
\null\hrulefill\null\\
\verb|\macro{keyA=Off, keyB=Value in fourth call}| - now you have:\\
\macro{keyA=Off, keyB=Value in fourth call}%
\texttt{\string\FOO: \meaning\FOO\\\string\BAR: \meaning\BAR\\}%
%
\null\hrulefill\null\\
\verb|\macro{keyA, keyB=Value in fifth call}| - now you have:\\
\macro{keyA, keyB=Value in fifth call}%
\texttt{\string\FOO: \meaning\FOO\\\string\BAR: \meaning\BAR\\}%
%
% Let's trigger some error-messages:
%
%\null\hrulefill\null\\
%\verb|\macro{keyA=woozle, }| - now you have:\\
%\macro{keyA=woozle, }%
%\texttt{\string\FOO: \meaning\FOO\\\string\BAR: \meaning\BAR\\}%
%
%\null\hrulefill\null\\
%\verb|\macro{keyA=1, keyA=false, keyB=Value in sixth call}| - now you have:\\
%\macro{keyA=1, keyA=false, keyB=Value in sixth call}%
%\texttt{\string\FOO: \meaning\FOO\\\string\BAR: \meaning\BAR\\}%
\end{document}
答案2
您提到的递归\foo
之所以会发生,是因为当您执行 时,内部不会展开。如果您使用 ,TeX 很快就会爆炸:。\foo
\def\foo{\foo(something else)}
\foo
\foo
TeX capacity exceeded, sorry [input stack size=5000]
要\foo
包含原有的\foo
内容以及新内容,您可以使用\edef
( e
xpand def
) 和\unexpanded
,如下所示:
\edef\foo{\unexpanded\expandafter{\foo(more stuff)}}
那么\foo
将是(something else)(more stuff)
。
现在,keyA=false
由于您定义了以下内容,因此不起作用keyA
:
\define@key{MyFamily}{keyA}[true]{\keyAtruetrue}
因此,无论你传递哪个值keyA
,最后它都会执行\keyAtruetrue
,也就是不是你想要什么。
我\teststring{<str a>}{<str b>}{<true>}{<false>}
为您定义了一个简单的宏,它比较两个字符串<str a>
,<str b>
然后返回<true>
它们是否相等或<false>
不相等。然后我更改了的定义以keyA
应对所有可能性true/yes/on/1 (yes-branch)
和false/no/off/0 (no-branch)
。
%
我还在行尾添加了一些以避免出现多余的空格。
而且,正如 Ulrich Diez 在他的回答中提到的那样(我完全忽略了),您在使用时应该小心,\def
因为您可以在不知情的情况下覆盖命令。我将其替换\bar
为\rab
(我没有创造力 :)
运行代码时我在终端上得到了这个:
> \foo=macro:
->(first value).
l.51 \show\foo
\show\rab
?
> \rab=macro:
->.
l.51 \show\foo\show\rab
?
> \foo=macro:
->(first value).
l.55 \show\foo
\show\rab
?
> \rab=macro:
->[second value].
l.55 \show\foo\show\rab
?
> \foo=macro:
->(first value).
l.59 \show\foo
\show\rab
?
> \rab=macro:
->[second value][third value].
l.59 \show\foo\show\rab
?
完整代码:
\documentclass{article}
\usepackage{keyval}
\def\foo{}
\def\rab{}
\newif\ifkeyAtrue\keyAtruefalse
\makeatletter
\def\teststring#1#2{%
\edef\@tempa{\detokenize{#1}}%
\edef\@tempb{\detokenize{#2}}%
\ifx\@tempa\@tempb
\@valid@keytrue
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
}
\newif\if@valid@key
\define@key{MyFamily}{keyA}[true]{%
\@valid@keyfalse
\teststring{#1}{true}{\keyAtruetrue}
{\teststring{#1}{yes}{\keyAtruetrue}
{\teststring{#1}{on}{\keyAtruetrue}
{\teststring{#1}{1}{\keyAtruetrue}
{}}}}%
\unless\if@valid@key
\teststring{#1}{false}{\keyAtruefalse}
{\teststring{#1}{no}{\keyAtruefalse}
{\teststring{#1}{off}{\keyAtruefalse}
{\teststring{#1}{0}{\keyAtruefalse}
{}}}}%
\fi
\unless\if@valid@key
\PackageError{}{Invalid option `#1' for keyA}{}
\fi
}
\define@key{MyFamily}{keyB}{% <--
\ifkeyAtrue
\edef\foo{\unexpanded\expandafter{\foo(#1)}}% <--
\else
\edef\rab{\unexpanded\expandafter{\rab[#1]}}% <--
\fi
}
\makeatother
\newcommand\macro[1]{% <--
\setkeys{MyFamily}{#1}% <--
\keyAtruefalse
}
\begin{document}
% This almost works but there is '\foo in \foo'-recursion.
\macro{keyA=true,keyB=first value}
\show\foo\show\rab
% This almost works but there is '\foo in \foo'-recursion and
% '\rab in \rab'-recursion
\macro{keyB=second value}
\show\foo\show\rab
% This dos not work at all. There is recursions and added to \foo, not to \rab.
% \tracingall
\macro{keyA=false,keyB=third value}
\show\foo\show\rab
\end{document}