keyval - function for keyB should act dependent on value of keyA - how to do this?Test if token is a control sequenceThe definition of key default value by keyval packageUse alternate cite keys depending on document versionA list of pairs in pgfkeysExpansion problems with pgfkeysXparse macro with optional parameter breaks value for PGF foreachHow to define a function with optional key-value argumentsGenerating a table from key-value pairsHow to create and manage hierarchically dependent PGF keys?LaTeX Utility/Template for Generic Reusable Code
Why Does Mama Coco Look Old After Going to the Other World?
How to befriend someone who doesn't like to talk?
2019 gold coins to share
Is using 'echo' to display attacker-controlled data on the terminal dangerous?
Can a human be transformed into a Mind Flayer?
Are polynomials with the same roots identical?
Varying the size of dots in a plot according to information contained in list
Who voices the small round football sized demon in Good Omens?
Live action TV show where High school Kids go into the virtual world and have to clear levels
How to avoid typing 'git' at the begining of every Git command
Why did Intel abandon unified CPU cache?
If I leave the US through an airport, do I have to return through the same airport?
Fermat's statement about the ancients: How serious was he?
Does putting salt first make it easier for attacker to bruteforce the hash?
Why is long-term living in Almost-Earth causing severe health problems?
What aircraft was used as Air Force One for the flight between Southampton and Shannon?
Was Self-modifying-code possible just using BASIC?
tabular: caption and align problem
How do free-speech protections in the United States apply in public to corporate misrepresentations?
Should I put programming books I wrote a few years ago on my resume?
A map of non-pathological topology?
How to make the letter "K" that denote Krylov space
Why is Na5 not played in this line of the French Defense, Advance Variation?
Section numbering in binary
keyval - function for keyB should act dependent on value of keyA - how to do this?
Test if token is a control sequenceThe definition of key default value by keyval packageUse alternate cite keys depending on document versionA list of pairs in pgfkeysExpansion problems with pgfkeysXparse macro with optional parameter breaks value for PGF foreachHow to define a function with optional key-value argumentsGenerating a table from key-value pairsHow to create and manage hierarchically dependent PGF keys?LaTeX Utility/Template for Generic Reusable Code
I'm new to LaTeX. I learn and I use article
class. I struggle learning working with keyval
package:
I try to write macro macro
which only uses one mandatory argument where user can provide key value list.
Possible keys should be keyA
and keyB
.
keyA
may take values true/yes/on/1 (yes-branch) and false/no/off/0 (no-branch).keyA
default should be yes branch.
I'd like keyB
to take an arbitrary value and to append this value between parentheses to macro foo
if keyA
denotes yes-branch and to append it between square brackets to the macro bar
if keyA
denotes no-branch.
Besides this I'd like that keyA
and keyB
at every call to macro
must be in the key value list and that they can be in the key value list only once - setting one of the keys more often or not at all → error-message.
I don't know how to do this because when setkeys
processes the key and value pairs one by one, it is not known what other key value pair follow and with key value pairs order is not firmly prescribed.
I tried this without success:
documentclassarticle
usepackagekeyval
deffoo
defbar
newififkeyAtruekeyAtruefalse
makeatletter
define@keyMyFamilykeyA[true]keyAtruetrue
define@keyMyFamilykeyB
ifkeyAtrue
deffoofoo(#1)
else
defbarbar[#1]
fi
makeatother
newcommandmacro[1]
setkeysMyFamily#1
keyAtruefalse
begindocument
% This almost works but there is 'foo in foo'-recursion.
macrokeyA=true,keyB=first value
showfooshowbar
% This almost works but there is 'foo in foo'-recursion and
% 'bar in bar'-recursion
macrokeyB=second value
showfooshowbar
% This dos not work at all. There is recursions and added to foo, not to bar.
macrokeyA=false,keyB=third value
showfooshowbar
enddocument
Thank you for help.
macros conditionals programming key-value
add a comment |
I'm new to LaTeX. I learn and I use article
class. I struggle learning working with keyval
package:
I try to write macro macro
which only uses one mandatory argument where user can provide key value list.
Possible keys should be keyA
and keyB
.
keyA
may take values true/yes/on/1 (yes-branch) and false/no/off/0 (no-branch).keyA
default should be yes branch.
I'd like keyB
to take an arbitrary value and to append this value between parentheses to macro foo
if keyA
denotes yes-branch and to append it between square brackets to the macro bar
if keyA
denotes no-branch.
Besides this I'd like that keyA
and keyB
at every call to macro
must be in the key value list and that they can be in the key value list only once - setting one of the keys more often or not at all → error-message.
I don't know how to do this because when setkeys
processes the key and value pairs one by one, it is not known what other key value pair follow and with key value pairs order is not firmly prescribed.
I tried this without success:
documentclassarticle
usepackagekeyval
deffoo
defbar
newififkeyAtruekeyAtruefalse
makeatletter
define@keyMyFamilykeyA[true]keyAtruetrue
define@keyMyFamilykeyB
ifkeyAtrue
deffoofoo(#1)
else
defbarbar[#1]
fi
makeatother
newcommandmacro[1]
setkeysMyFamily#1
keyAtruefalse
begindocument
% This almost works but there is 'foo in foo'-recursion.
macrokeyA=true,keyB=first value
showfooshowbar
% This almost works but there is 'foo in foo'-recursion and
% 'bar in bar'-recursion
macrokeyB=second value
showfooshowbar
% This dos not work at all. There is recursions and added to foo, not to bar.
macrokeyA=false,keyB=third value
showfooshowbar
enddocument
Thank you for help.
macros conditionals programming key-value
add a comment |
I'm new to LaTeX. I learn and I use article
class. I struggle learning working with keyval
package:
I try to write macro macro
which only uses one mandatory argument where user can provide key value list.
Possible keys should be keyA
and keyB
.
keyA
may take values true/yes/on/1 (yes-branch) and false/no/off/0 (no-branch).keyA
default should be yes branch.
I'd like keyB
to take an arbitrary value and to append this value between parentheses to macro foo
if keyA
denotes yes-branch and to append it between square brackets to the macro bar
if keyA
denotes no-branch.
Besides this I'd like that keyA
and keyB
at every call to macro
must be in the key value list and that they can be in the key value list only once - setting one of the keys more often or not at all → error-message.
I don't know how to do this because when setkeys
processes the key and value pairs one by one, it is not known what other key value pair follow and with key value pairs order is not firmly prescribed.
I tried this without success:
documentclassarticle
usepackagekeyval
deffoo
defbar
newififkeyAtruekeyAtruefalse
makeatletter
define@keyMyFamilykeyA[true]keyAtruetrue
define@keyMyFamilykeyB
ifkeyAtrue
deffoofoo(#1)
else
defbarbar[#1]
fi
makeatother
newcommandmacro[1]
setkeysMyFamily#1
keyAtruefalse
begindocument
% This almost works but there is 'foo in foo'-recursion.
macrokeyA=true,keyB=first value
showfooshowbar
% This almost works but there is 'foo in foo'-recursion and
% 'bar in bar'-recursion
macrokeyB=second value
showfooshowbar
% This dos not work at all. There is recursions and added to foo, not to bar.
macrokeyA=false,keyB=third value
showfooshowbar
enddocument
Thank you for help.
macros conditionals programming key-value
I'm new to LaTeX. I learn and I use article
class. I struggle learning working with keyval
package:
I try to write macro macro
which only uses one mandatory argument where user can provide key value list.
Possible keys should be keyA
and keyB
.
keyA
may take values true/yes/on/1 (yes-branch) and false/no/off/0 (no-branch).keyA
default should be yes branch.
I'd like keyB
to take an arbitrary value and to append this value between parentheses to macro foo
if keyA
denotes yes-branch and to append it between square brackets to the macro bar
if keyA
denotes no-branch.
Besides this I'd like that keyA
and keyB
at every call to macro
must be in the key value list and that they can be in the key value list only once - setting one of the keys more often or not at all → error-message.
I don't know how to do this because when setkeys
processes the key and value pairs one by one, it is not known what other key value pair follow and with key value pairs order is not firmly prescribed.
I tried this without success:
documentclassarticle
usepackagekeyval
deffoo
defbar
newififkeyAtruekeyAtruefalse
makeatletter
define@keyMyFamilykeyA[true]keyAtruetrue
define@keyMyFamilykeyB
ifkeyAtrue
deffoofoo(#1)
else
defbarbar[#1]
fi
makeatother
newcommandmacro[1]
setkeysMyFamily#1
keyAtruefalse
begindocument
% This almost works but there is 'foo in foo'-recursion.
macrokeyA=true,keyB=first value
showfooshowbar
% This almost works but there is 'foo in foo'-recursion and
% 'bar in bar'-recursion
macrokeyB=second value
showfooshowbar
% This dos not work at all. There is recursions and added to foo, not to bar.
macrokeyA=false,keyB=third value
showfooshowbar
enddocument
Thank you for help.
macros conditionals programming key-value
macros conditionals programming key-value
edited May 25 at 14:35
Phelype Oleinik
29.5k64897
29.5k64897
asked May 25 at 13:27
JevdokijaJevdokija
232
232
add a comment |
add a comment |
2 Answers
2
active
oldest
votes
Some remarks not related to the problem which nonetheless may be useful:
While line-wise and then character-wise reading/processing a .tex-input-file, (La)TeX usually will first remove all space characters (ASCII 32) at the right end of a line of input and then append a return character (ASCII 13) at the right end of that line of input. This is due to the integer parameter
endlinechar
usually having the value 13. Then (La)TeX will "look" at that line of input character by character and hereby take that line for a set of instructions for appending tokens (character tokens and control sequence tokens) to the token stream. The return character usually has category code 5 (end of line). After tokenizing a curly brace (or
), (La)TeX's reading apparatus usually is in state M (middle of line). (Generally (La)TeX's reading apparatus will be in state M after tokenizing a control symbol token (other than control space; a control symbol token is a control sequence token whose name consists of a single character which does not have category code 11(letter)) or a character-token other than a space token.)
When (La)TeX encounters a character of category code 5 (end of line) while in state M, (La)TeX will append a space token (character token of category code 10(space) and character code 32) to the token stream.
In (restricted) horizontal mode such a space token may yield visible horizontal glue.Thus: Make sure that lines of input, where the last thing that during reading and tokenizing shall be appended to the token stream either is a control symbol token (control sequence token whose name consists o a single character which is not of category code 11(letter)) or is a character token, end with a comment character (
%
) in situations where such horizontal glue is undesired.
As a rule of thumb make sure that lines of input, where the last thing that shall be appended to the token stream is some brace token (-character-token of category code 1 (begin group) or
-character-token of category code 2 (end group)), end with a comment-character (
%
).bar
is already defined in LaTeX2e. I suggest not to override it. Therefore in the example below for the names of the corresponding macros I use the all caps variantsFOO
andBAR
.
Some remarks related to the problem which probably may be useful:
With the keyval package you cannot have the "function" underlying keyB act depending on the value of keyA/depending on the result of carrying out the "function" underlying keyA.
As you stated yourself:
The order in which these "functions" get carried out is not predictable.
Also it is not predictable whether these "functions" get carried out at all or get carried out more than once.
Instead you can define a family of keys with underlying "functions" for setting some flags (which is a method of storing true/false-values or 0/1-values) and storing as macros other values that are to be used while/after evaluating the flags.
Then you can define a macro which does setkeys
for setting flags and storing values and then does evaluate the flags for printing error-messages and in case of not having printed any error message processing the stored values accordingly.
In the example below as flags I use switches created via newif
. I do so because with that example I don't wish LaTeX to load a large amount of packages. For setting flags, the packages flags and bitset, both of Heiko Oberdiek, may be of interest to you.
Processing comma separated key-value-lists is about processing macro arguments.
Braces need to be balanced with macro arguments.
Therefore I assume that the "arbitrary values" for keyB are sequences of tokens which are brace balanced. ;-)
When storing such (almost) arbitrary token sequences as macros, you need to make sure that hashes (#
) get doubled during the process of storing. The reason is: You retrieve these token sequences by expanding these macros, while during macro expansion two hashes (##
) will collapse into one which means that during macro expansion the amount of hashes will be halved.
For achieving the doubling of hashes you can use the fact that when the
delivers the content of a token register during an edef
, the hashes therein will be doubled.
In the example below LaTeX 2e's reserved scratch token register toks@
is used according to the pattern toks@⟨tokens⟩...edefmacrothetoks@
.
If e-TeX extensions are available, you can do edefmacrounexpanded⟨tokens⟩
as shown by Phelype Oleinik as unexpanded
also doubles hashes when carried out during edef
.
You also need forking depending on the phrase/depending on the token sequence provided as value to keyA:
In the example below you find DetectYesNo
which does the forking by means of delimited macro arguments.
You wish to have (almost) arbitrary things appended to the definition text of the macro foo
.
When doing this, you need to make sure that the new definition text of foo
also contains the tokens that form the old definition text of foo
by top-level-expanding foo
.
You could be tempted to do:
expandafterdefexpandafterfooexpandafterfoo⟨tokens to append⟩
But you need to be careful with such approaches as the amount of hashes will be halved during expansion of foo
. This may lead to problems.
If, e.g., foo
is defined via
deffoo%
defbas##1argument of bas: ##1.%
and you do:
expandafterdefexpandafterfooexpandafter%
foo
defbat##1argument of bat: ##1.%
, then this will not yield
deffoo%
defbas##1argument of bas: ##1.%
defbat##1argument of bat: ##1.%
, but it will yield:
deffoo%
defbas#1argument of bas: #1.%
defbat##1argument of bat: ##1.%
This will be a problem and therefore will yield an error message as foo
does not process parameters while foo
's definition text does contain #1
.
For achieving the hash-doubling you can again apply either the
-expansion on a token register (without e-TeX extensions) or unexpanded
(this requires e-TeX extensions) during edef
, but this time you need to combine that with expandafter
for obtaining the expansion of foo
:
Without e-TeX extensions, using LaTeX 2e's scratch token register toks@
, you can do something like:
deffoo%
defbas##1argument of bas: ##1.%
%
% Now let's apply expandafter for obtaining the expansion of `foo`:
toks@expandafter%
foo
defbat#1argument of bat: #1.%
%
edeffoothetoks@%
showfoo
But the scratch token register toks@
is reserved for the LaTeX 2e-kernel. This means that it should be reset before any token defined in the kernel will be carried out. This can be achieved by adding a bit of exchange
-trickery:
longdefexchange#1#2#2#1%
deffoo%
defbas##1argument of bas: ##1.%
%
expandafterexchangeexpandafter%
expandaftertoks@expandafterthetoks@%
%
% Now let's apply expandafter for obtaining the expansion of `foo`:
toks@expandafter%
foo
defbat#1argument of bat: #1.%
%
edeffoothetoks@%
%
showfoo
When e-TeX extensions are available, you can use a combination of edef
, expandafter
and unexpanded
as shown in the answer of Phelype Oleinik:
deffoo%
defbas##1argument of bas: ##1.%
%
edeffoo%
unexpandedexpandafter%
foo
defbat#1argument of bat: #1.%
%
%
showfoo
documentclassarticle
usepackagekeyval
makeatletter
%%----------------------------------------------------------------------
newcommandUD@firstofone[1]#1%
newcommandUD@firstoftwo[2]#1%
newcommandUD@secondoftwo[2]#2%
newcommandUD@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>
newcommandUD@CheckWhetherNull[1]%
romannumeral0expandafterUD@secondoftwostringexpandafter
UD@secondoftwoexpandafterexpandafterstring#1expandafter
UD@secondoftwostringexpandafterUD@firstoftwoexpandafter%
expandafterUD@secondoftwostringexpandafterexpandafter
UD@firstoftwo UD@secondoftwoexpandafterexpandafter
UD@firstoftwo UD@firstoftwo%
%
%%
%% A variant using e-TeX extensions could be:
%%
%%newcommandUD@CheckWhetherNull[1]%
%% romannumeral0ifrelaxdetokenize#1relax
%% expandafterUD@firstoftwoelseexpandafterUD@secondoftwofi
%% UD@firstoftwoexpandafter UD@firstoftwo%
%% UD@firstoftwoexpandafter UD@secondoftwo%
%%%
%%----------------------------------------------------------------------
%% AppendTokensToMacrotokensmacro%
%% 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.)
%%----------------------------------------------------------------------
newcommandAppendTokensToMacro[2]%
expandafterUD@Exchangeexpandafter%
expandaftertoks@expandafterthetoks@%
%
toks@expandafter#2#1%
edef#2thetoks@%
%
%
%%
%% A variant using e-TeX extensions could be:
%%
%%newcommandAppendTokensToMacro[2]%
%% edef#2unexpandedexpandafter#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?
newififDeliverErrMsgKeyANotProvided
DeliverErrMsgKeyANotProvidedtrue
%% Flag: Is there a need to print an error-message about keyB not
%% being set?
newififDeliverErrMsgKeyBNotProvided
DeliverErrMsgKeyBNotProvidedtrue
%% Flag: Is there a need to print an error-message about keyA
%% being provided multiple times?
newififDeliverErrMsgKeyAProvidedSeveralTimes
DeliverErrMsgKeyAProvidedSeveralTimesfalse
%% Flag: Is there a need to print an error-message about keyB
%% being provided multiple times?
newififDeliverErrMsgKeyBProvidedSeveralTimes
DeliverErrMsgKeyBProvidedSeveralTimesfalse
%% Flag: Is there a need to print an error-message about keyA
%% having a value which is not in the true/false-spectrum?
newififDeliverErrMsgKeyANeitherTrueNorFalse
DeliverErrMsgKeyANeitherTrueNorFalsefalse
%% Flag: Has keyA the value true?
newififKeyAsValueIsTrue
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%
spacespacespace@spaces@spaces@spaces
%
LaTeX Error: Inapproriate usage of macro string#1on@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:
%%----------------------------------------------------------------------
newcommandDeliverErrMsgKeyNeitherTrueNorFalse[1]%
PreambleMacroErrormacroInvalid value for #1%
%PackageErrorMyPackagestringmacro: Invalid value for #1on@line%
%@latex@errorstringmacro: Invalid value for #1on@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:
%%----------------------------------------------------------------------
newcommandDeliverErrMsgKeyNotProvided[1]%
PreambleMacroErrormacroSetting for #1 is missing%
%PackageErrorMyPackagestringmacro: Setting for #1 is missingon@line%
%@latex@errorstringmacro: Setting for #1 is missingon@line%
%
Setting #1 cannot be omitted.%
%
%
%%----------------------------------------------------------------------
%% Error-message in case a keys is set more times than once:
%%----------------------------------------------------------------------
newcommandDeliverErrMsgKeyProvidedSeveralTimes[1]%
PreambleMacroErrormacroMore than one value for #1%
%PackageErrorMyPackagestringmacro: More than one value for #1on@line%
%@latex@errorstringmacro: More than one value for #1on@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>%
%%
newcommandUD@GobbleToExclamlongdefUD@GobbleToExclam#1!%
newcommandUD@CheckWhetherNoExclam[1]%
expandafterUD@CheckWhetherNullexpandafterUD@GobbleToExclam#1!%
%
newcommandTrueFalseFork
longdefTrueFalseFork#1!!true!yes!on!1!false!no!off!0!#2#3!!!!#2%
newcommandDetectYesNo[1]lowercaseInnerDetectYesNo#1%
newcommandInnerDetectYesNo[4]%
romannumeral0UD@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@keyMyFamilykeyA[true]%
DeliverErrMsgKeyANotProvidedfalse
define@keyMyFamilykeyA[true]%
DeliverErrMsgKeyAProvidedSeveralTimestrue
%
DetectYesNo#1%
DeliverErrMsgKeyANeitherTrueNorFalsetrue%
KeyAsValueIsTruetrue%
KeyAsValueIsTruefalse%
%
define@keyMyFamilykeyB%
DeliverErrMsgKeyBNotProvidedfalse
define@keyMyFamilykeyB%
DeliverErrMsgKeyBProvidedSeveralTimestrue
%
AppendTokensToMacro#1MyKeyBvalue%
%
%%----------------------------------------------------------------------
%% Now the macro with evaluation and error-messages ad nauseam ;-) :
%%----------------------------------------------------------------------
newcommandmacro[1]%
begingroup
setkeysMyFamily#1%
%
% Print Error-Messages if necessary:
%
ifDeliverErrMsgKeyANeitherTrueNorFalseDeliverErrMsgKeyNeitherTrueNorFalsekeyAfi
ifDeliverErrMsgKeyANotProvidedDeliverErrMsgKeyNotProvidedkeyAfi
ifDeliverErrMsgKeyBNotProvidedDeliverErrMsgKeyNotProvidedkeyBfi
ifDeliverErrMsgKeyAProvidedSeveralTimesDeliverErrMsgKeyProvidedSeveralTimeskeyAfi
ifDeliverErrMsgKeyBProvidedSeveralTimesDeliverErrMsgKeyProvidedSeveralTimeskeyBfi
%
% Perform the adding to FOO or BAR in case no error-messages were
% printed:
%
ifDeliverErrMsgKeyANotProvidedexpandafterUD@secondoftwoelseexpandafterUD@firstofonefi%
ifDeliverErrMsgKeyBNotProvidedexpandafterUD@secondoftwoelseexpandafterUD@firstofonefi%
ifDeliverErrMsgKeyAProvidedSeveralTimesexpandafterUD@secondoftwoelseexpandafterUD@firstofonefi%
ifDeliverErrMsgKeyBProvidedSeveralTimesexpandafterUD@secondoftwoelseexpandafterUD@firstofonefi%
ifDeliverErrMsgKeyANeitherTrueNorFalseexpandafterUD@secondoftwoelseexpandafterUD@firstoftwofi%
ifKeyAsValueIsTrueKeyAsValueIsTruetrueexpandafterUD@firstoftwoelseexpandafterUD@secondoftwofi%
expandafterendgroupexpandafterAppendTokensToMacroexpandafterexpandafter(MyKeyBvalue)FOO%
%
expandafterendgroupexpandafterAppendTokensToMacroexpandafterexpandafter[MyKeyBvalue]BAR%
%
%
%
%
%
%
endgroup
%
makeatother
begindocument
noindent
verb|macrokeyA=true, keyB=Value in first call| - now you have:\
macrokeyA=true, keyB=Value in first call%
textttstringFOO: meaningFOO\stringBAR: meaningBAR\%
%
nullhrulefillnull\
verb|macrokeyA=yes, keyB=Value in second call| - now you have:\
macrokeyA=yes, keyB=Value in second call%
textttstringFOO: meaningFOO\stringBAR: meaningBAR\%
%
nullhrulefillnull\
verb|macrokeyA=0, keyB=Value in third call| - now you have:\
macrokeyA=0, keyB=Value in third call%
textttstringFOO: meaningFOO\stringBAR: meaningBAR\%
%
nullhrulefillnull\
verb|macrokeyA=Off, keyB=Value in fourth call| - now you have:\
macrokeyA=Off, keyB=Value in fourth call%
textttstringFOO: meaningFOO\stringBAR: meaningBAR\%
%
nullhrulefillnull\
verb|macrokeyA, keyB=Value in fifth call| - now you have:\
macrokeyA, keyB=Value in fifth call%
textttstringFOO: meaningFOO\stringBAR: meaningBAR\%
%
% Let's trigger some error-messages:
%
%nullhrulefillnull\
%verb|macrokeyA=woozle, | - now you have:\
%macrokeyA=woozle, %
%textttstringFOO: meaningFOO\stringBAR: meaningBAR\%
%
%nullhrulefillnull\
%verb|macrokeyA=1, keyA=false, keyB=Value in sixth call| - now you have:\
%macrokeyA=1, keyA=false, keyB=Value in sixth call%
%textttstringFOO: meaningFOO\stringBAR: meaningBAR\%
enddocument
add a comment |
The foo
in foo
recursion you mentioned happens because when you do deffoofoo(something else)
, the inner foo
is not expanded. If you were yo use that foo
TeX would soon explode: TeX capacity exceeded, sorry [input stack size=5000]
.
To have foo
contain whatever foo
was, plus the new contents, you can use edef
(e
xpand def
) and unexpanded
, like this:
edeffoounexpandedexpandafterfoo(more stuff)
then foo
will be (something else)(more stuff)
.
Now, the keyA=false
wasn't working because you defined keyA
with:
define@keyMyFamilykeyA[true]keyAtruetrue
so it wouldn't matter which value you passed to keyA
, at the end it would execute keyAtruetrue
, which is not what you wanted.
I defined a simple teststring<str a><str b><true><false>
macro for you which compares the two strings <str a>
and <str b>
then returns <true>
if they are equal or <false>
if they are not. Then I changed the definition of keyA
to cope with all the possibilities true/yes/on/1 (yes-branch)
and false/no/off/0 (no-branch)
.
I also added a couple of %
at the end of the lines to avoid spurious spaces.
And, as Ulrich Diez mentions in his answer (and I completely overlooked) you should be careful when using def
because you can override commands without knowing. I replaced bar
by rab
(I'm not creative :)
When running the code I got this in the terminal:
> foo=macro:
->(first value).
l.51 showfoo
showrab
?
> rab=macro:
->.
l.51 showfooshowrab
?
> foo=macro:
->(first value).
l.55 showfoo
showrab
?
> rab=macro:
->[second value].
l.55 showfooshowrab
?
> foo=macro:
->(first value).
l.59 showfoo
showrab
?
> rab=macro:
->[second value][third value].
l.59 showfooshowrab
?
Full code:
documentclassarticle
usepackagekeyval
deffoo
defrab
newififkeyAtruekeyAtruefalse
makeatletter
defteststring#1#2%
edef@tempadetokenize#1%
edef@tempbdetokenize#2%
ifx@tempa@tempb
@valid@keytrue
expandafter@firstoftwo
else
expandafter@secondoftwo
fi
newifif@valid@key
define@keyMyFamilykeyA[true]%
@valid@keyfalse
teststring#1truekeyAtruetrue
teststring#1yeskeyAtruetrue
teststring#1onkeyAtruetrue
teststring#11keyAtruetrue
%
unlessif@valid@key
teststring#1falsekeyAtruefalse
teststring#1nokeyAtruefalse
teststring#1offkeyAtruefalse
teststring#10keyAtruefalse
%
fi
unlessif@valid@key
PackageErrorInvalid option `#1' for keyA
fi
define@keyMyFamilykeyB% <--
ifkeyAtrue
edeffoounexpandedexpandafterfoo(#1)% <--
else
edefrabunexpandedexpandafterrab[#1]% <--
fi
makeatother
newcommandmacro[1]% <--
setkeysMyFamily#1% <--
keyAtruefalse
begindocument
% This almost works but there is 'foo in foo'-recursion.
macrokeyA=true,keyB=first value
showfooshowrab
% This almost works but there is 'foo in foo'-recursion and
% 'rab in rab'-recursion
macrokeyB=second value
showfooshowrab
% This dos not work at all. There is recursions and added to foo, not to rab.
% tracingall
macrokeyA=false,keyB=third value
showfooshowrab
enddocument
add a comment |
Your Answer
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "85"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2ftex.stackexchange.com%2fquestions%2f492597%2fkeyval-function-for-keyb-should-act-dependent-on-value-of-keya-how-to-do-thi%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
Some remarks not related to the problem which nonetheless may be useful:
While line-wise and then character-wise reading/processing a .tex-input-file, (La)TeX usually will first remove all space characters (ASCII 32) at the right end of a line of input and then append a return character (ASCII 13) at the right end of that line of input. This is due to the integer parameter
endlinechar
usually having the value 13. Then (La)TeX will "look" at that line of input character by character and hereby take that line for a set of instructions for appending tokens (character tokens and control sequence tokens) to the token stream. The return character usually has category code 5 (end of line). After tokenizing a curly brace (or
), (La)TeX's reading apparatus usually is in state M (middle of line). (Generally (La)TeX's reading apparatus will be in state M after tokenizing a control symbol token (other than control space; a control symbol token is a control sequence token whose name consists of a single character which does not have category code 11(letter)) or a character-token other than a space token.)
When (La)TeX encounters a character of category code 5 (end of line) while in state M, (La)TeX will append a space token (character token of category code 10(space) and character code 32) to the token stream.
In (restricted) horizontal mode such a space token may yield visible horizontal glue.Thus: Make sure that lines of input, where the last thing that during reading and tokenizing shall be appended to the token stream either is a control symbol token (control sequence token whose name consists o a single character which is not of category code 11(letter)) or is a character token, end with a comment character (
%
) in situations where such horizontal glue is undesired.
As a rule of thumb make sure that lines of input, where the last thing that shall be appended to the token stream is some brace token (-character-token of category code 1 (begin group) or
-character-token of category code 2 (end group)), end with a comment-character (
%
).bar
is already defined in LaTeX2e. I suggest not to override it. Therefore in the example below for the names of the corresponding macros I use the all caps variantsFOO
andBAR
.
Some remarks related to the problem which probably may be useful:
With the keyval package you cannot have the "function" underlying keyB act depending on the value of keyA/depending on the result of carrying out the "function" underlying keyA.
As you stated yourself:
The order in which these "functions" get carried out is not predictable.
Also it is not predictable whether these "functions" get carried out at all or get carried out more than once.
Instead you can define a family of keys with underlying "functions" for setting some flags (which is a method of storing true/false-values or 0/1-values) and storing as macros other values that are to be used while/after evaluating the flags.
Then you can define a macro which does setkeys
for setting flags and storing values and then does evaluate the flags for printing error-messages and in case of not having printed any error message processing the stored values accordingly.
In the example below as flags I use switches created via newif
. I do so because with that example I don't wish LaTeX to load a large amount of packages. For setting flags, the packages flags and bitset, both of Heiko Oberdiek, may be of interest to you.
Processing comma separated key-value-lists is about processing macro arguments.
Braces need to be balanced with macro arguments.
Therefore I assume that the "arbitrary values" for keyB are sequences of tokens which are brace balanced. ;-)
When storing such (almost) arbitrary token sequences as macros, you need to make sure that hashes (#
) get doubled during the process of storing. The reason is: You retrieve these token sequences by expanding these macros, while during macro expansion two hashes (##
) will collapse into one which means that during macro expansion the amount of hashes will be halved.
For achieving the doubling of hashes you can use the fact that when the
delivers the content of a token register during an edef
, the hashes therein will be doubled.
In the example below LaTeX 2e's reserved scratch token register toks@
is used according to the pattern toks@⟨tokens⟩...edefmacrothetoks@
.
If e-TeX extensions are available, you can do edefmacrounexpanded⟨tokens⟩
as shown by Phelype Oleinik as unexpanded
also doubles hashes when carried out during edef
.
You also need forking depending on the phrase/depending on the token sequence provided as value to keyA:
In the example below you find DetectYesNo
which does the forking by means of delimited macro arguments.
You wish to have (almost) arbitrary things appended to the definition text of the macro foo
.
When doing this, you need to make sure that the new definition text of foo
also contains the tokens that form the old definition text of foo
by top-level-expanding foo
.
You could be tempted to do:
expandafterdefexpandafterfooexpandafterfoo⟨tokens to append⟩
But you need to be careful with such approaches as the amount of hashes will be halved during expansion of foo
. This may lead to problems.
If, e.g., foo
is defined via
deffoo%
defbas##1argument of bas: ##1.%
and you do:
expandafterdefexpandafterfooexpandafter%
foo
defbat##1argument of bat: ##1.%
, then this will not yield
deffoo%
defbas##1argument of bas: ##1.%
defbat##1argument of bat: ##1.%
, but it will yield:
deffoo%
defbas#1argument of bas: #1.%
defbat##1argument of bat: ##1.%
This will be a problem and therefore will yield an error message as foo
does not process parameters while foo
's definition text does contain #1
.
For achieving the hash-doubling you can again apply either the
-expansion on a token register (without e-TeX extensions) or unexpanded
(this requires e-TeX extensions) during edef
, but this time you need to combine that with expandafter
for obtaining the expansion of foo
:
Without e-TeX extensions, using LaTeX 2e's scratch token register toks@
, you can do something like:
deffoo%
defbas##1argument of bas: ##1.%
%
% Now let's apply expandafter for obtaining the expansion of `foo`:
toks@expandafter%
foo
defbat#1argument of bat: #1.%
%
edeffoothetoks@%
showfoo
But the scratch token register toks@
is reserved for the LaTeX 2e-kernel. This means that it should be reset before any token defined in the kernel will be carried out. This can be achieved by adding a bit of exchange
-trickery:
longdefexchange#1#2#2#1%
deffoo%
defbas##1argument of bas: ##1.%
%
expandafterexchangeexpandafter%
expandaftertoks@expandafterthetoks@%
%
% Now let's apply expandafter for obtaining the expansion of `foo`:
toks@expandafter%
foo
defbat#1argument of bat: #1.%
%
edeffoothetoks@%
%
showfoo
When e-TeX extensions are available, you can use a combination of edef
, expandafter
and unexpanded
as shown in the answer of Phelype Oleinik:
deffoo%
defbas##1argument of bas: ##1.%
%
edeffoo%
unexpandedexpandafter%
foo
defbat#1argument of bat: #1.%
%
%
showfoo
documentclassarticle
usepackagekeyval
makeatletter
%%----------------------------------------------------------------------
newcommandUD@firstofone[1]#1%
newcommandUD@firstoftwo[2]#1%
newcommandUD@secondoftwo[2]#2%
newcommandUD@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>
newcommandUD@CheckWhetherNull[1]%
romannumeral0expandafterUD@secondoftwostringexpandafter
UD@secondoftwoexpandafterexpandafterstring#1expandafter
UD@secondoftwostringexpandafterUD@firstoftwoexpandafter%
expandafterUD@secondoftwostringexpandafterexpandafter
UD@firstoftwo UD@secondoftwoexpandafterexpandafter
UD@firstoftwo UD@firstoftwo%
%
%%
%% A variant using e-TeX extensions could be:
%%
%%newcommandUD@CheckWhetherNull[1]%
%% romannumeral0ifrelaxdetokenize#1relax
%% expandafterUD@firstoftwoelseexpandafterUD@secondoftwofi
%% UD@firstoftwoexpandafter UD@firstoftwo%
%% UD@firstoftwoexpandafter UD@secondoftwo%
%%%
%%----------------------------------------------------------------------
%% AppendTokensToMacrotokensmacro%
%% 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.)
%%----------------------------------------------------------------------
newcommandAppendTokensToMacro[2]%
expandafterUD@Exchangeexpandafter%
expandaftertoks@expandafterthetoks@%
%
toks@expandafter#2#1%
edef#2thetoks@%
%
%
%%
%% A variant using e-TeX extensions could be:
%%
%%newcommandAppendTokensToMacro[2]%
%% edef#2unexpandedexpandafter#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?
newififDeliverErrMsgKeyANotProvided
DeliverErrMsgKeyANotProvidedtrue
%% Flag: Is there a need to print an error-message about keyB not
%% being set?
newififDeliverErrMsgKeyBNotProvided
DeliverErrMsgKeyBNotProvidedtrue
%% Flag: Is there a need to print an error-message about keyA
%% being provided multiple times?
newififDeliverErrMsgKeyAProvidedSeveralTimes
DeliverErrMsgKeyAProvidedSeveralTimesfalse
%% Flag: Is there a need to print an error-message about keyB
%% being provided multiple times?
newififDeliverErrMsgKeyBProvidedSeveralTimes
DeliverErrMsgKeyBProvidedSeveralTimesfalse
%% Flag: Is there a need to print an error-message about keyA
%% having a value which is not in the true/false-spectrum?
newififDeliverErrMsgKeyANeitherTrueNorFalse
DeliverErrMsgKeyANeitherTrueNorFalsefalse
%% Flag: Has keyA the value true?
newififKeyAsValueIsTrue
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%
spacespacespace@spaces@spaces@spaces
%
LaTeX Error: Inapproriate usage of macro string#1on@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:
%%----------------------------------------------------------------------
newcommandDeliverErrMsgKeyNeitherTrueNorFalse[1]%
PreambleMacroErrormacroInvalid value for #1%
%PackageErrorMyPackagestringmacro: Invalid value for #1on@line%
%@latex@errorstringmacro: Invalid value for #1on@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:
%%----------------------------------------------------------------------
newcommandDeliverErrMsgKeyNotProvided[1]%
PreambleMacroErrormacroSetting for #1 is missing%
%PackageErrorMyPackagestringmacro: Setting for #1 is missingon@line%
%@latex@errorstringmacro: Setting for #1 is missingon@line%
%
Setting #1 cannot be omitted.%
%
%
%%----------------------------------------------------------------------
%% Error-message in case a keys is set more times than once:
%%----------------------------------------------------------------------
newcommandDeliverErrMsgKeyProvidedSeveralTimes[1]%
PreambleMacroErrormacroMore than one value for #1%
%PackageErrorMyPackagestringmacro: More than one value for #1on@line%
%@latex@errorstringmacro: More than one value for #1on@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>%
%%
newcommandUD@GobbleToExclamlongdefUD@GobbleToExclam#1!%
newcommandUD@CheckWhetherNoExclam[1]%
expandafterUD@CheckWhetherNullexpandafterUD@GobbleToExclam#1!%
%
newcommandTrueFalseFork
longdefTrueFalseFork#1!!true!yes!on!1!false!no!off!0!#2#3!!!!#2%
newcommandDetectYesNo[1]lowercaseInnerDetectYesNo#1%
newcommandInnerDetectYesNo[4]%
romannumeral0UD@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@keyMyFamilykeyA[true]%
DeliverErrMsgKeyANotProvidedfalse
define@keyMyFamilykeyA[true]%
DeliverErrMsgKeyAProvidedSeveralTimestrue
%
DetectYesNo#1%
DeliverErrMsgKeyANeitherTrueNorFalsetrue%
KeyAsValueIsTruetrue%
KeyAsValueIsTruefalse%
%
define@keyMyFamilykeyB%
DeliverErrMsgKeyBNotProvidedfalse
define@keyMyFamilykeyB%
DeliverErrMsgKeyBProvidedSeveralTimestrue
%
AppendTokensToMacro#1MyKeyBvalue%
%
%%----------------------------------------------------------------------
%% Now the macro with evaluation and error-messages ad nauseam ;-) :
%%----------------------------------------------------------------------
newcommandmacro[1]%
begingroup
setkeysMyFamily#1%
%
% Print Error-Messages if necessary:
%
ifDeliverErrMsgKeyANeitherTrueNorFalseDeliverErrMsgKeyNeitherTrueNorFalsekeyAfi
ifDeliverErrMsgKeyANotProvidedDeliverErrMsgKeyNotProvidedkeyAfi
ifDeliverErrMsgKeyBNotProvidedDeliverErrMsgKeyNotProvidedkeyBfi
ifDeliverErrMsgKeyAProvidedSeveralTimesDeliverErrMsgKeyProvidedSeveralTimeskeyAfi
ifDeliverErrMsgKeyBProvidedSeveralTimesDeliverErrMsgKeyProvidedSeveralTimeskeyBfi
%
% Perform the adding to FOO or BAR in case no error-messages were
% printed:
%
ifDeliverErrMsgKeyANotProvidedexpandafterUD@secondoftwoelseexpandafterUD@firstofonefi%
ifDeliverErrMsgKeyBNotProvidedexpandafterUD@secondoftwoelseexpandafterUD@firstofonefi%
ifDeliverErrMsgKeyAProvidedSeveralTimesexpandafterUD@secondoftwoelseexpandafterUD@firstofonefi%
ifDeliverErrMsgKeyBProvidedSeveralTimesexpandafterUD@secondoftwoelseexpandafterUD@firstofonefi%
ifDeliverErrMsgKeyANeitherTrueNorFalseexpandafterUD@secondoftwoelseexpandafterUD@firstoftwofi%
ifKeyAsValueIsTrueKeyAsValueIsTruetrueexpandafterUD@firstoftwoelseexpandafterUD@secondoftwofi%
expandafterendgroupexpandafterAppendTokensToMacroexpandafterexpandafter(MyKeyBvalue)FOO%
%
expandafterendgroupexpandafterAppendTokensToMacroexpandafterexpandafter[MyKeyBvalue]BAR%
%
%
%
%
%
%
endgroup
%
makeatother
begindocument
noindent
verb|macrokeyA=true, keyB=Value in first call| - now you have:\
macrokeyA=true, keyB=Value in first call%
textttstringFOO: meaningFOO\stringBAR: meaningBAR\%
%
nullhrulefillnull\
verb|macrokeyA=yes, keyB=Value in second call| - now you have:\
macrokeyA=yes, keyB=Value in second call%
textttstringFOO: meaningFOO\stringBAR: meaningBAR\%
%
nullhrulefillnull\
verb|macrokeyA=0, keyB=Value in third call| - now you have:\
macrokeyA=0, keyB=Value in third call%
textttstringFOO: meaningFOO\stringBAR: meaningBAR\%
%
nullhrulefillnull\
verb|macrokeyA=Off, keyB=Value in fourth call| - now you have:\
macrokeyA=Off, keyB=Value in fourth call%
textttstringFOO: meaningFOO\stringBAR: meaningBAR\%
%
nullhrulefillnull\
verb|macrokeyA, keyB=Value in fifth call| - now you have:\
macrokeyA, keyB=Value in fifth call%
textttstringFOO: meaningFOO\stringBAR: meaningBAR\%
%
% Let's trigger some error-messages:
%
%nullhrulefillnull\
%verb|macrokeyA=woozle, | - now you have:\
%macrokeyA=woozle, %
%textttstringFOO: meaningFOO\stringBAR: meaningBAR\%
%
%nullhrulefillnull\
%verb|macrokeyA=1, keyA=false, keyB=Value in sixth call| - now you have:\
%macrokeyA=1, keyA=false, keyB=Value in sixth call%
%textttstringFOO: meaningFOO\stringBAR: meaningBAR\%
enddocument
add a comment |
Some remarks not related to the problem which nonetheless may be useful:
While line-wise and then character-wise reading/processing a .tex-input-file, (La)TeX usually will first remove all space characters (ASCII 32) at the right end of a line of input and then append a return character (ASCII 13) at the right end of that line of input. This is due to the integer parameter
endlinechar
usually having the value 13. Then (La)TeX will "look" at that line of input character by character and hereby take that line for a set of instructions for appending tokens (character tokens and control sequence tokens) to the token stream. The return character usually has category code 5 (end of line). After tokenizing a curly brace (or
), (La)TeX's reading apparatus usually is in state M (middle of line). (Generally (La)TeX's reading apparatus will be in state M after tokenizing a control symbol token (other than control space; a control symbol token is a control sequence token whose name consists of a single character which does not have category code 11(letter)) or a character-token other than a space token.)
When (La)TeX encounters a character of category code 5 (end of line) while in state M, (La)TeX will append a space token (character token of category code 10(space) and character code 32) to the token stream.
In (restricted) horizontal mode such a space token may yield visible horizontal glue.Thus: Make sure that lines of input, where the last thing that during reading and tokenizing shall be appended to the token stream either is a control symbol token (control sequence token whose name consists o a single character which is not of category code 11(letter)) or is a character token, end with a comment character (
%
) in situations where such horizontal glue is undesired.
As a rule of thumb make sure that lines of input, where the last thing that shall be appended to the token stream is some brace token (-character-token of category code 1 (begin group) or
-character-token of category code 2 (end group)), end with a comment-character (
%
).bar
is already defined in LaTeX2e. I suggest not to override it. Therefore in the example below for the names of the corresponding macros I use the all caps variantsFOO
andBAR
.
Some remarks related to the problem which probably may be useful:
With the keyval package you cannot have the "function" underlying keyB act depending on the value of keyA/depending on the result of carrying out the "function" underlying keyA.
As you stated yourself:
The order in which these "functions" get carried out is not predictable.
Also it is not predictable whether these "functions" get carried out at all or get carried out more than once.
Instead you can define a family of keys with underlying "functions" for setting some flags (which is a method of storing true/false-values or 0/1-values) and storing as macros other values that are to be used while/after evaluating the flags.
Then you can define a macro which does setkeys
for setting flags and storing values and then does evaluate the flags for printing error-messages and in case of not having printed any error message processing the stored values accordingly.
In the example below as flags I use switches created via newif
. I do so because with that example I don't wish LaTeX to load a large amount of packages. For setting flags, the packages flags and bitset, both of Heiko Oberdiek, may be of interest to you.
Processing comma separated key-value-lists is about processing macro arguments.
Braces need to be balanced with macro arguments.
Therefore I assume that the "arbitrary values" for keyB are sequences of tokens which are brace balanced. ;-)
When storing such (almost) arbitrary token sequences as macros, you need to make sure that hashes (#
) get doubled during the process of storing. The reason is: You retrieve these token sequences by expanding these macros, while during macro expansion two hashes (##
) will collapse into one which means that during macro expansion the amount of hashes will be halved.
For achieving the doubling of hashes you can use the fact that when the
delivers the content of a token register during an edef
, the hashes therein will be doubled.
In the example below LaTeX 2e's reserved scratch token register toks@
is used according to the pattern toks@⟨tokens⟩...edefmacrothetoks@
.
If e-TeX extensions are available, you can do edefmacrounexpanded⟨tokens⟩
as shown by Phelype Oleinik as unexpanded
also doubles hashes when carried out during edef
.
You also need forking depending on the phrase/depending on the token sequence provided as value to keyA:
In the example below you find DetectYesNo
which does the forking by means of delimited macro arguments.
You wish to have (almost) arbitrary things appended to the definition text of the macro foo
.
When doing this, you need to make sure that the new definition text of foo
also contains the tokens that form the old definition text of foo
by top-level-expanding foo
.
You could be tempted to do:
expandafterdefexpandafterfooexpandafterfoo⟨tokens to append⟩
But you need to be careful with such approaches as the amount of hashes will be halved during expansion of foo
. This may lead to problems.
If, e.g., foo
is defined via
deffoo%
defbas##1argument of bas: ##1.%
and you do:
expandafterdefexpandafterfooexpandafter%
foo
defbat##1argument of bat: ##1.%
, then this will not yield
deffoo%
defbas##1argument of bas: ##1.%
defbat##1argument of bat: ##1.%
, but it will yield:
deffoo%
defbas#1argument of bas: #1.%
defbat##1argument of bat: ##1.%
This will be a problem and therefore will yield an error message as foo
does not process parameters while foo
's definition text does contain #1
.
For achieving the hash-doubling you can again apply either the
-expansion on a token register (without e-TeX extensions) or unexpanded
(this requires e-TeX extensions) during edef
, but this time you need to combine that with expandafter
for obtaining the expansion of foo
:
Without e-TeX extensions, using LaTeX 2e's scratch token register toks@
, you can do something like:
deffoo%
defbas##1argument of bas: ##1.%
%
% Now let's apply expandafter for obtaining the expansion of `foo`:
toks@expandafter%
foo
defbat#1argument of bat: #1.%
%
edeffoothetoks@%
showfoo
But the scratch token register toks@
is reserved for the LaTeX 2e-kernel. This means that it should be reset before any token defined in the kernel will be carried out. This can be achieved by adding a bit of exchange
-trickery:
longdefexchange#1#2#2#1%
deffoo%
defbas##1argument of bas: ##1.%
%
expandafterexchangeexpandafter%
expandaftertoks@expandafterthetoks@%
%
% Now let's apply expandafter for obtaining the expansion of `foo`:
toks@expandafter%
foo
defbat#1argument of bat: #1.%
%
edeffoothetoks@%
%
showfoo
When e-TeX extensions are available, you can use a combination of edef
, expandafter
and unexpanded
as shown in the answer of Phelype Oleinik:
deffoo%
defbas##1argument of bas: ##1.%
%
edeffoo%
unexpandedexpandafter%
foo
defbat#1argument of bat: #1.%
%
%
showfoo
documentclassarticle
usepackagekeyval
makeatletter
%%----------------------------------------------------------------------
newcommandUD@firstofone[1]#1%
newcommandUD@firstoftwo[2]#1%
newcommandUD@secondoftwo[2]#2%
newcommandUD@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>
newcommandUD@CheckWhetherNull[1]%
romannumeral0expandafterUD@secondoftwostringexpandafter
UD@secondoftwoexpandafterexpandafterstring#1expandafter
UD@secondoftwostringexpandafterUD@firstoftwoexpandafter%
expandafterUD@secondoftwostringexpandafterexpandafter
UD@firstoftwo UD@secondoftwoexpandafterexpandafter
UD@firstoftwo UD@firstoftwo%
%
%%
%% A variant using e-TeX extensions could be:
%%
%%newcommandUD@CheckWhetherNull[1]%
%% romannumeral0ifrelaxdetokenize#1relax
%% expandafterUD@firstoftwoelseexpandafterUD@secondoftwofi
%% UD@firstoftwoexpandafter UD@firstoftwo%
%% UD@firstoftwoexpandafter UD@secondoftwo%
%%%
%%----------------------------------------------------------------------
%% AppendTokensToMacrotokensmacro%
%% 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.)
%%----------------------------------------------------------------------
newcommandAppendTokensToMacro[2]%
expandafterUD@Exchangeexpandafter%
expandaftertoks@expandafterthetoks@%
%
toks@expandafter#2#1%
edef#2thetoks@%
%
%
%%
%% A variant using e-TeX extensions could be:
%%
%%newcommandAppendTokensToMacro[2]%
%% edef#2unexpandedexpandafter#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?
newififDeliverErrMsgKeyANotProvided
DeliverErrMsgKeyANotProvidedtrue
%% Flag: Is there a need to print an error-message about keyB not
%% being set?
newififDeliverErrMsgKeyBNotProvided
DeliverErrMsgKeyBNotProvidedtrue
%% Flag: Is there a need to print an error-message about keyA
%% being provided multiple times?
newififDeliverErrMsgKeyAProvidedSeveralTimes
DeliverErrMsgKeyAProvidedSeveralTimesfalse
%% Flag: Is there a need to print an error-message about keyB
%% being provided multiple times?
newififDeliverErrMsgKeyBProvidedSeveralTimes
DeliverErrMsgKeyBProvidedSeveralTimesfalse
%% Flag: Is there a need to print an error-message about keyA
%% having a value which is not in the true/false-spectrum?
newififDeliverErrMsgKeyANeitherTrueNorFalse
DeliverErrMsgKeyANeitherTrueNorFalsefalse
%% Flag: Has keyA the value true?
newififKeyAsValueIsTrue
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%
spacespacespace@spaces@spaces@spaces
%
LaTeX Error: Inapproriate usage of macro string#1on@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:
%%----------------------------------------------------------------------
newcommandDeliverErrMsgKeyNeitherTrueNorFalse[1]%
PreambleMacroErrormacroInvalid value for #1%
%PackageErrorMyPackagestringmacro: Invalid value for #1on@line%
%@latex@errorstringmacro: Invalid value for #1on@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:
%%----------------------------------------------------------------------
newcommandDeliverErrMsgKeyNotProvided[1]%
PreambleMacroErrormacroSetting for #1 is missing%
%PackageErrorMyPackagestringmacro: Setting for #1 is missingon@line%
%@latex@errorstringmacro: Setting for #1 is missingon@line%
%
Setting #1 cannot be omitted.%
%
%
%%----------------------------------------------------------------------
%% Error-message in case a keys is set more times than once:
%%----------------------------------------------------------------------
newcommandDeliverErrMsgKeyProvidedSeveralTimes[1]%
PreambleMacroErrormacroMore than one value for #1%
%PackageErrorMyPackagestringmacro: More than one value for #1on@line%
%@latex@errorstringmacro: More than one value for #1on@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>%
%%
newcommandUD@GobbleToExclamlongdefUD@GobbleToExclam#1!%
newcommandUD@CheckWhetherNoExclam[1]%
expandafterUD@CheckWhetherNullexpandafterUD@GobbleToExclam#1!%
%
newcommandTrueFalseFork
longdefTrueFalseFork#1!!true!yes!on!1!false!no!off!0!#2#3!!!!#2%
newcommandDetectYesNo[1]lowercaseInnerDetectYesNo#1%
newcommandInnerDetectYesNo[4]%
romannumeral0UD@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@keyMyFamilykeyA[true]%
DeliverErrMsgKeyANotProvidedfalse
define@keyMyFamilykeyA[true]%
DeliverErrMsgKeyAProvidedSeveralTimestrue
%
DetectYesNo#1%
DeliverErrMsgKeyANeitherTrueNorFalsetrue%
KeyAsValueIsTruetrue%
KeyAsValueIsTruefalse%
%
define@keyMyFamilykeyB%
DeliverErrMsgKeyBNotProvidedfalse
define@keyMyFamilykeyB%
DeliverErrMsgKeyBProvidedSeveralTimestrue
%
AppendTokensToMacro#1MyKeyBvalue%
%
%%----------------------------------------------------------------------
%% Now the macro with evaluation and error-messages ad nauseam ;-) :
%%----------------------------------------------------------------------
newcommandmacro[1]%
begingroup
setkeysMyFamily#1%
%
% Print Error-Messages if necessary:
%
ifDeliverErrMsgKeyANeitherTrueNorFalseDeliverErrMsgKeyNeitherTrueNorFalsekeyAfi
ifDeliverErrMsgKeyANotProvidedDeliverErrMsgKeyNotProvidedkeyAfi
ifDeliverErrMsgKeyBNotProvidedDeliverErrMsgKeyNotProvidedkeyBfi
ifDeliverErrMsgKeyAProvidedSeveralTimesDeliverErrMsgKeyProvidedSeveralTimeskeyAfi
ifDeliverErrMsgKeyBProvidedSeveralTimesDeliverErrMsgKeyProvidedSeveralTimeskeyBfi
%
% Perform the adding to FOO or BAR in case no error-messages were
% printed:
%
ifDeliverErrMsgKeyANotProvidedexpandafterUD@secondoftwoelseexpandafterUD@firstofonefi%
ifDeliverErrMsgKeyBNotProvidedexpandafterUD@secondoftwoelseexpandafterUD@firstofonefi%
ifDeliverErrMsgKeyAProvidedSeveralTimesexpandafterUD@secondoftwoelseexpandafterUD@firstofonefi%
ifDeliverErrMsgKeyBProvidedSeveralTimesexpandafterUD@secondoftwoelseexpandafterUD@firstofonefi%
ifDeliverErrMsgKeyANeitherTrueNorFalseexpandafterUD@secondoftwoelseexpandafterUD@firstoftwofi%
ifKeyAsValueIsTrueKeyAsValueIsTruetrueexpandafterUD@firstoftwoelseexpandafterUD@secondoftwofi%
expandafterendgroupexpandafterAppendTokensToMacroexpandafterexpandafter(MyKeyBvalue)FOO%
%
expandafterendgroupexpandafterAppendTokensToMacroexpandafterexpandafter[MyKeyBvalue]BAR%
%
%
%
%
%
%
endgroup
%
makeatother
begindocument
noindent
verb|macrokeyA=true, keyB=Value in first call| - now you have:\
macrokeyA=true, keyB=Value in first call%
textttstringFOO: meaningFOO\stringBAR: meaningBAR\%
%
nullhrulefillnull\
verb|macrokeyA=yes, keyB=Value in second call| - now you have:\
macrokeyA=yes, keyB=Value in second call%
textttstringFOO: meaningFOO\stringBAR: meaningBAR\%
%
nullhrulefillnull\
verb|macrokeyA=0, keyB=Value in third call| - now you have:\
macrokeyA=0, keyB=Value in third call%
textttstringFOO: meaningFOO\stringBAR: meaningBAR\%
%
nullhrulefillnull\
verb|macrokeyA=Off, keyB=Value in fourth call| - now you have:\
macrokeyA=Off, keyB=Value in fourth call%
textttstringFOO: meaningFOO\stringBAR: meaningBAR\%
%
nullhrulefillnull\
verb|macrokeyA, keyB=Value in fifth call| - now you have:\
macrokeyA, keyB=Value in fifth call%
textttstringFOO: meaningFOO\stringBAR: meaningBAR\%
%
% Let's trigger some error-messages:
%
%nullhrulefillnull\
%verb|macrokeyA=woozle, | - now you have:\
%macrokeyA=woozle, %
%textttstringFOO: meaningFOO\stringBAR: meaningBAR\%
%
%nullhrulefillnull\
%verb|macrokeyA=1, keyA=false, keyB=Value in sixth call| - now you have:\
%macrokeyA=1, keyA=false, keyB=Value in sixth call%
%textttstringFOO: meaningFOO\stringBAR: meaningBAR\%
enddocument
add a comment |
Some remarks not related to the problem which nonetheless may be useful:
While line-wise and then character-wise reading/processing a .tex-input-file, (La)TeX usually will first remove all space characters (ASCII 32) at the right end of a line of input and then append a return character (ASCII 13) at the right end of that line of input. This is due to the integer parameter
endlinechar
usually having the value 13. Then (La)TeX will "look" at that line of input character by character and hereby take that line for a set of instructions for appending tokens (character tokens and control sequence tokens) to the token stream. The return character usually has category code 5 (end of line). After tokenizing a curly brace (or
), (La)TeX's reading apparatus usually is in state M (middle of line). (Generally (La)TeX's reading apparatus will be in state M after tokenizing a control symbol token (other than control space; a control symbol token is a control sequence token whose name consists of a single character which does not have category code 11(letter)) or a character-token other than a space token.)
When (La)TeX encounters a character of category code 5 (end of line) while in state M, (La)TeX will append a space token (character token of category code 10(space) and character code 32) to the token stream.
In (restricted) horizontal mode such a space token may yield visible horizontal glue.Thus: Make sure that lines of input, where the last thing that during reading and tokenizing shall be appended to the token stream either is a control symbol token (control sequence token whose name consists o a single character which is not of category code 11(letter)) or is a character token, end with a comment character (
%
) in situations where such horizontal glue is undesired.
As a rule of thumb make sure that lines of input, where the last thing that shall be appended to the token stream is some brace token (-character-token of category code 1 (begin group) or
-character-token of category code 2 (end group)), end with a comment-character (
%
).bar
is already defined in LaTeX2e. I suggest not to override it. Therefore in the example below for the names of the corresponding macros I use the all caps variantsFOO
andBAR
.
Some remarks related to the problem which probably may be useful:
With the keyval package you cannot have the "function" underlying keyB act depending on the value of keyA/depending on the result of carrying out the "function" underlying keyA.
As you stated yourself:
The order in which these "functions" get carried out is not predictable.
Also it is not predictable whether these "functions" get carried out at all or get carried out more than once.
Instead you can define a family of keys with underlying "functions" for setting some flags (which is a method of storing true/false-values or 0/1-values) and storing as macros other values that are to be used while/after evaluating the flags.
Then you can define a macro which does setkeys
for setting flags and storing values and then does evaluate the flags for printing error-messages and in case of not having printed any error message processing the stored values accordingly.
In the example below as flags I use switches created via newif
. I do so because with that example I don't wish LaTeX to load a large amount of packages. For setting flags, the packages flags and bitset, both of Heiko Oberdiek, may be of interest to you.
Processing comma separated key-value-lists is about processing macro arguments.
Braces need to be balanced with macro arguments.
Therefore I assume that the "arbitrary values" for keyB are sequences of tokens which are brace balanced. ;-)
When storing such (almost) arbitrary token sequences as macros, you need to make sure that hashes (#
) get doubled during the process of storing. The reason is: You retrieve these token sequences by expanding these macros, while during macro expansion two hashes (##
) will collapse into one which means that during macro expansion the amount of hashes will be halved.
For achieving the doubling of hashes you can use the fact that when the
delivers the content of a token register during an edef
, the hashes therein will be doubled.
In the example below LaTeX 2e's reserved scratch token register toks@
is used according to the pattern toks@⟨tokens⟩...edefmacrothetoks@
.
If e-TeX extensions are available, you can do edefmacrounexpanded⟨tokens⟩
as shown by Phelype Oleinik as unexpanded
also doubles hashes when carried out during edef
.
You also need forking depending on the phrase/depending on the token sequence provided as value to keyA:
In the example below you find DetectYesNo
which does the forking by means of delimited macro arguments.
You wish to have (almost) arbitrary things appended to the definition text of the macro foo
.
When doing this, you need to make sure that the new definition text of foo
also contains the tokens that form the old definition text of foo
by top-level-expanding foo
.
You could be tempted to do:
expandafterdefexpandafterfooexpandafterfoo⟨tokens to append⟩
But you need to be careful with such approaches as the amount of hashes will be halved during expansion of foo
. This may lead to problems.
If, e.g., foo
is defined via
deffoo%
defbas##1argument of bas: ##1.%
and you do:
expandafterdefexpandafterfooexpandafter%
foo
defbat##1argument of bat: ##1.%
, then this will not yield
deffoo%
defbas##1argument of bas: ##1.%
defbat##1argument of bat: ##1.%
, but it will yield:
deffoo%
defbas#1argument of bas: #1.%
defbat##1argument of bat: ##1.%
This will be a problem and therefore will yield an error message as foo
does not process parameters while foo
's definition text does contain #1
.
For achieving the hash-doubling you can again apply either the
-expansion on a token register (without e-TeX extensions) or unexpanded
(this requires e-TeX extensions) during edef
, but this time you need to combine that with expandafter
for obtaining the expansion of foo
:
Without e-TeX extensions, using LaTeX 2e's scratch token register toks@
, you can do something like:
deffoo%
defbas##1argument of bas: ##1.%
%
% Now let's apply expandafter for obtaining the expansion of `foo`:
toks@expandafter%
foo
defbat#1argument of bat: #1.%
%
edeffoothetoks@%
showfoo
But the scratch token register toks@
is reserved for the LaTeX 2e-kernel. This means that it should be reset before any token defined in the kernel will be carried out. This can be achieved by adding a bit of exchange
-trickery:
longdefexchange#1#2#2#1%
deffoo%
defbas##1argument of bas: ##1.%
%
expandafterexchangeexpandafter%
expandaftertoks@expandafterthetoks@%
%
% Now let's apply expandafter for obtaining the expansion of `foo`:
toks@expandafter%
foo
defbat#1argument of bat: #1.%
%
edeffoothetoks@%
%
showfoo
When e-TeX extensions are available, you can use a combination of edef
, expandafter
and unexpanded
as shown in the answer of Phelype Oleinik:
deffoo%
defbas##1argument of bas: ##1.%
%
edeffoo%
unexpandedexpandafter%
foo
defbat#1argument of bat: #1.%
%
%
showfoo
documentclassarticle
usepackagekeyval
makeatletter
%%----------------------------------------------------------------------
newcommandUD@firstofone[1]#1%
newcommandUD@firstoftwo[2]#1%
newcommandUD@secondoftwo[2]#2%
newcommandUD@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>
newcommandUD@CheckWhetherNull[1]%
romannumeral0expandafterUD@secondoftwostringexpandafter
UD@secondoftwoexpandafterexpandafterstring#1expandafter
UD@secondoftwostringexpandafterUD@firstoftwoexpandafter%
expandafterUD@secondoftwostringexpandafterexpandafter
UD@firstoftwo UD@secondoftwoexpandafterexpandafter
UD@firstoftwo UD@firstoftwo%
%
%%
%% A variant using e-TeX extensions could be:
%%
%%newcommandUD@CheckWhetherNull[1]%
%% romannumeral0ifrelaxdetokenize#1relax
%% expandafterUD@firstoftwoelseexpandafterUD@secondoftwofi
%% UD@firstoftwoexpandafter UD@firstoftwo%
%% UD@firstoftwoexpandafter UD@secondoftwo%
%%%
%%----------------------------------------------------------------------
%% AppendTokensToMacrotokensmacro%
%% 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.)
%%----------------------------------------------------------------------
newcommandAppendTokensToMacro[2]%
expandafterUD@Exchangeexpandafter%
expandaftertoks@expandafterthetoks@%
%
toks@expandafter#2#1%
edef#2thetoks@%
%
%
%%
%% A variant using e-TeX extensions could be:
%%
%%newcommandAppendTokensToMacro[2]%
%% edef#2unexpandedexpandafter#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?
newififDeliverErrMsgKeyANotProvided
DeliverErrMsgKeyANotProvidedtrue
%% Flag: Is there a need to print an error-message about keyB not
%% being set?
newififDeliverErrMsgKeyBNotProvided
DeliverErrMsgKeyBNotProvidedtrue
%% Flag: Is there a need to print an error-message about keyA
%% being provided multiple times?
newififDeliverErrMsgKeyAProvidedSeveralTimes
DeliverErrMsgKeyAProvidedSeveralTimesfalse
%% Flag: Is there a need to print an error-message about keyB
%% being provided multiple times?
newififDeliverErrMsgKeyBProvidedSeveralTimes
DeliverErrMsgKeyBProvidedSeveralTimesfalse
%% Flag: Is there a need to print an error-message about keyA
%% having a value which is not in the true/false-spectrum?
newififDeliverErrMsgKeyANeitherTrueNorFalse
DeliverErrMsgKeyANeitherTrueNorFalsefalse
%% Flag: Has keyA the value true?
newififKeyAsValueIsTrue
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%
spacespacespace@spaces@spaces@spaces
%
LaTeX Error: Inapproriate usage of macro string#1on@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:
%%----------------------------------------------------------------------
newcommandDeliverErrMsgKeyNeitherTrueNorFalse[1]%
PreambleMacroErrormacroInvalid value for #1%
%PackageErrorMyPackagestringmacro: Invalid value for #1on@line%
%@latex@errorstringmacro: Invalid value for #1on@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:
%%----------------------------------------------------------------------
newcommandDeliverErrMsgKeyNotProvided[1]%
PreambleMacroErrormacroSetting for #1 is missing%
%PackageErrorMyPackagestringmacro: Setting for #1 is missingon@line%
%@latex@errorstringmacro: Setting for #1 is missingon@line%
%
Setting #1 cannot be omitted.%
%
%
%%----------------------------------------------------------------------
%% Error-message in case a keys is set more times than once:
%%----------------------------------------------------------------------
newcommandDeliverErrMsgKeyProvidedSeveralTimes[1]%
PreambleMacroErrormacroMore than one value for #1%
%PackageErrorMyPackagestringmacro: More than one value for #1on@line%
%@latex@errorstringmacro: More than one value for #1on@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>%
%%
newcommandUD@GobbleToExclamlongdefUD@GobbleToExclam#1!%
newcommandUD@CheckWhetherNoExclam[1]%
expandafterUD@CheckWhetherNullexpandafterUD@GobbleToExclam#1!%
%
newcommandTrueFalseFork
longdefTrueFalseFork#1!!true!yes!on!1!false!no!off!0!#2#3!!!!#2%
newcommandDetectYesNo[1]lowercaseInnerDetectYesNo#1%
newcommandInnerDetectYesNo[4]%
romannumeral0UD@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@keyMyFamilykeyA[true]%
DeliverErrMsgKeyANotProvidedfalse
define@keyMyFamilykeyA[true]%
DeliverErrMsgKeyAProvidedSeveralTimestrue
%
DetectYesNo#1%
DeliverErrMsgKeyANeitherTrueNorFalsetrue%
KeyAsValueIsTruetrue%
KeyAsValueIsTruefalse%
%
define@keyMyFamilykeyB%
DeliverErrMsgKeyBNotProvidedfalse
define@keyMyFamilykeyB%
DeliverErrMsgKeyBProvidedSeveralTimestrue
%
AppendTokensToMacro#1MyKeyBvalue%
%
%%----------------------------------------------------------------------
%% Now the macro with evaluation and error-messages ad nauseam ;-) :
%%----------------------------------------------------------------------
newcommandmacro[1]%
begingroup
setkeysMyFamily#1%
%
% Print Error-Messages if necessary:
%
ifDeliverErrMsgKeyANeitherTrueNorFalseDeliverErrMsgKeyNeitherTrueNorFalsekeyAfi
ifDeliverErrMsgKeyANotProvidedDeliverErrMsgKeyNotProvidedkeyAfi
ifDeliverErrMsgKeyBNotProvidedDeliverErrMsgKeyNotProvidedkeyBfi
ifDeliverErrMsgKeyAProvidedSeveralTimesDeliverErrMsgKeyProvidedSeveralTimeskeyAfi
ifDeliverErrMsgKeyBProvidedSeveralTimesDeliverErrMsgKeyProvidedSeveralTimeskeyBfi
%
% Perform the adding to FOO or BAR in case no error-messages were
% printed:
%
ifDeliverErrMsgKeyANotProvidedexpandafterUD@secondoftwoelseexpandafterUD@firstofonefi%
ifDeliverErrMsgKeyBNotProvidedexpandafterUD@secondoftwoelseexpandafterUD@firstofonefi%
ifDeliverErrMsgKeyAProvidedSeveralTimesexpandafterUD@secondoftwoelseexpandafterUD@firstofonefi%
ifDeliverErrMsgKeyBProvidedSeveralTimesexpandafterUD@secondoftwoelseexpandafterUD@firstofonefi%
ifDeliverErrMsgKeyANeitherTrueNorFalseexpandafterUD@secondoftwoelseexpandafterUD@firstoftwofi%
ifKeyAsValueIsTrueKeyAsValueIsTruetrueexpandafterUD@firstoftwoelseexpandafterUD@secondoftwofi%
expandafterendgroupexpandafterAppendTokensToMacroexpandafterexpandafter(MyKeyBvalue)FOO%
%
expandafterendgroupexpandafterAppendTokensToMacroexpandafterexpandafter[MyKeyBvalue]BAR%
%
%
%
%
%
%
endgroup
%
makeatother
begindocument
noindent
verb|macrokeyA=true, keyB=Value in first call| - now you have:\
macrokeyA=true, keyB=Value in first call%
textttstringFOO: meaningFOO\stringBAR: meaningBAR\%
%
nullhrulefillnull\
verb|macrokeyA=yes, keyB=Value in second call| - now you have:\
macrokeyA=yes, keyB=Value in second call%
textttstringFOO: meaningFOO\stringBAR: meaningBAR\%
%
nullhrulefillnull\
verb|macrokeyA=0, keyB=Value in third call| - now you have:\
macrokeyA=0, keyB=Value in third call%
textttstringFOO: meaningFOO\stringBAR: meaningBAR\%
%
nullhrulefillnull\
verb|macrokeyA=Off, keyB=Value in fourth call| - now you have:\
macrokeyA=Off, keyB=Value in fourth call%
textttstringFOO: meaningFOO\stringBAR: meaningBAR\%
%
nullhrulefillnull\
verb|macrokeyA, keyB=Value in fifth call| - now you have:\
macrokeyA, keyB=Value in fifth call%
textttstringFOO: meaningFOO\stringBAR: meaningBAR\%
%
% Let's trigger some error-messages:
%
%nullhrulefillnull\
%verb|macrokeyA=woozle, | - now you have:\
%macrokeyA=woozle, %
%textttstringFOO: meaningFOO\stringBAR: meaningBAR\%
%
%nullhrulefillnull\
%verb|macrokeyA=1, keyA=false, keyB=Value in sixth call| - now you have:\
%macrokeyA=1, keyA=false, keyB=Value in sixth call%
%textttstringFOO: meaningFOO\stringBAR: meaningBAR\%
enddocument
Some remarks not related to the problem which nonetheless may be useful:
While line-wise and then character-wise reading/processing a .tex-input-file, (La)TeX usually will first remove all space characters (ASCII 32) at the right end of a line of input and then append a return character (ASCII 13) at the right end of that line of input. This is due to the integer parameter
endlinechar
usually having the value 13. Then (La)TeX will "look" at that line of input character by character and hereby take that line for a set of instructions for appending tokens (character tokens and control sequence tokens) to the token stream. The return character usually has category code 5 (end of line). After tokenizing a curly brace (or
), (La)TeX's reading apparatus usually is in state M (middle of line). (Generally (La)TeX's reading apparatus will be in state M after tokenizing a control symbol token (other than control space; a control symbol token is a control sequence token whose name consists of a single character which does not have category code 11(letter)) or a character-token other than a space token.)
When (La)TeX encounters a character of category code 5 (end of line) while in state M, (La)TeX will append a space token (character token of category code 10(space) and character code 32) to the token stream.
In (restricted) horizontal mode such a space token may yield visible horizontal glue.Thus: Make sure that lines of input, where the last thing that during reading and tokenizing shall be appended to the token stream either is a control symbol token (control sequence token whose name consists o a single character which is not of category code 11(letter)) or is a character token, end with a comment character (
%
) in situations where such horizontal glue is undesired.
As a rule of thumb make sure that lines of input, where the last thing that shall be appended to the token stream is some brace token (-character-token of category code 1 (begin group) or
-character-token of category code 2 (end group)), end with a comment-character (
%
).bar
is already defined in LaTeX2e. I suggest not to override it. Therefore in the example below for the names of the corresponding macros I use the all caps variantsFOO
andBAR
.
Some remarks related to the problem which probably may be useful:
With the keyval package you cannot have the "function" underlying keyB act depending on the value of keyA/depending on the result of carrying out the "function" underlying keyA.
As you stated yourself:
The order in which these "functions" get carried out is not predictable.
Also it is not predictable whether these "functions" get carried out at all or get carried out more than once.
Instead you can define a family of keys with underlying "functions" for setting some flags (which is a method of storing true/false-values or 0/1-values) and storing as macros other values that are to be used while/after evaluating the flags.
Then you can define a macro which does setkeys
for setting flags and storing values and then does evaluate the flags for printing error-messages and in case of not having printed any error message processing the stored values accordingly.
In the example below as flags I use switches created via newif
. I do so because with that example I don't wish LaTeX to load a large amount of packages. For setting flags, the packages flags and bitset, both of Heiko Oberdiek, may be of interest to you.
Processing comma separated key-value-lists is about processing macro arguments.
Braces need to be balanced with macro arguments.
Therefore I assume that the "arbitrary values" for keyB are sequences of tokens which are brace balanced. ;-)
When storing such (almost) arbitrary token sequences as macros, you need to make sure that hashes (#
) get doubled during the process of storing. The reason is: You retrieve these token sequences by expanding these macros, while during macro expansion two hashes (##
) will collapse into one which means that during macro expansion the amount of hashes will be halved.
For achieving the doubling of hashes you can use the fact that when the
delivers the content of a token register during an edef
, the hashes therein will be doubled.
In the example below LaTeX 2e's reserved scratch token register toks@
is used according to the pattern toks@⟨tokens⟩...edefmacrothetoks@
.
If e-TeX extensions are available, you can do edefmacrounexpanded⟨tokens⟩
as shown by Phelype Oleinik as unexpanded
also doubles hashes when carried out during edef
.
You also need forking depending on the phrase/depending on the token sequence provided as value to keyA:
In the example below you find DetectYesNo
which does the forking by means of delimited macro arguments.
You wish to have (almost) arbitrary things appended to the definition text of the macro foo
.
When doing this, you need to make sure that the new definition text of foo
also contains the tokens that form the old definition text of foo
by top-level-expanding foo
.
You could be tempted to do:
expandafterdefexpandafterfooexpandafterfoo⟨tokens to append⟩
But you need to be careful with such approaches as the amount of hashes will be halved during expansion of foo
. This may lead to problems.
If, e.g., foo
is defined via
deffoo%
defbas##1argument of bas: ##1.%
and you do:
expandafterdefexpandafterfooexpandafter%
foo
defbat##1argument of bat: ##1.%
, then this will not yield
deffoo%
defbas##1argument of bas: ##1.%
defbat##1argument of bat: ##1.%
, but it will yield:
deffoo%
defbas#1argument of bas: #1.%
defbat##1argument of bat: ##1.%
This will be a problem and therefore will yield an error message as foo
does not process parameters while foo
's definition text does contain #1
.
For achieving the hash-doubling you can again apply either the
-expansion on a token register (without e-TeX extensions) or unexpanded
(this requires e-TeX extensions) during edef
, but this time you need to combine that with expandafter
for obtaining the expansion of foo
:
Without e-TeX extensions, using LaTeX 2e's scratch token register toks@
, you can do something like:
deffoo%
defbas##1argument of bas: ##1.%
%
% Now let's apply expandafter for obtaining the expansion of `foo`:
toks@expandafter%
foo
defbat#1argument of bat: #1.%
%
edeffoothetoks@%
showfoo
But the scratch token register toks@
is reserved for the LaTeX 2e-kernel. This means that it should be reset before any token defined in the kernel will be carried out. This can be achieved by adding a bit of exchange
-trickery:
longdefexchange#1#2#2#1%
deffoo%
defbas##1argument of bas: ##1.%
%
expandafterexchangeexpandafter%
expandaftertoks@expandafterthetoks@%
%
% Now let's apply expandafter for obtaining the expansion of `foo`:
toks@expandafter%
foo
defbat#1argument of bat: #1.%
%
edeffoothetoks@%
%
showfoo
When e-TeX extensions are available, you can use a combination of edef
, expandafter
and unexpanded
as shown in the answer of Phelype Oleinik:
deffoo%
defbas##1argument of bas: ##1.%
%
edeffoo%
unexpandedexpandafter%
foo
defbat#1argument of bat: #1.%
%
%
showfoo
documentclassarticle
usepackagekeyval
makeatletter
%%----------------------------------------------------------------------
newcommandUD@firstofone[1]#1%
newcommandUD@firstoftwo[2]#1%
newcommandUD@secondoftwo[2]#2%
newcommandUD@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>
newcommandUD@CheckWhetherNull[1]%
romannumeral0expandafterUD@secondoftwostringexpandafter
UD@secondoftwoexpandafterexpandafterstring#1expandafter
UD@secondoftwostringexpandafterUD@firstoftwoexpandafter%
expandafterUD@secondoftwostringexpandafterexpandafter
UD@firstoftwo UD@secondoftwoexpandafterexpandafter
UD@firstoftwo UD@firstoftwo%
%
%%
%% A variant using e-TeX extensions could be:
%%
%%newcommandUD@CheckWhetherNull[1]%
%% romannumeral0ifrelaxdetokenize#1relax
%% expandafterUD@firstoftwoelseexpandafterUD@secondoftwofi
%% UD@firstoftwoexpandafter UD@firstoftwo%
%% UD@firstoftwoexpandafter UD@secondoftwo%
%%%
%%----------------------------------------------------------------------
%% AppendTokensToMacrotokensmacro%
%% 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.)
%%----------------------------------------------------------------------
newcommandAppendTokensToMacro[2]%
expandafterUD@Exchangeexpandafter%
expandaftertoks@expandafterthetoks@%
%
toks@expandafter#2#1%
edef#2thetoks@%
%
%
%%
%% A variant using e-TeX extensions could be:
%%
%%newcommandAppendTokensToMacro[2]%
%% edef#2unexpandedexpandafter#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?
newififDeliverErrMsgKeyANotProvided
DeliverErrMsgKeyANotProvidedtrue
%% Flag: Is there a need to print an error-message about keyB not
%% being set?
newififDeliverErrMsgKeyBNotProvided
DeliverErrMsgKeyBNotProvidedtrue
%% Flag: Is there a need to print an error-message about keyA
%% being provided multiple times?
newififDeliverErrMsgKeyAProvidedSeveralTimes
DeliverErrMsgKeyAProvidedSeveralTimesfalse
%% Flag: Is there a need to print an error-message about keyB
%% being provided multiple times?
newififDeliverErrMsgKeyBProvidedSeveralTimes
DeliverErrMsgKeyBProvidedSeveralTimesfalse
%% Flag: Is there a need to print an error-message about keyA
%% having a value which is not in the true/false-spectrum?
newififDeliverErrMsgKeyANeitherTrueNorFalse
DeliverErrMsgKeyANeitherTrueNorFalsefalse
%% Flag: Has keyA the value true?
newififKeyAsValueIsTrue
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%
spacespacespace@spaces@spaces@spaces
%
LaTeX Error: Inapproriate usage of macro string#1on@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:
%%----------------------------------------------------------------------
newcommandDeliverErrMsgKeyNeitherTrueNorFalse[1]%
PreambleMacroErrormacroInvalid value for #1%
%PackageErrorMyPackagestringmacro: Invalid value for #1on@line%
%@latex@errorstringmacro: Invalid value for #1on@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:
%%----------------------------------------------------------------------
newcommandDeliverErrMsgKeyNotProvided[1]%
PreambleMacroErrormacroSetting for #1 is missing%
%PackageErrorMyPackagestringmacro: Setting for #1 is missingon@line%
%@latex@errorstringmacro: Setting for #1 is missingon@line%
%
Setting #1 cannot be omitted.%
%
%
%%----------------------------------------------------------------------
%% Error-message in case a keys is set more times than once:
%%----------------------------------------------------------------------
newcommandDeliverErrMsgKeyProvidedSeveralTimes[1]%
PreambleMacroErrormacroMore than one value for #1%
%PackageErrorMyPackagestringmacro: More than one value for #1on@line%
%@latex@errorstringmacro: More than one value for #1on@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>%
%%
newcommandUD@GobbleToExclamlongdefUD@GobbleToExclam#1!%
newcommandUD@CheckWhetherNoExclam[1]%
expandafterUD@CheckWhetherNullexpandafterUD@GobbleToExclam#1!%
%
newcommandTrueFalseFork
longdefTrueFalseFork#1!!true!yes!on!1!false!no!off!0!#2#3!!!!#2%
newcommandDetectYesNo[1]lowercaseInnerDetectYesNo#1%
newcommandInnerDetectYesNo[4]%
romannumeral0UD@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@keyMyFamilykeyA[true]%
DeliverErrMsgKeyANotProvidedfalse
define@keyMyFamilykeyA[true]%
DeliverErrMsgKeyAProvidedSeveralTimestrue
%
DetectYesNo#1%
DeliverErrMsgKeyANeitherTrueNorFalsetrue%
KeyAsValueIsTruetrue%
KeyAsValueIsTruefalse%
%
define@keyMyFamilykeyB%
DeliverErrMsgKeyBNotProvidedfalse
define@keyMyFamilykeyB%
DeliverErrMsgKeyBProvidedSeveralTimestrue
%
AppendTokensToMacro#1MyKeyBvalue%
%
%%----------------------------------------------------------------------
%% Now the macro with evaluation and error-messages ad nauseam ;-) :
%%----------------------------------------------------------------------
newcommandmacro[1]%
begingroup
setkeysMyFamily#1%
%
% Print Error-Messages if necessary:
%
ifDeliverErrMsgKeyANeitherTrueNorFalseDeliverErrMsgKeyNeitherTrueNorFalsekeyAfi
ifDeliverErrMsgKeyANotProvidedDeliverErrMsgKeyNotProvidedkeyAfi
ifDeliverErrMsgKeyBNotProvidedDeliverErrMsgKeyNotProvidedkeyBfi
ifDeliverErrMsgKeyAProvidedSeveralTimesDeliverErrMsgKeyProvidedSeveralTimeskeyAfi
ifDeliverErrMsgKeyBProvidedSeveralTimesDeliverErrMsgKeyProvidedSeveralTimeskeyBfi
%
% Perform the adding to FOO or BAR in case no error-messages were
% printed:
%
ifDeliverErrMsgKeyANotProvidedexpandafterUD@secondoftwoelseexpandafterUD@firstofonefi%
ifDeliverErrMsgKeyBNotProvidedexpandafterUD@secondoftwoelseexpandafterUD@firstofonefi%
ifDeliverErrMsgKeyAProvidedSeveralTimesexpandafterUD@secondoftwoelseexpandafterUD@firstofonefi%
ifDeliverErrMsgKeyBProvidedSeveralTimesexpandafterUD@secondoftwoelseexpandafterUD@firstofonefi%
ifDeliverErrMsgKeyANeitherTrueNorFalseexpandafterUD@secondoftwoelseexpandafterUD@firstoftwofi%
ifKeyAsValueIsTrueKeyAsValueIsTruetrueexpandafterUD@firstoftwoelseexpandafterUD@secondoftwofi%
expandafterendgroupexpandafterAppendTokensToMacroexpandafterexpandafter(MyKeyBvalue)FOO%
%
expandafterendgroupexpandafterAppendTokensToMacroexpandafterexpandafter[MyKeyBvalue]BAR%
%
%
%
%
%
%
endgroup
%
makeatother
begindocument
noindent
verb|macrokeyA=true, keyB=Value in first call| - now you have:\
macrokeyA=true, keyB=Value in first call%
textttstringFOO: meaningFOO\stringBAR: meaningBAR\%
%
nullhrulefillnull\
verb|macrokeyA=yes, keyB=Value in second call| - now you have:\
macrokeyA=yes, keyB=Value in second call%
textttstringFOO: meaningFOO\stringBAR: meaningBAR\%
%
nullhrulefillnull\
verb|macrokeyA=0, keyB=Value in third call| - now you have:\
macrokeyA=0, keyB=Value in third call%
textttstringFOO: meaningFOO\stringBAR: meaningBAR\%
%
nullhrulefillnull\
verb|macrokeyA=Off, keyB=Value in fourth call| - now you have:\
macrokeyA=Off, keyB=Value in fourth call%
textttstringFOO: meaningFOO\stringBAR: meaningBAR\%
%
nullhrulefillnull\
verb|macrokeyA, keyB=Value in fifth call| - now you have:\
macrokeyA, keyB=Value in fifth call%
textttstringFOO: meaningFOO\stringBAR: meaningBAR\%
%
% Let's trigger some error-messages:
%
%nullhrulefillnull\
%verb|macrokeyA=woozle, | - now you have:\
%macrokeyA=woozle, %
%textttstringFOO: meaningFOO\stringBAR: meaningBAR\%
%
%nullhrulefillnull\
%verb|macrokeyA=1, keyA=false, keyB=Value in sixth call| - now you have:\
%macrokeyA=1, keyA=false, keyB=Value in sixth call%
%textttstringFOO: meaningFOO\stringBAR: meaningBAR\%
enddocument
edited May 26 at 15:57
answered May 25 at 14:11
Ulrich DiezUlrich Diez
6,217621
6,217621
add a comment |
add a comment |
The foo
in foo
recursion you mentioned happens because when you do deffoofoo(something else)
, the inner foo
is not expanded. If you were yo use that foo
TeX would soon explode: TeX capacity exceeded, sorry [input stack size=5000]
.
To have foo
contain whatever foo
was, plus the new contents, you can use edef
(e
xpand def
) and unexpanded
, like this:
edeffoounexpandedexpandafterfoo(more stuff)
then foo
will be (something else)(more stuff)
.
Now, the keyA=false
wasn't working because you defined keyA
with:
define@keyMyFamilykeyA[true]keyAtruetrue
so it wouldn't matter which value you passed to keyA
, at the end it would execute keyAtruetrue
, which is not what you wanted.
I defined a simple teststring<str a><str b><true><false>
macro for you which compares the two strings <str a>
and <str b>
then returns <true>
if they are equal or <false>
if they are not. Then I changed the definition of keyA
to cope with all the possibilities true/yes/on/1 (yes-branch)
and false/no/off/0 (no-branch)
.
I also added a couple of %
at the end of the lines to avoid spurious spaces.
And, as Ulrich Diez mentions in his answer (and I completely overlooked) you should be careful when using def
because you can override commands without knowing. I replaced bar
by rab
(I'm not creative :)
When running the code I got this in the terminal:
> foo=macro:
->(first value).
l.51 showfoo
showrab
?
> rab=macro:
->.
l.51 showfooshowrab
?
> foo=macro:
->(first value).
l.55 showfoo
showrab
?
> rab=macro:
->[second value].
l.55 showfooshowrab
?
> foo=macro:
->(first value).
l.59 showfoo
showrab
?
> rab=macro:
->[second value][third value].
l.59 showfooshowrab
?
Full code:
documentclassarticle
usepackagekeyval
deffoo
defrab
newififkeyAtruekeyAtruefalse
makeatletter
defteststring#1#2%
edef@tempadetokenize#1%
edef@tempbdetokenize#2%
ifx@tempa@tempb
@valid@keytrue
expandafter@firstoftwo
else
expandafter@secondoftwo
fi
newifif@valid@key
define@keyMyFamilykeyA[true]%
@valid@keyfalse
teststring#1truekeyAtruetrue
teststring#1yeskeyAtruetrue
teststring#1onkeyAtruetrue
teststring#11keyAtruetrue
%
unlessif@valid@key
teststring#1falsekeyAtruefalse
teststring#1nokeyAtruefalse
teststring#1offkeyAtruefalse
teststring#10keyAtruefalse
%
fi
unlessif@valid@key
PackageErrorInvalid option `#1' for keyA
fi
define@keyMyFamilykeyB% <--
ifkeyAtrue
edeffoounexpandedexpandafterfoo(#1)% <--
else
edefrabunexpandedexpandafterrab[#1]% <--
fi
makeatother
newcommandmacro[1]% <--
setkeysMyFamily#1% <--
keyAtruefalse
begindocument
% This almost works but there is 'foo in foo'-recursion.
macrokeyA=true,keyB=first value
showfooshowrab
% This almost works but there is 'foo in foo'-recursion and
% 'rab in rab'-recursion
macrokeyB=second value
showfooshowrab
% This dos not work at all. There is recursions and added to foo, not to rab.
% tracingall
macrokeyA=false,keyB=third value
showfooshowrab
enddocument
add a comment |
The foo
in foo
recursion you mentioned happens because when you do deffoofoo(something else)
, the inner foo
is not expanded. If you were yo use that foo
TeX would soon explode: TeX capacity exceeded, sorry [input stack size=5000]
.
To have foo
contain whatever foo
was, plus the new contents, you can use edef
(e
xpand def
) and unexpanded
, like this:
edeffoounexpandedexpandafterfoo(more stuff)
then foo
will be (something else)(more stuff)
.
Now, the keyA=false
wasn't working because you defined keyA
with:
define@keyMyFamilykeyA[true]keyAtruetrue
so it wouldn't matter which value you passed to keyA
, at the end it would execute keyAtruetrue
, which is not what you wanted.
I defined a simple teststring<str a><str b><true><false>
macro for you which compares the two strings <str a>
and <str b>
then returns <true>
if they are equal or <false>
if they are not. Then I changed the definition of keyA
to cope with all the possibilities true/yes/on/1 (yes-branch)
and false/no/off/0 (no-branch)
.
I also added a couple of %
at the end of the lines to avoid spurious spaces.
And, as Ulrich Diez mentions in his answer (and I completely overlooked) you should be careful when using def
because you can override commands without knowing. I replaced bar
by rab
(I'm not creative :)
When running the code I got this in the terminal:
> foo=macro:
->(first value).
l.51 showfoo
showrab
?
> rab=macro:
->.
l.51 showfooshowrab
?
> foo=macro:
->(first value).
l.55 showfoo
showrab
?
> rab=macro:
->[second value].
l.55 showfooshowrab
?
> foo=macro:
->(first value).
l.59 showfoo
showrab
?
> rab=macro:
->[second value][third value].
l.59 showfooshowrab
?
Full code:
documentclassarticle
usepackagekeyval
deffoo
defrab
newififkeyAtruekeyAtruefalse
makeatletter
defteststring#1#2%
edef@tempadetokenize#1%
edef@tempbdetokenize#2%
ifx@tempa@tempb
@valid@keytrue
expandafter@firstoftwo
else
expandafter@secondoftwo
fi
newifif@valid@key
define@keyMyFamilykeyA[true]%
@valid@keyfalse
teststring#1truekeyAtruetrue
teststring#1yeskeyAtruetrue
teststring#1onkeyAtruetrue
teststring#11keyAtruetrue
%
unlessif@valid@key
teststring#1falsekeyAtruefalse
teststring#1nokeyAtruefalse
teststring#1offkeyAtruefalse
teststring#10keyAtruefalse
%
fi
unlessif@valid@key
PackageErrorInvalid option `#1' for keyA
fi
define@keyMyFamilykeyB% <--
ifkeyAtrue
edeffoounexpandedexpandafterfoo(#1)% <--
else
edefrabunexpandedexpandafterrab[#1]% <--
fi
makeatother
newcommandmacro[1]% <--
setkeysMyFamily#1% <--
keyAtruefalse
begindocument
% This almost works but there is 'foo in foo'-recursion.
macrokeyA=true,keyB=first value
showfooshowrab
% This almost works but there is 'foo in foo'-recursion and
% 'rab in rab'-recursion
macrokeyB=second value
showfooshowrab
% This dos not work at all. There is recursions and added to foo, not to rab.
% tracingall
macrokeyA=false,keyB=third value
showfooshowrab
enddocument
add a comment |
The foo
in foo
recursion you mentioned happens because when you do deffoofoo(something else)
, the inner foo
is not expanded. If you were yo use that foo
TeX would soon explode: TeX capacity exceeded, sorry [input stack size=5000]
.
To have foo
contain whatever foo
was, plus the new contents, you can use edef
(e
xpand def
) and unexpanded
, like this:
edeffoounexpandedexpandafterfoo(more stuff)
then foo
will be (something else)(more stuff)
.
Now, the keyA=false
wasn't working because you defined keyA
with:
define@keyMyFamilykeyA[true]keyAtruetrue
so it wouldn't matter which value you passed to keyA
, at the end it would execute keyAtruetrue
, which is not what you wanted.
I defined a simple teststring<str a><str b><true><false>
macro for you which compares the two strings <str a>
and <str b>
then returns <true>
if they are equal or <false>
if they are not. Then I changed the definition of keyA
to cope with all the possibilities true/yes/on/1 (yes-branch)
and false/no/off/0 (no-branch)
.
I also added a couple of %
at the end of the lines to avoid spurious spaces.
And, as Ulrich Diez mentions in his answer (and I completely overlooked) you should be careful when using def
because you can override commands without knowing. I replaced bar
by rab
(I'm not creative :)
When running the code I got this in the terminal:
> foo=macro:
->(first value).
l.51 showfoo
showrab
?
> rab=macro:
->.
l.51 showfooshowrab
?
> foo=macro:
->(first value).
l.55 showfoo
showrab
?
> rab=macro:
->[second value].
l.55 showfooshowrab
?
> foo=macro:
->(first value).
l.59 showfoo
showrab
?
> rab=macro:
->[second value][third value].
l.59 showfooshowrab
?
Full code:
documentclassarticle
usepackagekeyval
deffoo
defrab
newififkeyAtruekeyAtruefalse
makeatletter
defteststring#1#2%
edef@tempadetokenize#1%
edef@tempbdetokenize#2%
ifx@tempa@tempb
@valid@keytrue
expandafter@firstoftwo
else
expandafter@secondoftwo
fi
newifif@valid@key
define@keyMyFamilykeyA[true]%
@valid@keyfalse
teststring#1truekeyAtruetrue
teststring#1yeskeyAtruetrue
teststring#1onkeyAtruetrue
teststring#11keyAtruetrue
%
unlessif@valid@key
teststring#1falsekeyAtruefalse
teststring#1nokeyAtruefalse
teststring#1offkeyAtruefalse
teststring#10keyAtruefalse
%
fi
unlessif@valid@key
PackageErrorInvalid option `#1' for keyA
fi
define@keyMyFamilykeyB% <--
ifkeyAtrue
edeffoounexpandedexpandafterfoo(#1)% <--
else
edefrabunexpandedexpandafterrab[#1]% <--
fi
makeatother
newcommandmacro[1]% <--
setkeysMyFamily#1% <--
keyAtruefalse
begindocument
% This almost works but there is 'foo in foo'-recursion.
macrokeyA=true,keyB=first value
showfooshowrab
% This almost works but there is 'foo in foo'-recursion and
% 'rab in rab'-recursion
macrokeyB=second value
showfooshowrab
% This dos not work at all. There is recursions and added to foo, not to rab.
% tracingall
macrokeyA=false,keyB=third value
showfooshowrab
enddocument
The foo
in foo
recursion you mentioned happens because when you do deffoofoo(something else)
, the inner foo
is not expanded. If you were yo use that foo
TeX would soon explode: TeX capacity exceeded, sorry [input stack size=5000]
.
To have foo
contain whatever foo
was, plus the new contents, you can use edef
(e
xpand def
) and unexpanded
, like this:
edeffoounexpandedexpandafterfoo(more stuff)
then foo
will be (something else)(more stuff)
.
Now, the keyA=false
wasn't working because you defined keyA
with:
define@keyMyFamilykeyA[true]keyAtruetrue
so it wouldn't matter which value you passed to keyA
, at the end it would execute keyAtruetrue
, which is not what you wanted.
I defined a simple teststring<str a><str b><true><false>
macro for you which compares the two strings <str a>
and <str b>
then returns <true>
if they are equal or <false>
if they are not. Then I changed the definition of keyA
to cope with all the possibilities true/yes/on/1 (yes-branch)
and false/no/off/0 (no-branch)
.
I also added a couple of %
at the end of the lines to avoid spurious spaces.
And, as Ulrich Diez mentions in his answer (and I completely overlooked) you should be careful when using def
because you can override commands without knowing. I replaced bar
by rab
(I'm not creative :)
When running the code I got this in the terminal:
> foo=macro:
->(first value).
l.51 showfoo
showrab
?
> rab=macro:
->.
l.51 showfooshowrab
?
> foo=macro:
->(first value).
l.55 showfoo
showrab
?
> rab=macro:
->[second value].
l.55 showfooshowrab
?
> foo=macro:
->(first value).
l.59 showfoo
showrab
?
> rab=macro:
->[second value][third value].
l.59 showfooshowrab
?
Full code:
documentclassarticle
usepackagekeyval
deffoo
defrab
newififkeyAtruekeyAtruefalse
makeatletter
defteststring#1#2%
edef@tempadetokenize#1%
edef@tempbdetokenize#2%
ifx@tempa@tempb
@valid@keytrue
expandafter@firstoftwo
else
expandafter@secondoftwo
fi
newifif@valid@key
define@keyMyFamilykeyA[true]%
@valid@keyfalse
teststring#1truekeyAtruetrue
teststring#1yeskeyAtruetrue
teststring#1onkeyAtruetrue
teststring#11keyAtruetrue
%
unlessif@valid@key
teststring#1falsekeyAtruefalse
teststring#1nokeyAtruefalse
teststring#1offkeyAtruefalse
teststring#10keyAtruefalse
%
fi
unlessif@valid@key
PackageErrorInvalid option `#1' for keyA
fi
define@keyMyFamilykeyB% <--
ifkeyAtrue
edeffoounexpandedexpandafterfoo(#1)% <--
else
edefrabunexpandedexpandafterrab[#1]% <--
fi
makeatother
newcommandmacro[1]% <--
setkeysMyFamily#1% <--
keyAtruefalse
begindocument
% This almost works but there is 'foo in foo'-recursion.
macrokeyA=true,keyB=first value
showfooshowrab
% This almost works but there is 'foo in foo'-recursion and
% 'rab in rab'-recursion
macrokeyB=second value
showfooshowrab
% This dos not work at all. There is recursions and added to foo, not to rab.
% tracingall
macrokeyA=false,keyB=third value
showfooshowrab
enddocument
edited May 25 at 14:33
answered May 25 at 13:56
Phelype OleinikPhelype Oleinik
29.5k64897
29.5k64897
add a comment |
add a comment |
Thanks for contributing an answer to TeX - LaTeX Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2ftex.stackexchange.com%2fquestions%2f492597%2fkeyval-function-for-keyb-should-act-dependent-on-value-of-keya-how-to-do-thi%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown