动态变量名称被覆盖

动态变量名称被覆盖

我正在编写一个类,并将一堆数据存储到变量中。这些变量需要访问,并依赖于计数器值。

然而,当访问先前定义的变量的值时,我惊讶地发现它已被覆盖

我的 MWE 有一个计数器,dataID。我希望将数据存储在用这种模式声明的动态变量中:data@<counter_value>@prio。我正在使用自定义宏(getValuestoreValue分别使用\@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}

输出如下: MWE 输出

根据调用的地方不同,的值\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},这......不是很有用,因为:

    1. 您正在传递\expandafter#1\@nameuse(用参数代替#1),而您可能想要传递结果它的扩展。在这个特殊情况下,由于\@nameuse只是macro:#1->\csname #1\endcsname,它的参数无论如何都会被扩展,这是由于 的\csname工作方式。
    2. 这里没有\expandafter充分利用 。例如,如果你调用\getValue{foo},当\getValue控制序列标记扩展时,\getValue{foo}将被 的替换文本替换\getValue,其中参数foo将被替换#1。在这种情况下,这将产生:
      \@ifundefined{foo}{%  
          \PackageError{lookup}{No foo key defined}{}%
      }{%
          \@nameuse{\expandafter foo}%
      }%
      
      当 TeX 扩展它时\@nameuse,将导致:
      \csname \expandafter foo\endcsname
      
      因此,当 TeX 开始扩展 时\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完全可扩展。

相关内容