Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

It is likely that the answer to the problem is buried on this site somewhere -- it is my limited understanding of macro expansion which seems to defy my search foo...

I am trying to define macros which I can use like this:

\begin{document}

  \AimTaskDef{1}{task 1}
  \AimTaskAdd{1}{task 2}

  \AimTaskDef{2}{task 3}
  \AimTaskAdd{2}{task 4}

  ...
  % later, in some table
  \AimTaskGet{1}

  \AimTaskGet{2}

Which should result in output like this

\begin{itemize}
 \item task 1
 \item task 2
\end{itemize}

\begin{itemize}
 \item task 3
 \item task 4
\end{itemize}

I have the following macro set in place:

 \def\AimTaskDef#1#2{%
   \expandafter\def\csname AimTask#1\endcsname{ \item #2}
 }

 \def\AimTaskAdd#1#2{%
   \expandafter\def\tmp{\expandafter\csname AimTask#1\endcsname}
   \expandafter\def\csname AimTask#1\endcsname{ \tmp \item #2}
 }

 \def\AimTaskGet#1{ \begin{itemize} \csname AimTask#1\endcsname \end{itemize} }

I am doing something wrong with the 'tmp' definition/usage, as I see the following errors:

\AimTaskAdd #1#2->\expandafter \def \tmp
                                         {\expandafter \csname AimTask#1\end...
l.28       \AimTaskAdd{1}{task 2}

?
! Missing control sequence inserted.
<inserted text>
                \inaccessible
l.28       \AimTaskAdd{1}{task 2}

?

and the result contains only the last specified task. I would appreciate any insight in what is going on with \tmp.

Many thanks, Andre.

share|improve this question
    
Pretty sure your problem is here \expandafter\def\tmp. The \expandafter skips \def and tries to expand \tmp which hasn't been defined. I get an undefined control sequence at that point when copy/pasting your code into a document. –  Scott H. Jul 13 '12 at 1:35
    
Thanks, that explanation makes sense, even to me :-) –  Andre Merzky Jul 13 '12 at 8:23

2 Answers 2

up vote 7 down vote accepted

The following does what you're after (but requires e-TeX; it can be modified to work without as well):

enter image description here

\documentclass{article}

\makeatletter
\def\AimTaskDef#1#2{%
  \expandafter\def\csname AimTask#1\endcsname{ \item #2}%
}
\def\AimTaskAdd#1#2{%
  \ifcsname AimTask#1\endcsname
    % Check for repeated entries (AM):
    \@expandtwoargs\in@
      {\detokenize{\item #2}}
      {\detokenize\expandafter\expandafter\expandafter{\csname AimTask#1\endcsname}}%
    \ifin@\else
      \expandafter\g@addto@macro\csname AimTask#1\endcsname{\item #2}%
    \fi      
  \else
    \AimTaskDef{#1}{#2}%
  \fi%
}
\def\AimTaskGet#1{\begin{itemize}\csname AimTask#1\endcsname\end{itemize}}
\makeatother
\begin{document}

Explicit definition:

\begin{itemize}
 \item task 1
 \item task 2
\end{itemize}

\begin{itemize}
 \item task 3
 \item task 4
\end{itemize}

\ldots

Macro definition:

\AimTaskAdd{1}{task 1}
\AimTaskAdd{1}{task 2}
\AimTaskDef{2}{task 3}
\AimTaskAdd{2}{task 4}
\AimTaskAdd{2}{task 4} % Repeated (AM)


\AimTaskGet{1}

\AimTaskGet{2}
\end{document}

In order to append things to an existing macro, it's sufficient to use \g@addto@macro{<macro>}{<stuff>}. Also, I've updated \AimTaskAdd to check for the existence of AimTask#1 and create it if it doesn't exist. That allows you to do

\AimTaskAdd{1}{task 1}
\AimTaskAdd{1}{task 2}

without being concerned that AimTask1 exists already (I've also used this in my example as an illustration).

share|improve this answer
    
\g@addto@macro -- cool! Excellent, that is exactly what I was looking for, really :-) –  Andre Merzky Jul 13 '12 at 8:21

An implementation with expl3 facilities.

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\AimTaskDef}{ m m }{ \andre_task_def:nn { #1 } { #2 } }
\NewDocumentCommand{\AimTaskAdd}{ m m }{ \andre_task_add:nn { #1 } { #2 } }
\NewDocumentCommand{\AimTaskGet}{ m }
 {
  \begin{itemize} \prop_get:Nn \g_andre_tasks_prop { #1 } \end{itemize}
 }

\prop_new:N \g_andre_tasks_prop
\msg_new:nnn { andre } { task-exist }
 {
  Task~#1~already~exists:~I'm~overwriting~it
 }
\msg_new:nnn { andre } { task-not-exist }
 {
  Task~#1~doesn't~exist~yet:~I'm~defining~it
 }

\cs_new_protected:Npn \andre_task_def:nn #1 #2
 {
  \prop_if_in:NnT \g_andre_tasks_prop { #1 } 
   {
    \msg_warning:nnx { andre } { task-exist } { #1 }
   }
  \prop_gput:Nnn \g_andre_tasks_prop { #1 } { \item #2 }
 }
\cs_new_protected:Npn \andre_task_add:nn #1 #2
 {
  \prop_if_in:NnTF \g_andre_tasks_prop { #1 }
   {
    \prop_gput:Nnx \g_andre_tasks_prop { #1 }
     {
      \prop_get:Nn \g_andre_tasks_prop { #1 } \exp_not:n { \item #2 }
     }
   }
   {
    \msg_warning:nnx { andre } { task-not-exist } { #1 }
    \prop_gput:Nnn \g_andre_tasks_prop { #1 } { \item #2 }
   }
 }
\ExplSyntaxOff


\begin{document}

\AimTaskDef{1}{task 1}
\AimTaskAdd{1}{task 2}

\AimTaskDef{2}{task 3}
\AimTaskAdd{2}{task 4}

\AimTaskGet{1}

\AimTaskGet{2}

\AimTaskDef{1}{task 1}
\AimTaskAdd{3}{task !}
\end{document}

The macros warn if \AimTaskDef is used with an already defined task and, in this case, overwrite it (this could be changed); they also warn if \AimTaskAdd is used on a not yet defined task (and define it).

A possibly more efficient version (thanks to Bruno Le Floch):

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\AimTaskDef}{ m m }{ \andre_task_def:nn { #1 } { #2 } }
\NewDocumentCommand{\AimTaskAdd}{ m m }{ \andre_task_add:nn { #1 } { #2 } }
\NewDocumentCommand{\AimTaskGet}{ m }
 {
  \begin{itemize} \prop_get:Nn \g_andre_tasks_prop { #1 } \end{itemize}
 }

% Variables    
\prop_new:N \g_andre_tasks_prop
\tl_new:N \l_andre_temp_tl

% Messages
\msg_new:nnn { andre } { task-exists }
 {
  Task~#1~already~exists:~I'm~overwriting~it
 }
\msg_new:nnn { andre } { task-exists-not }
 {
  Task~#1~doesn't~exist~yet:~I'm~defining~it
 }

% Functions
\cs_new_protected:Npn \andre_task_def:nn #1 #2
 {
  \prop_if_in:NnT \g_andre_tasks_prop { #1 } 
   {
    \msg_warning:nnx { andre } { task-exists } { #1 }
   }
  \prop_gput:Nnn \g_andre_tasks_prop { #1 } { \item #2 }
 }
\cs_new_protected:Npn \andre_task_add:nn #1 #2
 {
  \prop_get:NnNTF \g_andre_tasks_prop { #1 } \l_andre_temp_tl
   {
    \tl_put_right:Nn \l_andre_temp_tl { \item #2 }
    \prop_gput:NnV \g_andre_tasks_prop { #1 } \l_tmpa_tl
   }
   {
    \msg_warning:nnx { andre } { task-exists-not } { #1 }
    \prop_gput:Nnn \g_andre_tasks_prop { #1 } { \item #2 }
   }
 }
\ExplSyntaxOff


\begin{document}

\AimTaskDef{1}{task 1}
\AimTaskAdd{1}{task 2}

\AimTaskDef{2}{task 3}
\AimTaskAdd{2}{task 4}

\AimTaskGet{1}

\AimTaskGet{2}

\AimTaskDef{1}{task 1}
\AimTaskAdd{3}{task !}
\end{document}
share|improve this answer
    
I would personally use \prop_get:NnNTF in \andre_task_add:nn to retrieve the old value in a temporary variable and test if_in at the same time. It may still be marked as an experimental function in the version on CTAN, but it is now not experimental anymore (upload to CTAN in a few days). The only drawback of using \prop_get:NnNTF is that you will need an auxiliary token list variable. –  Bruno Le Floch Jul 13 '12 at 15:02

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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