Processing key/value options with many non-predefined keys

Say I want to define a command \defineaspect that takes a key/value option list to associate values with keys that are not known in advance. For example, to associate certain aspects with a list of persons, a use of that command might look like

\defineaspect{forename}{
.print = {},
sherlock = Sherlock,
jim = James
}
\defineaspect{surname}{
.print = \scshape,
sherlock = Holmes,
jim = Moriarty
}


sherlock and jim should then be treated kind of like primary keys in a database table with the various aspects being further columns in that table. Additionally, some actually predefined keys might occur in this list too, like .print in the example.

I had a glance at some of the packages for key/value processing, but as far as I can see having a lot of non-predefined keys is not a typical use case for these packages.

Is there a key/value package that provides easy handling of such keys? Or should I better use a custom parser that splits the list at , first and then parses each key/value pair separately?

• These look like property lists to me: is every value 'some tokens to store'? Jul 7, 2019 at 8:48
• Can't the parsing be easily done with l3keys using unknown .code:n = { ... } for the keys that aren't know in advance, and normal handling for the other keys? Jul 7, 2019 at 13:32
• As Joseph said, something like \NewDocumentCommand \defineaspect { mm } { \prop_gset_from_keyval:cn { g_siracusa_#1_aspect_prop } {#2} } should do what you want. Jul 7, 2019 at 14:12
• @frougon Thanks, this approach also seems to work. I misunderstood the documentation in that the unknown key were only meant for a custom error message. Jul 8, 2019 at 0:34
• There is no need to implement a parser splitting at , and parsing a key=value pair, \keyval_parse:NNn and \ekvparse (from expkv) do exactly that for you so that you can provide two macros which should be called each element of your key=value list (one if no value was given for an element, one if a value was given). Dec 6, 2020 at 13:37

You can exploit expl3's key-value system and the prop data type. To turn a key-value list into an expl3 property list you can use \prop_set_from_keyval:Nn. The function will get each key = value pair in #2 and add to the prop list in #1. A sample document in this case would be:

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand \defineaspect { mm }
{
\prop_new:c { g__siracusa_#1_aspect_prop }
\prop_gset_from_keyval:cn { g__siracusa_#1_aspect_prop } {#2}
}
%
\tl_new:N \l__siracusa_tmpa_tl
\NewDocumentCommand \getaspect { mm }
{
\prop_get:cnNTF { g__siracusa_#1_aspect_prop } {#2} \l__siracusa_tmpa_tl
{ \tl_use:N \l__siracusa_tmpa_tl }
{ Aspect~#2~does~not~exist~in~#1! }
}
\NewDocumentCommand \fullname { m }
{
{ \getaspect { forename } { .print } \getaspect { forename } {#1} ~ }
{ \getaspect { surname  } { .print } \getaspect { surname  } {#1} }
}
\ExplSyntaxOff
\defineaspect{forename}{
.print = {},
sherlock = Sherlock,
jim = James
}
\defineaspect{surname}{
.print = \scshape,
sherlock = Holmes,
jim = Moriarty
}
\begin{document}
Did you miss me? --- \fullname{jim}
\end{document}


However this does a one-to-one translation of the input to the data structure. If you need a finer-grained control of that process, say, to differentiate simple keys from key from "special" keys, like .print, then you could use \keyval_parse:NNn.

\keyval_parse:NNn takes two functions and a key-value list as argument. \keyval_parse:NNn iterates through the key-value list in #3 and uses function #1 or #2 in each item of the list depending if the item has only a key or a key and a value.

A reimplementation of the \defineaspect above would become:

\prop_new:N \l__siracusa_tmpa_prop
\NewDocumentCommand \defineaspect { mm }
{
\prop_clear:N \l__siracusa_tmpa_prop
\keyval_parse:NNn \__siracusa_aspect:n \__siracusa_aspect:nn {#2}
\prop_new:c { g__siracusa_#1_aspect_prop }
\prop_gset_eq:cN { g__siracusa_#1_aspect_prop } \l__siracusa_tmpa_prop
}
\cs_new_protected:Npn \__siracusa_aspect:n #1
{ \__kernel_msg_error:nnn { kernel } { prop-keyval } {#1} }
\cs_new_protected:Npn \__siracusa_aspect:nn #1#2
{ \prop_put:Nnn \l__siracusa_tmpa_prop {#1} {#2} }


The error message in the definition of \__siracusa_aspect:n is there only to mimick the behaviour of \prop_set_from_keyval:Nn, but of course you can change that to the appropriate action when only a key is given.

• Thanks for mentioning \keyval_parse:NNn. I believe the second code sample would appreciate \prop_gset_eq:cN and \prop_put:Nnn, wouldn't it? ;-) Jul 8, 2019 at 20:55
• @frougon Ouch :| Fixed. Thanks for pointing it out :-) Jul 8, 2019 at 21:01