add tag
निरंजन
I am trying to setup a key-value interface for selecting fonts for different fields. I am using `expkv-def` along with some l3 functions. Here is what I develop keys:


```
\documentclass{article}
\usepackage{fontspec}
\usepackage{expkv-def}
\makeatletter
\ekvdefinekeys{foo}{%
  code    title     = {%
    \def\my@field{title}%
    \ekvset{bar}{#1}%
    \fontsettingcmd
  },%
  code    subtitle  = {%
    \def\my@field{subtitle}%
    \ekvset{bar}{#1}%
    \fontsettingcmd
  }%
}
\ekvdefinekeys{bar}{%
  code    rm        = {%
    \def\my@type{rm}%
    \my@fnt@processor{#1}%
  },%
  code    sf        = {%
    \def\my@type{sf}%
    \my@fnt@processor{#1}%
  },%
  code    tt        = {%
    \def\my@type{tt}%
    \my@fnt@processor{#1}%
  }%
}
\NewDocumentCommand \testfoo { m }{%
  \ekvset{foo}{#1}%
}
```

The LaTeX3 code for interpreting this data is as follows:

```
\ExplSyntaxOn
\seq_new:N \l__my_fnt_type_seq
\cs_new:Npn \my@fnt@processor #1 {
  \exp_after:wN \newfontfamily \cs:w
    my @ \my@field @ \my@type @ fnt
  \cs_end: [
    NFSSFamily        = {
      my @ \my@field @ \my@type @ nfss
    }
  ]{ #1 }
  \seq_put_right:Ne \l__my_fnt_type_seq { \my@type }
}
\cs_new:Npn \fontsettingcmd {
  \cs_gset:cpn { my @ \my@field @ fnts } {
    % As per https://tex.stackexchange.com/a/729805,
    % we need to generate the following macros:
    %   \def\rmdefault{my@title@rm@nfss}
    %   \def\sfdefault{my@title@sf@nfss}
    %   \def\ttdefault{my@title@tt@nfss}
    % Doesn't work, as of now.
    \seq_map_inline:Nn \l__my_fnt_type_seq {
      \cs_gset:cpe { ####1default } {
        my @ \my@field @ ####1 @ nfss
      }
    }
    \normalfont
  }
}
\ExplSyntaxOff
\makeatother
```

The objective is to make something like following work:

```
\begin{document}
\testfoo{%
  title             = {%
    rm                = {%
      NewCM10-Book.otf%
    },%
    sf                = {%
      NewCMSans10-Book.otf%
    },%
    tt                = {%
      NewCMMono10-Book.otf%
    }%
  },%
  subtitle          = {%
    rm                = {%
      KpRoman-Regular.otf%
    },%
    sf                = {%
      KpSans-Regular.otf%
    },%
    tt                = {%
      KpMono-Regular.otf%
    }%
  }%
}

\makeatletter
\my@title@fnts
\makeatother

\setlength{\parindent}{0pt}

{%
  \rmfamily
  \meaning\rmdefault
  
  test%
}

{%
  \sffamily
  \meaning\sfdefault
  
  test%
}

{%
  \ttfamily
  \meaning\ttdefault

  test%
}
\end{document}
```

As of now the families are set only for the last field, i.e., in this case `subtitle`. I need to eagerly expand the value `\my@field`, I tried `f` also instead of `e`, but no luck. What should I do?
Top Answer
Skillmon
There are a few issues in your code. Let's list them :)

- I'd not mix 2e-style internal code with L3, use `\ekvdefinekeys` inside the L3 scope instead of defining 2e-style macros in the L3 layer.
- `\exp_after:wN \<function> \cs:w <name> \cs_end:` is really bad L3 code, use expansion control where possible, only resort to `\exp_after:wN` and `\cs:w` in optimised low level code or where not otherwise possible. So use `\exp_args:Nc \<function> { <name> }` or, if `\<function>` is an L3 function use `\cs_generate_variant:Nn` on it and use the variant.
- (as already mentioned by frougon) you need to expand `\my@field` when assigning to your internal inside of `\fontsettingcmd` (my code below uses the fully expandable `\seq_map_function:NN` instead of `\seq_map_inline:Nn`, and expands the variable, the result is that in my code your font-setting macro only contains `\cs_gset:Npn \rmdefault {my@nfss@title@rm}...`.

Since your key codes are essentially identical I'd define them via a loop (and if you only need `code` you don't need `expkv-def`, so the following uses the basic `expkv`-syntax).

```
\documentclass{article}

\usepackage{fontspec}
\usepackage{expkv}

\ExplSyntaxOn
% key definition
\tl_new:N \l__my_field_tl
\tl_new:N \l__my_type_tl
\clist_map_inline:nn { title, subtitle }
  {
    \protected\ekvdef { foo } {#1}
      {
        \tl_set:Nn \l__my_field_tl {#1}
        \ekvset { bar } {##1}
        \__my_define_fnt_setting_cmd:
      }
  }
\clist_map_inline:nn { rm, sf, tt }
  {
    \protected\ekvdef { bar } {#1}
      {
        \tl_set:Nn \l__my_type_tl {#1}
        \__my_fnt_processor:n {##1}
      }
  }
\protected\ekvsetdef\testfoo{foo}

% font loading
\seq_new:N \l__my_fnt_type_seq
\cs_new_protected:Npn \__my_fnt_processor:n #1
  {
    \exp_args:Nc \newfontfamily { __my_fnt_ \l__my_field_tl _ \l__my_type_tl : }
      [
        NFSSFamily = { my@nfss@ \l__my_field_tl @ \l__my_type_tl }
      ]
      {#1}
    \seq_put_right:NV \l__my_fnt_type_seq \l__my_type_tl
  }

% define font setup command
\cs_new_protected:Npn \__my_define_fnt_setting_cmd:
  {
    \cs_gset:cpx { __my_fnts_ \l__my_field_tl : }
      {
        \seq_map_function:NN
          \l__my_fnt_type_seq \__my_define_fnt_setting_cmd_aux:n
      }
  }
\cs_new:Npn \__my_define_fnt_setting_cmd_aux:n #1
  {
    \cs_gset:Npn \exp_not:c { #1 default } { my@nfss@ \l__my_field_tl @ #1 }
  }

% frontend macro to picking fonts
\msg_new:nnn { my } { unknown-font-field }
  { Unknown~ font~ field~ #1.~ Maybe~ you~ misspelled~ it. }
\NewDocumentCommand \setmyfonts { m }
  {
    \cs_if_exist_use:cF { __my_fnts_ #1 : }
      { \msg_error:nnn { my } { unknown-font-field } {#1} }
  }
\ExplSyntaxOff

\testfoo
  {
    title    = {
      rm = NewCM10-Book.otf,
      sf = NewCMSans10-Book.otf,
      tt = NewCMMono10-Book.otf
    },
    subtitle = {
      rm = KpRoman-Regular.otf,
      sf = KpSans-Regular.otf,
      tt = KpMono-Regular.otf
    }%
  }

\expandafter\show\csname __my_fnts_title:\endcsname

\setmyfonts{title}

\begin{document}
{%
  \rmfamily
  \meaning\rmdefault
  
  test%
}

{%
  \sffamily
  \meaning\sfdefault
  
  test%
}

{%
  \ttfamily
  \meaning\ttdefault

  test%
}
\end{document}
```
Answer #2
frougon
You should use things like `\show\my@title@fnts` to debug your code. This would reveal that your macro:

```
\cs_new:Npn \fontsettingcmd {
  \cs_gset:cpn { my @ \my@field @ fnts } {
    % As per https://tex.stackexchange.com/a/729805,
    % we need to generate the following macros:
    %   \def\rmdefault{my@title@rm@nfss}
    %   \def\sfdefault{my@title@sf@nfss}
    %   \def\ttdefault{my@title@tt@nfss}
    % Doesn't work, as of now.
    \seq_map_inline:Nn \l__my_fnt_type_seq {
      \cs_gset:cpe { ####1default } {
        my @ \my@field @ ####1 @ nfss
      }
    }
    \normalfont
  }
}
```

doesn't work as you expect, because there is nothing to expand the last `\my@field` before the `\cs_gset:cpn` is executed.

When `\fontsettingcmd` is defined, it has two `\my@field` tokens in its replacement text. The first one is expanded when `\cs_gset:cpn { my @ \my@field @ fnts } { … }` is executed due to the `c` argument type; the second one becomes part of the replacement text of the `\my@....@fnts` macro defined at this moment. And that is the problem: in order to work correctly, instead of this `\my@field` token, your code would need the *replacement text* of `\my@field` at the moment `\fontsettingcmd` is executed. You did try to expand it with `\cs_gset:cpe`, but that one is executed way too late.

You could use `\cs_gset:cpx` or `\cs_gset:cpe`, but that would probably result in not very readable code. A simple and readable way to solve the problem is to make `\fontsettingcmd` take an argument which gives the current replacement text of `\my@field`, i.e. `title` or `subtitle` in your examples (of course, we could derive it from `\my@field` in the `\ekvdefinekeys{foo}{...}`).

Besides, I define `\fontsettingcmd` with `\cs_new_protected:Npn` because it can't be expandable since it performs an assignment (a macro assignment). Ditto for `\my@fnt@processor`, as `\newfontfamily` and `\seq_put_right:NV` are not expandable. Also, I use

```
\seq_put_right:NV \l__my_fnt_type_seq \my@type
```

because we only need one expansion of `\my@type` here (`\seq_put_right:No \l__my_fnt_type_seq { \my@type }` would work as well).

You're lucky that `\newfontfamily` appears to expand its optional argument!

I propose to replace the IMHO ugly

```
\cs_new:Npn \my@fnt@processor #1 {
  \exp_after:wN \newfontfamily \cs:w
    my @ \my@field @ \my@type @ fnt
  \cs_end: [
    NFSSFamily        = {
      my @ \my@field @ \my@type @ nfss
    }
  ]{ #1 }
  \seq_put_right:Ne \l__my_fnt_type_seq { \my@type }
}
```

with

```
\cs_new_protected:Npn \__my_new_font_family:Nnn #1#2#3
  { \newfontfamily {#1} [#2] {#3} }

\cs_generate_variant:Nn \__my_new_font_family:Nnn { ce }

\cs_new_protected:Npn \__my_process_font:n #1
  {
    \__my_new_font_family:cen { my @ \my@field @ \my@type @ fnt }
      { NFSSFamily = { my @ \my@field @ \my@type @ nfss } }
      {#1}
    \seq_put_right:NV \l__my_font_type_seq \my@type
  }
```

The `c`-type argument allows us to get rid of the ugly `\exp_after:wN \newfontfamily \cs:w my @ \my@field @ \my@type @ fnt \cs_end: `; my function uses a name that is in line with expl3 conventions (form + imperative style); and the `e`-type argument allows us not to rely on the lucky fact that `\newfontfamily` expands its optional argument.

Similar thing for the other function `\fontsettingcmd`. The expl3 names are made accessible outside `\ExplSyntaxOn ... \ExplSyntaxOff` with this:

```
\cs_new_eq:NN \my@process@font \__my_process_font:n
\cs_new_eq:NN \my@set@fonts \__my_set_fonts:n
```

I assign `\rmdefault` and friends locally rather than globally (why would you want this global?).

I also fixed indentation according to `texdoc l3styleguide`. Please, pretty please, read this short document.

Final remark: please leave at least a blank line between function definitions for readability.

```
\documentclass{article}
\usepackage{fontspec}
\usepackage{expkv-def}

\makeatletter
\ekvdefinekeys{foo}{%
  code    title     = {%
    \def\my@field{title}%
    \ekvset{bar}{#1}%
    \my@set@fonts{title}%
  },%
  code    subtitle  = {%
    \def\my@field{subtitle}%
    \ekvset{bar}{#1}%
    \my@set@fonts{subtitle}%
  }%
}

\ekvdefinekeys{bar}{%
  code    rm        = {%
    \def\my@type{rm}%
    \my@process@font{#1}%
  },%
  code    sf        = {%
    \def\my@type{sf}%
    \my@process@font{#1}%
  },%
  code    tt        = {%
    \def\my@type{tt}%
    \my@process@font{#1}%
  }%
}

\NewDocumentCommand \testfoo { m } {%
  \ekvset{foo}{#1}%
}

\ExplSyntaxOn

\cs_new_protected:Npn \__my_new_font_family:Nnn #1#2#3
  { \newfontfamily {#1} [#2] {#3} }

\cs_generate_variant:Nn \__my_new_font_family:Nnn { ce }

\seq_new:N \l__my_font_type_seq

\cs_new_protected:Npn \__my_process_font:n #1
  {
    \__my_new_font_family:cen { my @ \my@field @ \my@type @ fnt }
      { NFSSFamily = { my @ \my@field @ \my@type @ nfss } }
      {#1}
    \seq_put_right:NV \l__my_font_type_seq \my@type
  }

\cs_new_protected:Npn \__my_set_fonts:n #1
  {
    \cs_gset:cpn { my @ #1 @ fnts }
      {
        \seq_map_inline:Nn \l__my_font_type_seq
          {
            \cs_set:cpn { ####1default } { my @ #1 @ ####1 @ nfss }
          }
        \normalfont
      }
  }

\cs_new_eq:NN \my@process@font \__my_process_font:n
\cs_new_eq:NN \my@set@fonts \__my_set_fonts:n

\ExplSyntaxOff
\makeatother

\begin{document}
\testfoo{%
  title             = {%
    rm                = {%
      NewCM10-Book.otf%
    },%
    sf                = {%
      NewCMSans10-Book.otf%
    },%
    tt                = {%
      NewCMMono10-Book.otf%
    }%
  },%
  subtitle          = {%
    rm                = {%
      KpRoman-Regular.otf%
    },%
    sf                = {%
      KpSans-Regular.otf%
    },%
    tt                = {%
      KpMono-Regular.otf%
    }%
  }%
}

\makeatletter
% \show\my@title@fnts
% \show\my@subtitle@fnts
\my@title@fnts
\makeatother

\setlength{\parindent}{0pt}

{%
  \rmfamily
  \meaning\rmdefault

  test%
}

{%
  \sffamily
  \meaning\sfdefault

  test%
}

{%
  \ttfamily
  \meaning\ttdefault

  test%
}
\end{document}
```

![image.png](/image?hash=d21f6d5a30c1ab8895be421ca9d8ecc8193c918551ea4e1d42c24de1380f8eec)

Enter question or answer id or url (and optionally further answer ids/urls from the same question) from

Separate each id/url with a space. No need to list your own answers; they will be imported automatically.