2

Let's consider a macro name of the form \first@second. I would like to write a command that would convert the name of the macro to pgfkey name of the form /first/second so that the following approaches would be equivalent and have the same effect:

% This can change:
\newcommand{\macrotokey}[1]{% WHAT TO DO HERE? %}
\newcommand{\setviakey}[2]{\pgfkeys{#1 = #2}}
\newcommand{\setviamacro}[2]{\pgfkeys{\macrotokey{#1} = #2}}

% If possible I would like to have this syntax on the user side:
\setviakey{/A/B}{Hello}
\setviamacro{\A@B}{Hello}

I have no idea where to start to do such a trick.

4
  • \first@second is not normally a legal macro name. It is possible to parse a macro name using \detokenize (\newcommand does it), but why? Commented Oct 28, 2022 at 1:49
  • I am in a zone where \makeatletter is "on" (to avoid the macro name being used elsewhere). How would you achieve that using \detokenize?
    – Vincent
    Commented Oct 28, 2022 at 1:52
  • You should be able to run xstring parsing functions om detokenized csnames. Removing the starting backslash may be tricky. Commented Oct 28, 2022 at 2:10
  • Seems you wish \macrotokey to transform the name of a control-sequence-token into a key-path. 1) Do you have patterns/rules in mind for distinguishing denotation of an absolute key path via control-sequence-token from denotation of a relative key path via control-sequence-token? 2) Under normal catcode-régime you can have names of keys that contain spaces but you need to do some catcode-trickery for causing TeX to take a space for a component of a name of a control sequence whiile tokenizing .tex-input. 3) How to handle keys whose names contains @? Commented Oct 28, 2022 at 14:52

2 Answers 2

2

If you wish to use \macrotokey within the argument of \pgfkeys, then \macrotokey needs to be fully expandable.

Expl3 comes along with a fully expandable routine \cs_to_str:N where the name of a control-sequence-token without leading escape character (usually backslash) is delivered by triggering two expansion-steps to \cs_to_str:N. With this routine you don't need to care for edge cases like \escapechar having value 32 or a negative value. (32 means that instead of a backslash a space is delivered as escape-character by things like \string or \detokenize. Negative value means that no escape-characteres are delivered at all.)

So let's use expl3 for defining a macro \macrotokey{⟨argument⟩} which triggers two expansion-steps on \cs_to_str:N ⟨argument⟩ and then initiates a recursive loop \__MyStuff_macrotokeyReplaceAtLoop:n which checks whether the argument (still) contains @ of category 12(other) and if so replaces the first of these @ by / before calling itself again and if not so delivers the argument.

I would probably have named the thing \cstokentokey instead of \macrotokey because the meaning of the token that is to form the argument of \macrotokey is not relevant (except that it should not be an \outer token)—the argument being a control sequence token is sufficient. It does not even need to be defined.

If you uncomment \exp_end: and \exp:w, then the result of \macrotokey is obtained by triggering exactly two expansion-steps on \macrotokey.
But for \pgfkeys this is not necessary as \pgfkeys triggers expansion of the tokens forming the key itself.

\documentclass{article}
\usepackage{pgfkeys}

\ExplSyntaxOn
\cs_new:Npn \MyStuff_GobbleTillAt:w #1@ {}
\cs_new:Npn \MyStuff_ReplaceAt:w #1@ { #1/ }
\cs_new:Nn \__MyStuff_macrotokeyReplaceAtLoop:n 
  {
    \tl_if_empty:oTF {\MyStuff_GobbleTillAt:w #1 @} 
                     { %\exp_end: 
                       #1
                       % If the name of the control-sequence-token begins with @, then it denotes an absolute path,
                       % otherwise the path is relative to what \pgfkeysdefaultpath was defined to via the /.cd-handler.
                       % If you do /#1  instead of #1, then every path is absolute and the names of
                       % control-sequence-tokens shall not begin with `@`.
                       % If you do  \pgfkeysdefaultpath #1  instead of #1, then every path is relative and the names of
                       % control-sequence-tokenss shall not begin with `@`.
                     } 
                     {
                       \exp_args:No \__MyStuff_macrotokeyReplaceAtLoop:n 
                                    {\MyStuff_ReplaceAt:w #1}
                     }
  }
\cs_new:Npn \macrotokey #1 
  { %\exp:w 
    \exp_args:NNo \exp_args:No 
                  \__MyStuff_macrotokeyReplaceAtLoop:n
                  { \cs_to_str:N #1 } 
  }
\ExplSyntaxOff

\newcommand{\setviamacro}[2]{\pgfkeys{\macrotokey{#1}={#2}}}
\newcommand{\setviakey}[2]{\pgfkeys{#1={#2}}}

\makeatletter

\pgfkeys{%
  %---------------------------------------------------------------
  % define the code to execute when encountering the key /A/B/C:
  %---------------------------------------------------------------
  /A/B/C/.code=\message{^^JKey /A/B/C's value in parentheses is: (#1)^^J},
  %---------------------------------------------------------------
  % Use the key /A/B/C :
  %---------------------------------------------------------------
  /A/B/C=Test 1: Absolute key provided,
  \macrotokey{\@A@B@C} = Test 1: Control sequence token denoting absolute key provided,
  /A/.cd,
  B/C=Test 2: Relative key provided,
  \macrotokey{\B@C} = Test 2: Control sequence token denoting relative key provided,
  /A/B/.cd,
  C=Test 3: Relative key provided,
  \macrotokey{\C} = Test 3: Control sequence token denoting relative key provided,
}

\setviakey{/A/B/C}{Test 4: Absolute key provided}
\setviamacro{\@A@B@C}{Test 4: Control sequence token denoting absolute key provided}

\stop

The following messages are delivered to the console:

Key /A/B/C's value in parentheses is: (Test 1: Absolute key provided)


Key /A/B/C's value in parentheses is: (Test 1: Control sequence token denoting 
absolute key provided)

Key /A/B/C's value in parentheses is: (Test 2: Relative key provided)


Key /A/B/C's value in parentheses is: (Test 2: Control sequence token denoting 
relative key provided)

Key /A/B/C's value in parentheses is: (Test 3: Relative key provided)


Key /A/B/C's value in parentheses is: (Test 3: Control sequence token denoting 
relative key provided)

Key /A/B/C's value in parentheses is: (Test 4: Absolute key provided)


Key /A/B/C's value in parentheses is: (Test 4: Control sequence token denoting 
absolute key provided)
3
  • Is there a reason why \setviamacro is defined inside the expl syntax or can it be defined outside with \newcommand?
    – Vincent
    Commented Oct 28, 2022 at 16:10
  • @Vincent It can be defined with \newcommand both inside and outside expl syntax. The reason why I defined it inside expl syntax is that I overlooked that expl syntax is not needed. ;-) I just edited my answer accordingly. Commented Oct 28, 2022 at 16:24
  • Thanks for all the explanation!
    – Vincent
    Commented Oct 28, 2022 at 16:31
2

This is probably not the most robust version.

All macros don't use @ in their name because then there would be a conflict between @11 (a @ with catcode 11, the one in the macro's name) and @12 (the one that's produced by \string).

There are ways around it – there even was a Q about is recently which I can't find right now – but, honestly, I wouldn't use it that way anyway.


You could just define \A@B to expand to /A/B and use it as \setviakey{\A@B}{…} but I guess \A@B has another purpose in your use-case.

Code

\documentclass{article}
\usepackage{pgfkeys}
\pgfkeys{/A/B/.code=``#1''}
\begin{document}
\def\gobble#1{}

\def\macrotokeyatstripperstop{!stop!}%
\newcommand*\macrotokey[1]{%
  /\expandafter\expandafter\expandafter\macrotokeyatstripper
  \expandafter\gobble\string#1@\macrotokeyatstripperstop
}%
\def\macrotokeyatstripper#1@#2{%
  #1\ifx\macrotokeyatstripperstop#2\expandafter\gobble\else
    /\expandafter\macrotokeyatstripper
  \fi#2}%

\newcommand{\setviakey}[2]{\pgfkeys{#1 = #2}}
\newcommand{\setviamacro}[2]{\pgfkeys{\macrotokey{#1} = #2}}

\setviakey{/A/B}{Hello}

\makeatletter
\setviamacro{\A@B}{Hello World}
\makeatother
\end{document}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .