# Generating pgfkeys paths using \foreach loop

I'm trying to generate a set of keys using a \foreach loop, but they don't appear to be created properly.

I've tried debugging with a handler that shows the keys, but they look correct to me in the log. There must be something that I'm missing, but I can't figure out what.

See the comments in the MWE below for where the error shows up.

MWE:

\documentclass{article}

\RequirePackage{pgfkeys}
\RequirePackage{pgffor}

\RequirePackage{etoolbox}

\pgfkeys{/handlers/.store in cs/.code=\pgfkeysalso{%
\pgfkeyscurrentpath/.code=\expandafter\def\csname#1\endcsname{##1}}%
}

\pgfkeysdef{/handlers/.show path}
{%
\edef\path{\pgfkeyscurrentpath}%
\pgfkeysgetvalue{\pgfkeyscurrentpath}{\val}%
\show\path%
}

\makeatletter

\def\meide@keys{\pgfqkeys{/meide}}

\newcommand\meide@setup[3]{%
% #1 = name
% #2 = number of levels
% #3 = further key/value pairs
\meide@keys{
#1/.cd,
levels/.store in cs=meide@#1@levels,
levels=#2,
}%
\foreach \level in {0,...,\csuse{meide@#1@levels}}%
{%
\meide@setup@level{#1}{\level}%
}%
\meide@keys{#1/.cd,#3}%
}

\newcommand\meide@setup@level[2]{
% #1 = name
% #2 = level to create
\begingroup\edef\x{\endgroup\noexpand%
\meide@keys{
#1/level #2/.cd,
% uncomment the '.show path' handler to see the current keypaths. I see them as expected:
% /meide/name/level 0/myvals
% /meide/name/level 1/myvals
%myvals/.show path,
myvals/.code 2 args={%
% #1 = first
% #2 = second
\csxdef{meide@#1@myvals@level #2@first}{\unexpanded{##1}}%
\csxdef{meide@#1@myvals@level #2@second}{\unexpanded{##2}}%
}
}%
}\x%
}

% setting up one of the levels manually to show how it should work
\meide@keys{
name/level 0/myvals/.code 2 args={%
% #1 = first
% #2 = second
\csxdef{meide@name@myvals@level 0@first}{#1}%
\csxdef{meide@name@myvals@level 0@second}{#2}%
},
}

\meide@setup{name}{1}{
level 0/myvals={a}{b}, % works because it was created manually
level 1/myvals={c}{d}, % Package pgfkeys Error: I do not know the key '/meide/name/level 1/myvals', to which you passed '{c}{d}'
}

\makeatother

\begin{document}

\csuse{meide@name@myvals@level 0@first} % displays 'a'

\csuse{meide@name@myvals@level 1@first} % displays nothing

\end{document}


This doesn't work because your myvals/.code 2 args={...} is a local assignment, but it is executed inside a TeX group. Indeed, the \foreach loop in \meide@setup executes the loop code inside a TeX group. Once this group is finished, all local definitions performed therein automatically vanish. In order to solve this problem, I simply replaced the \foreach loop with expl3 's \int_step_inline:nnn macro, which doesn't create a group around the loop code.

I also removed the \edef and the associated tricks, because I don't see why they are needed here. With these two changes, everything appears to work as expected.

\documentclass{article}
\usepackage{pgfkeys}
\usepackage{expl3}
\usepackage{etoolbox}

\ExplSyntaxOn
% Borrow \int_step_inline:nnn from expl3
\cs_new_eq:NN \intstepinline \int_step_inline:nnn
\ExplSyntaxOff

\pgfkeys{/handlers/.store in cs/.code=\pgfkeysalso{%
\pgfkeyscurrentpath/.code=\expandafter\def\csname#1\endcsname{##1}}%
}

\pgfkeysdef{/handlers/.show path}
{%
\edef\path{\pgfkeyscurrentpath}%
\pgfkeysgetvalue{\pgfkeyscurrentpath}{\val}%
\show\path
}

\makeatletter

\newcommand\meide@keys{\pgfqkeys{/meide}}

\newcommand\meide@setup[3]{%
% #1 = name
% #2 = number of levels
% #3 = further key/value pairs
\meide@keys{
#1/.cd,
levels/.store in cs=meide@#1@levels,
levels=#2,
}%
\intstepinline{0}{\csuse{meide@#1@levels}}
{%
\meide@setup@level{#1}{##1}%
}%
\meide@keys{#1/.cd,#3}%
}

\newcommand\meide@setup@level[2]{%
\meide@keys{
#1/level #2/.cd,
% uncomment the '.show path' handler to see the current keypaths. I see them as expected:
% /meide/name/level 0/myvals
% /meide/name/level 1/myvals
%myvals/.show path,
myvals/.code 2 args={%
% #1 = first
% #2 = second
\csxdef{meide@#1@myvals@level #2@first}{##1}%
\csxdef{meide@#1@myvals@level #2@second}{##2}%
},
}%
}

\meide@setup{name}{1}{
level 0/myvals={a}{b}, % works
level 1/myvals={c}{d}, % now also works
}

\makeatother

\begin{document}

\csuse{meide@name@myvals@level 0@first}  % displays 'a'
\csuse{meide@name@myvals@level 0@second} % displays 'b'

\csuse{meide@name@myvals@level 1@first}  % displays 'c'
\csuse{meide@name@myvals@level 1@second} % displays 'd'

\end{document}


Please try to provide a minimal example next time: this takes too much time to read and analyze. You could probably reduce the code, although I understand that maybe you didn't manage to locate the problem very precisely.

• Sorry, I wasn't able to minimize further without showing the problem :) I had missed that setting keys is local to the current group. The edef/begingroup trick I must shamefully admit was a bit of cargo culting I picked up at github.com/T-F-S/genealogytree/blob/master/tex/latex/… Sep 7, 2020 at 16:36
• Okay, no problem. :-) Sep 7, 2020 at 16:46