4

I'm trying to store a value in a macro that is constructed from the arguments of another macro, but I must be missing something because the macro is not created.

MWE:

\documentclass{article}

\RequirePackage{pgfkeys}

\newcommand\zkeys[1]{\pgfkeys{/prefix/.cd,#1}}

\newcommand\zsetup[2]{
    \zkeys{
        #1/.store in=\z#2,
    }
}
\zsetup{keya}{storagea}
\zsetup{keyb}{storageb}

\zkeys{
    keya=test,
}

\begin{document}

\zstoragea % Undefined control sequence.

\end{document}
6

New solution

Another, more flexible approach is to extend the handlers \pgfkeys accepts by a new one .store in cs which does basically the same as .store in, but doesn't take a complete control sequence name as value but a list of characters from that the final control sequence is built. So the following calls would be equal:

foo/.store in=\mymacro
foo/.store in cs=mymacro

The full example then looks like

\documentclass{article}
\RequirePackage{pgfkeys}

\newcommand\zkeys[1]{\pgfkeys{/prefix/.cd,#1}}

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

\newcommand\zsetup[2]{%
    \zkeys{
        #1/.store in cs=z#2,
    }%
}
\zsetup{keya}{storagea}
\zsetup{keyb}{storageb}

\zkeys{
    keya=test,
}

\begin{document}

\zstoragea % Undefined control sequence.

\end{document}

Old solution

When you type \z#2, TeX parses this as the command name \z followed by the tokens inserted from the second argument. If you want to build a new control sequence from a series of characters/tokens, you have to use the sequence \csname ...\endcsname, where ... would be z#2 in this case.

However, in this specific situation store in=\csname z#2\endcsname wouldn't work, because the \csname call must be expanded exactly once to build the actual new control sequence from the characters, but not more than once, otherwise the built macro would be tried to expanded itself.

A possible solution is to wrap the whole key definitions into an \edef, prefix all commands in it by \noexpand, and use \unexpanded\expandafter{...} in each place, we want exactly one expansion step:

\newcommand\zsetup[2]{
    \edef\temp{%
        \noexpand\zkeys{
            #1/.store in=\unexpanded\expandafter{\csname z#2\endcsname},
        }%
    }\temp
}

\zstoragea will then expand to test.

  • 1
    Aha, I forgot to mention that I had tried \csname without success, thanks for the thorough explanation on why that didn't work! – meide Jul 16 at 4:04
4

Here are two solutions. They both take care not to expand the first argument of \zsetup before passing it to \zkeys, nor to define or overwrite any macro in the current group as a side effect.

First solution

\documentclass{article}
\usepackage{pgfkeys}

\newcommand{\zkeys}[1]{\pgfkeys{/prefix/.cd,#1}}

\newcommand*{\zsetup}[2]{%
  \begingroup
    \edef\arg{\unexpanded{#1/.store in=}%
              \expandafter\noexpand\csname z#2\endcsname}%
    \expandafter
  \endgroup
  \expandafter\zkeys\expandafter{\arg}%
}

\zsetup{keya}{storagea}
\zsetup{keyb}{storageb}

\zkeys{
    keya=test,
}

\begin{document}

\zstoragea % Print 'test'

\end{document}

Second solution

Same code, except for the definition of \zsetup:

\newcommand*{\zsetup}[2]{%
  \begingroup
    \def\tmp##1{\zkeys{#1/.store in=##1}}%
    \expandafter\expandafter\expandafter
  \endgroup
  \expandafter\tmp\csname z#2\endcsname
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.