我正在编写一个类,并将一堆数据存储到变量中。这些变量需要访问,并依赖于计数器值。
然而,当访问先前定义的变量的值时,我惊讶地发现它已被覆盖。
我的 MWE 有一个计数器,dataID
。我希望将数据存储在用这种模式声明的动态变量中:data@<counter_value>@prio
。我正在使用自定义宏(getValue
和storeValue
分别使用\@nameuse
和\@namedef
)。
但是,正如您在 MWE 中看到的,当计数器dataID
为 2 时,变量 data@1@prio
将被存储在 中的值覆盖data@2@prio
。我该如何防止这种情况发生?
\documentclass[a4paper]{article}
\usepackage{hyperref}
\usepackage{xcolor}
\makeatletter
\newcommand{\getValue}[1]{%
\@ifundefined{#1}{%
\PackageError{lookup}{No #1 key defined}{}%
}{%
\@nameuse{\expandafter#1}%
}%
}
\newcommand{\storeValue}[2]{%
\@namedef{#1}{#2}%
}
\newcommand{\labelText}[1]{%
\@bsphack
\csname phantomsection\endcsname % for hyperrredf usage
\def\@currentlabel{#1}{\label{#1}}%
\@esphack
}
\newcounter{dataID}
\newcommand{\data}{%
\refstepcounter{dataID}%
\labelText{data\thedataID}
data\thedataID
\quad dataID value = \thedataID
\expandafter\storeValue{data@\thedataID @prio}{This is \ref{data\thedataID}}
}
\makeatother
\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}
根据调用的地方不同,的值\getValue{data@1@prio}
也不相同。
答案1
正如 @frougon 在对你的代码进行很好的分析时所说的那样,他应该得到绿色的勾号,输出方面的主要问题是你存储了未扩展的内容,\ref{data\thedataID}
因此\getValue
返回的内容取决于当前的值\thedataID
。
而不是使用\@nameuse
来自电子工具箱打包,至少对我来说,这会产生更易读的代码:
\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}
输出与上面相同:
答案2
您选择的问题标签正确:这里的主要问题涉及扩展。:-)
当你这样做时
\@nameuse{\expandafter#1}
,这......不是很有用,因为:- 您正在传递
\expandafter#1
给\@nameuse
(用参数代替#1
),而您可能想要传递结果它的扩展。在这个特殊情况下,由于\@nameuse
只是macro:#1->\csname #1\endcsname
,它的参数无论如何都会被扩展,这是由于 的\csname
工作方式。 - 这里没有
\expandafter
充分利用 。例如,如果你调用\getValue{foo}
,当\getValue
控制序列标记扩展时,\getValue{foo}
将被 的替换文本替换\getValue
,其中参数foo
将被替换#1
。在这种情况下,这将产生:
当 TeX 扩展它时\@ifundefined{foo}{% \PackageError{lookup}{No foo key defined}{}% }{% \@nameuse{\expandafter foo}% }%
\@nameuse
,将导致:
因此,当 TeX 开始扩展 时\csname \expandafter foo\endcsname
\expandafter
,它会尝试扩展 后面的标记f
,即第一个字符标记o
,而 是不可扩展的。所以,这\expandafter
没有任何用处。
- 您正在传递
另一个
\expandafter
:\expandafter\storeValue{data@\thedataID @prio}{This is \ref{data\thedataID}}
有类似的问题。它会尝试扩展 后面的左括号
{
,\storeValue
而 又是非活动字符标记,因此无法扩展。在这种情况下,你想扩展这两个\thedataID
标记,但不想\ref
前\storeValue
本身就扩展了。因此,我建议:\begingroup \edef\tmp{% \endgroup \noexpand\storeValue{data@\thedataID @prio} {This is \noexpand\ref{data\thedataID}}% }% \tmp
这些
\noexpand
标记会阻止 中下一个标记的扩展\edef
。然后它们会消失,因此\tmp
在这段摘录的最后一行中 扩展之后,TeX 会看到:\endgroup \storeValue{data@1@prio}{This is \ref{data1}}%
例如假设
\thedataID
扩展为 1。\endgroup
关闭定义的组\tmp
,并且输入流中剩下的正是您想要的。类似地,在 中
\labelText
,您希望递归扩展参数,以便获取 的替换文本中的计数器值\@currentlabel
,而不是五个标记data\thedataID
(四个字符标记后跟一个控制序列标记)。由于\label
无论如何都会使用 递归扩展其参数\protected@write
,因此我们可以\protected@edef\@currentlabel{#1}
为了一致性而扩展参数(这意味着\protect
在该参数中使用时会起作用,以防您需要它)。实际上,\def
在这里也有效,因为\label
,\protected@write\@auxout{}{ ... \@currentlabel ...}
所以它递归扩展了它的参数和\@currentlabel
。
其他事情:
没有必要将调用括
\label
在括号内(并使其看起来像一个参数\def
!)。hyperref
应该最后加载,除非在非常特殊的情况下,例如cleveref
使用 (cleveref
必须在 之后加载hyperref
)。小问题:你不需要
\csname ... \endcsname
形成\phantomsection
控制序列标记(所有字母 p、h、a、...、i、o、n 在正常情况下都有类别代码 11,即字母对于 TeX)。
注意:我保留了原样@
,因为它有效,但有一个陷阱,所以要小心。事实上,@
宏的替换文本中的\data
类别代码为 11(字母),而文档正文中的类别代码为 12(其他)。这些是不是相同的字符标记。这是有效的,因为最终它们只在 内部使用\csname ... \endcsname
,并且那里它们是否有 catcode 11 或 12 并不重要(它们成为控制序列的一部分姓名(这只是普通意义上的字符序列,没有附加任何 catcode)。如果不确定,您可以随时使用其他字符,例如.
或-
。
总而言之,我建议如下:
\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}
答案3
我建议使用以下属性列表来采用不同的实现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}
优点是\getValue
完全可扩展。