Dynamic variable name gets overwritten
You picked the question tags right: your main problem here concerns expansion. :-)
When you do
\@nameuse{\expandafter#1}
, this is... not very useful, because:- You are passing
\expandafter#1
to\@nameuse
(with the argument substituted for#1
), whereas you probably wanted to pass the result of its expansion. In this particular case, since\@nameuse
is simplymacro:#1->\csname #1\endcsname
, its argument will be expanded anyway, due to the way\csname
works. - The
\expandafter
is not put to good use here. If you call for instance\getValue{foo}
, when the\getValue
control sequence token is expanded,\getValue{foo}
will be replaced by the replacement text of\getValue
, in which the argumentfoo
will have been substituted for#1
. In this case, this will yield:
When TeX expands this\@ifundefined{foo}{% \PackageError{lookup}{No foo key defined}{}% }{% \@nameuse{\expandafter foo}% }%
\@nameuse
, this will result in:
thus, when TeX gets to the expand the\csname \expandafter foo\endcsname
\expandafter
, it will try to expand the token followingf
, i.e. the first character tokeno
, which is unexpandable. So, this\expandafter
doesn't serve any purpose.
- You are passing
The other
\expandafter
:\expandafter\storeValue{data@\thedataID @prio}{This is \ref{data\thedataID}}
has a similar problem. It will try to expand the opening brace
{
following\storeValue
, which again is a non-active character token and therefore is unexpandable. In this case, you want to expand the two\thedataID
tokens but not\ref
before\storeValue
is itself expanded. Therefore, I would propose something like:\begingroup \edef\tmp{% \endgroup \noexpand\storeValue{data@\thedataID @prio} {This is \noexpand\ref{data\thedataID}}% }% \tmp
The
\noexpand
tokens prevent expansion of the next token inside the\edef
. Then they disappear, so that after\tmp
is expanded in the last line of this excerpt, TeX sees:\endgroup \storeValue{data@1@prio}{This is \ref{data1}}%
assuming for instance that
\thedataID
expanded to 1. The\endgroup
closes the group where\tmp
was defined, and what is left in the input stream is exactly what you wanted.Similarly, in
\labelText
, you want the argument to be recursively expanded in order to get the counter value in the replacement text of\@currentlabel
, as opposed to the five tokensdata\thedataID
(four character tokens followed by one control sequence token). Since\label
recursively expands its argument anyway using\protected@write
, we can do\protected@edef\@currentlabel{#1}
for consistency to expand the argument (this implies that\protect
will work when used inside this argument in case you need it). Actually,\def
also works here, because\label
does\protected@write\@auxout{}{ ... \@currentlabel ...}
, so it recursively expands both its argument and the\@currentlabel
.
Other things:
There is no need to wrap the
\label
call within braces (and make it look like an argument to\def
!).hyperref
should be loaded last, except in very special cases such as whencleveref
is used (cleveref
must be loaded afterhyperref
).Minor nit: you don't need
\csname ... \endcsname
to form the\phantomsection
control sequence token (all letters p, h, a, ..., i, o, n have category code 11 in normal circumstances, i.e. are letters for TeX).
Note: I left your @
as is because it works, but there is a trap, so beware. Indeed, the @
in the replacement text of your \data
macro has category code 11 (letter), whereas the ones in your document body have category code 12 (other). These are not the same character tokens. This works because in the end, they are only used inside \csname ... \endcsname
, and there it doesn't matter whether they have catcode 11 or 12 (they become part of a control sequence name, which is just a sequence of characters in the ordinary sense, without any catcode attached). If unsure, you can always use another character, such as .
or -
.
All in all, I propose the following:
\documentclass{article}
\usepackage{hyperref}
\makeatletter
\newcommand{\getValue}[1]{%
\@ifundefined{#1}{%
\PackageError{lookup}{No '#1' key defined}{}%
}{%
\@nameuse{#1}%
}%
}
\newcommand{\storeValue}[2]{%
\@namedef{#1}{#2}%
}
\newcommand{\labelText}[1]{%
\@bsphack
\phantomsection % for hyperrred usage
% \def also works here (see above)
\protected@edef\@currentlabel{#1}%
\label{#1}%
\@esphack
}
\newcounter{dataID}
\newcommand{\data}{%
\refstepcounter{dataID}%
\labelText{data\thedataID}
data\thedataID
\quad dataID value = \thedataID
\begingroup
\edef\tmp{%
\endgroup
\noexpand\storeValue{data@\thedataID @prio}
{This is \noexpand\ref{data\thedataID}}%
}%
\tmp
}
\makeatother
\begin{document}
\data % data1
\verb|data@1@prio|
$\rightarrow$ \getValue{data@1@prio}
---
\data % data2
\verb|data@\thedataID@prio|
$\rightarrow$ \getValue{data@\thedataID @prio} (because dataID=2)
---
\verb|data@1@prio| $\rightarrow$ \getValue{data@1@prio}
\verb|data@2@prio| $\rightarrow$ \getValue{data@2@prio}
\end{document}
As @frougon says in their nice breakdown of your code, for which he deserves the green tick, the main problem in terms of the output is that you are storing the unexpanded \ref{data\thedataID}
so what \getValue
returns depends on the current the value of \thedataID
.
Rather than using \@nameuse
and friends I recommend using the corresponding commands from the etoolbox package as, at least for me, this results in more readable code:
\documentclass[a4paper]{article}
\usepackage{hyperref}
\usepackage{xcolor}
\usepackage{etoolbox}
\newcommand{\getValue}[1]{
\ifcsdef{#1}{\csuse{#1}}%
{\PackageError{lookup}{No #1 key defined}{}}%
}
\newcommand{\storeValue}[2]{\csxdef{#1}{#2}}
\makeatletter
\newcommand{\labelText}[1]{\typeout{label=#1.}%
\@bsphack
\phantomsection % for hyperrredf usage
\def\@currentlabel{#1}{\label{#1}}%
\@esphack
}
\makeatother
\newcounter{dataID}
\newcommand{\data}{%
\refstepcounter{dataID}%
\labelText{data\thedataID}
data\thedataID
\quad dataID value = \thedataID
\storeValue{data@\thedataID @prio}{This is \noexpand\ref{data\thedataID}}
}
\begin{document}
\data % data1
\texttt{data@1@prio} $\rightarrow$ \getValue{data@1@prio} \quad\textcolor{blue}{1st Definition of \texttt{data@1@prio} !}
---
\data % data2
\texttt{data@thedataID@prio} $\rightarrow$ \getValue{data@\thedataID @prio} (because dataID=2)
---
\texttt{data@1@prio} $\rightarrow$ \getValue{data@1@prio} \quad\textcolor{red}{Redefinition of \texttt{data@1@prio} !}
\texttt{data@2@prio} $\rightarrow$ \getValue{data@2@prio}
\end{document}
The output is the same as above:
I propose a different implementation using property lists of expl3
:
\documentclass[a4paper]{article}
\usepackage{xcolor,xparse}
\usepackage{hyperref}
\ExplSyntaxOn
\prop_new:N \g_eisenheim_values_prop
\cs_generate_variant:Nn \prop_item:Nn { Ne }
\cs_generate_variant:Nn \prop_gput:Nnn { Nxx }
\prg_generate_conditional_variant:Nnn \prop_if_in:Nn { Ne } { T,F,TF,p }
\cs_generate_variant:Nn \msg_expandable_error:nnn { nne }
\NewExpandableDocumentCommand{\getValue}{m}
{
\prop_if_in:NeTF \g_eisenheim_values_prop { #1 }
{
\prop_item:Ne \g_eisenheim_values_prop { #1 }
}
{
\msg_expandable_error:nne { lookup } { missing } { #1 }
}
}
\NewDocumentCommand{\storeValue}{mm}
{
\prop_gput:Nxx \g_eisenheim_values_prop { #1 } { #2 }
}
\msg_new:nnn { lookup } { missing } { The~value~#1~is~missing }
\ExplSyntaxOff
\makeatletter
\newcommand{\labelText}[1]{%
\@bsphack
\csname phantomsection\endcsname % for hyperref usage
\def\@currentlabel{#1}{\label{#1}}%
\@esphack
}
\makeatother
\newcounter{dataID}
\newcommand{\data}{%
\refstepcounter{dataID}%
\labelText{data\thedataID}%
data\thedataID
\quad dataID value = \thedataID
\storeValue{data@\thedataID @prio}{This is \noexpand\ref{data\thedataID}}
}
\begin{document}
\data % data1
\texttt{data@1@prio} $\rightarrow$ \getValue{data@1@prio}
---
\data % data2
\texttt{data@thedataID@prio} $\rightarrow$ \getValue{data@\thedataID @prio} (because dataID=2)
---
\texttt{data@1@prio} $\rightarrow$ \getValue{data@1@prio}
\texttt{data@2@prio} $\rightarrow$ \getValue{data@2@prio}
\getValue{x}
\end{document}
The advantage is that \getValue
is fully expandable.