5

I'm trying to associate some text (dynamically generated with counters) to a label, so that I can refer to that label and print the text associated(without re-generating it, that would cause problems with counters).

Example of usage:

\textLabel{key}{\dynamicText}

Somewhere, after, in the document i want to have a new macro that links to key and displays the text generated by \dynamictext.

This macro does not quite work:

\makeatletter
\newcommand{\textLabel}[2]{%
    \protected@edef\var{\expandafter\noexpand#2}%
    \label{#1}\var%
    \global\expandafter\def\csname#1\endcsname{\hyperref[#1]{\var}}%
}
\makeatother

The macro \var seems to work just fine. The problem is that the macro identified with \#1 is nor visible in the document, and produces an error (control sequence not found).

Where am I goingwrong?


Context: I have a long list of items that need an unique ID in some strange format. I'm automatically generating the ID for the items in the list, with a call to a macro that increases some counters and returns the IDs in function of the values of the counters.

Now, I want to write the IDs generated and refer to the items, without the need of recall the macro used to build them. (It would mean to call the \getID in the same order and number of times, and I don't want it)


Here a minimal (not) working example:

\documentclass[11pt]{report}       

\makeatletter
\newcommand{\textLabel}[2]{%
    \protected@edef\var{\expandafter\noexpand#2}%
    \label{#1}\var%
    \global\expandafter\def\csname #1\endcsname{\hyperref[#1]{\var}}
}
\makeatother

\newcounter{req}
\newcommand{\getid}[1]{%
    \stepcounter{req}%
    #1.\arabic{req}%
} 

\begin{document}
    \begin{itemize}
        \item \textLabel{itemkeyone}{\getid{ITEM}}
        \item \textLabel{itemkeytwo}{\getid{ITEM}}
    \end{itemize}

    \itemkeyone %I want: ITEM.1, instead produces command not found
\end{document}  

Here a minimal working example, but with the wrong behaviour:

\documentclass[11pt]{report}       

\usepackage{hyperref}

\newcommand{\textLabel}[2]{%
    \label{#1}#2%
    \global\expandafter\def\csname#1\endcsname{\hyperref[#1]{#2}}
}


\newcounter{req}
\newcommand{\getid}[1]{%
    \stepcounter{req}%
    #1.\arabic{req}%
} 

\begin{document}
    \begin{itemize}
        \item \textLabel{itemkeyone}{\getid{ITEM}}
        \item \textLabel{itemkeytwo}{\getid{ITEM}}
    \end{itemize}

    \itemkeyone %I want: ITEM.1, instead produces ITEM.3
\end{document}  

Thank you very much

  • Welcome to TeX.SX!! It is better to post a full minimal working example. For example, A LaTeX MWE should start with a \documentclass command, have a minimal preamble and then \begin{document}...\end{document}. Unless the problem is a compilation error, the code should compile and be as small as possible to demonstrate your problem. This makes it much easier for people to help you --- and much more likely that they will! – Andrew May 6 '16 at 9:06
  • The reason why it prints item.3 is that \getId isn't expandable and does not have the value of the counter at the time of definition, but at the time of calling \itemkeyone (plus +1, since it's stepped then) – Christian Hupfer May 6 '16 at 12:38
5

Here is a completely different approach using pgfkeys. With all of the comments the code is longer than Manuel's so pehaps I can't claim simplicity, but I just like using pgfkeys for things like this.

To show that it works here's what my code produces: enter image description here ...and here is the code:

\documentclass{article}
\usepackage{etoolbox}

\usepackage{pgfkeys}
\pgfkeys{/MyLabels/.is family, /MyLabels,
  % set labels using \pgfkeyssetvalue => can test with \pgfkeysifdefined
  % This allows arbitrary new keys to be defined on the fly
  .unknown/.code={\pgfkeyssetvalue{/MyLabels/\pgfkeyscurrentname}{#1}},
}

\newcounter{getID}% a dummy counter to imitate \getID

\newcommand\SetLabel[1]{% set label to current value of getID
  \stepcounter{getID}% might want \refstepcounter in real life
  Settting: #1 = ID\arabic{getID} %
  \pgfkeys{/MyLabels,#1/.expanded=ID\arabic{getID}}% force expansion
  (stored value: \pgfkeys{/MyLabels/#1}).
}
\newcommand\UseLabel[1]{% print label if it bas been defined
  Getting: #1 =
   \pgfkeysifdefined{/MyLabels/#1}%
      {\pgfkeysvalueof{/MyLabels/#1}}% print if defined
      {???}% question marks if not defined
  .%
}

\begin{document}

\SetLabel{first}

\SetLabel{second}

\UseLabel{first}

\UseLabel{second}

\UseLabel{first}

\end{document}

Edit: Solution II

I realised that this is all overkill: you should just be using LaTeX's \label and \ref commands. When you create a label what happens is that the current value of \@currentlabel is written to the aux file and this is then accessible on the next compile using \ref. So if you set \@currentlabel to the output of \getID then you are done. You can't, however, set \@currentlabel equal to \getID{item} because the \getID command contains some unexpandable stuff. Instead you should make \getID set a dummy variable or, better still, set \@currentlabel.

Here is a second solution based on your MWE:

\documentclass[11pt]{report}

\newcommand\textLabel[2]{\getid{#2}\label{#1}}

\makeatletter
\newcounter{req}
\newcommand{\getid}[1]{%
    \stepcounter{req}%
    \def\@currentlabel{#1.\arabic{req}}%
}
\makeatother

\begin{document}
    \begin{itemize}
        \item \textLabel{itemkeyone}{ITEM}
        \item \textLabel{itemkeytwo}{ITEM}
    \end{itemize}

    \ref{itemkeyone} %I want: ITEM.1

    \ref{itemkeytwo} %I want: ITEM.1
\end{document}

As this is using \label and \ref behind the scenes the labels will persist from one compile to the next but you will need to compile the document twice before it takes effect.

Here's the output:

enter image description here

  • Awesome, I'm going wih the solution II. It works perfectly, even using some other macro to get the formatted ID :) Thank you very much! – Luca May 6 '16 at 14:08
  • @Nopaste Glad it was helpful! I just edited Solution II to make it a little nicer: you shouldn't call \getid inside the \texLabel command. Rather, \textLabel should be given the "label" and then call \getid with it. – Andrew May 6 '16 at 21:56
2

A style with writing textlabels as faked labels to the .aux file, manually establishing the \hypertarget and \hyperlink then.

\documentclass[11pt]{report}       

\usepackage{hyperref}


\newcounter{req}

\makeatletter
\newcommand{\textLabel}[2]{%
  \stepcounter{req}%
  \hypertarget{#1.\number\value{req}}{#2.\arabic{req}}%
  \immediate\write\@auxout{%
    \string\newlabel{#1.\number\value{req}}{{#2.\number\value{req}}{}{}{}{}} % Write the text label!
  }
  \expandafter\xdef\csname #1\endcsname{%
    \unexpanded{\hyperlink}{#1.\number\value{req}}{\getrefnumber{#1.\number\value{req}}}
  }
}
\makeatother


\begin{document}
\chapter{Foo} \label{foolabel}
    \begin{itemize}
        \item \textLabel{itemkeyone}{ITEM}
          \clearpage
        \item \textLabel{itemkeytwo}{ITEM}
    \end{itemize}

\clearpage
    \itemkeyone %I want: ITEM.1, instead produces ITEM.3

    \itemkeytwo
\end{document}  

enter image description here

1

I think this works, but haven't tested thoroughly

\documentclass{scrartcl}

\usepackage{hyperref}

\newcounter{req}
\newcommand*\getid[1]{\def\thereq{#1.\arabic{req}}\refstepcounter{req}\thereq} 

\begin{document}

\begin{itemize}
  \item \getid{ITEM} \label{itemkeyone}
  \item \getid{ITEM} \label{itemkeytwo}
\end{itemize}
\ref{itemkeyone}

\end{document}
  • Thank you very much! It seems to work fine, (I'm not able to understand how it works, however) But there is some problem, maybe I semplified the problem too much. If I try to add a link with: \hyperref[test]{\getid{ITEM}} \label{itemkeyone} it doesn't work anymore. That list is a list of names of items, each item should be link with it's definition (in some part of the document), and from the definition I should be able to go to the list... edit. It works if I put the label inside the hyperref – Luca May 6 '16 at 11:01
  • It is normal that I'm not able to replace #1.\arabic{req} with a macro? (the macro that really generates the id) – Luca May 6 '16 at 11:20
  • 1
    @Nopaste It depends what is inside the macro. One alternative is to have the macro set a dummy variable, which will be expandable, and then use that. – Andrew May 6 '16 at 13:52

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

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