2

I am confused when using \g@addto@macro in a loop. My code is:

\documentclass{article}

\usepackage{ifthen}
\usepackage{newfile}
\usepackage{xstring}

\makeatletter

\DeclareRobustCommand\@initial[1]{#1}
\DeclareRobustCommand\initial[1]{%
    \StrLeft{#1}{1}[\firstletter]%
    \@initial{\firstletter}}

\newcounter{authors@count}
\newcommand\authors[2]{
    \stepcounter{authors@count}
    \expandafter\gdef\csname @author@first\arabic{authors@count}\endcsname{#1}
    \expandafter\gdef\csname @author@last\arabic{authors@count}\endcsname{#2}
}

\newcounter{i}
\def\authorsliststring{}
\def\authorslist{%
    \whiledo{\value{i} < \value{authors@count}}{%
        \stepcounter{i}%
        \@nameuse{@author@last\thei}\space% for test only
        \initial{\@nameuse{@author@first\thei}}.% for test only
        \g@addto@macro\authorsliststring{%
            \@nameuse{@author@last\thei} 
            \initial{\@nameuse{@author@first\thei}}.}%
        \ifthenelse{\thei = \theauthors@count}{}
            {,\space% for test only
                \g@addto@macro\authorsliststring{, }%
        }%
    }
}

\newcommand\writetoc{
    \newoutputstream{txt}
    \openoutputfile{\jobname.txt}{txt}
    \addtostream{txt}{\authorsliststring}
    \closeoutputstream{txt}
}

\makeatother

\begin{document}

\authors{Alex}{Andersson}
\authors{Barbara}{Brown}
\authors{Charles}{Chaplin}

\authorslist

\authorsliststring

\writetoc

\end{document}

When developing my class, it is convenient for me to write separately the First and Nast Name in separate variables using repeated \authors commands. Later, using a separate \authorslist macro, I generate \authorsliststring string as Last Name and the first char of First Name (it's a long explanation here why I need to do this in a separate \authorslist command and why I need \initial command in this way, but I need it like this). The string \authorsliststring is then used in the right places in the class and written to the file to generate TOC later from other TeX file. I need to get the same in \authorsliststring and in the file - "Andersson A., Brown B., Chaplin C."

However, on the screen I see:

1

and I get in the file:

Chaplin \initial {Charles}., Chaplin \initial {Charles}., Chaplin \initial {Charles}.

Besides, I do not understand how to write to the file not \initial {Charles}, but C..

7
  • To see what's happening, write \show\authorsliststring after setting a bunch of \authors. You'll see that macro doesn't actually have any of the author's names in it, and in all cases refer to the author by index \thei. That \thei points to whatever the last value of i is, hence all the same (last) authors being listed. You should expand the value of \thei so that it stores the actual number rather than the macro \thei. That's one option.
    – Werner
    Nov 19, 2022 at 17:27
  • Is there a need to write the \authorsliststring to a file?
    – Werner
    Nov 19, 2022 at 17:44
  • @Werner Yes, it's necessary
    – Crosfield
    Nov 19, 2022 at 18:32
  • @Crosfield What is the purpose of \@initial ? Nov 19, 2022 at 20:55
  • @UlrichDiez In my class this function is much more complicated. It takes as a parameter any number of space-separated words, including those in languages containing Slavic characters, and cuts out the first letters in each. It uses functions from xstring. Here I have simplified it as much as possible.
    – Crosfield
    Nov 19, 2022 at 21:12

2 Answers 2

2

The rôle of the macro-combination \initial-\@initial is not exactly clear to me.

Seems \initial is a macro-mechanism which internally uses \@initial and \StrLeft and at some point defines the macro \firstletter to expand to the first letter of the first name of the author.

As long as within your \authorslist-recursion/\whiledo-recursion you properly separate which actions are expandable and which are not—e.g., \StrLeft (and thus also \initial) is not, you can combine the trick of (ab?)using \romannumeral for keeping TeX triggering expansion until encountering \UD@stopromannumeral with the trick of exchanging macro arguments in order to have TeX line up the tokens of a \g@addto@macro-directive for adding a name to \authorsliststring in one go:

\documentclass{article}

\usepackage{ifthen}
\usepackage{newfile}
\usepackage{xstring}

\makeatletter
\newcommand\UD@Exchange[2]{#2#1}%
\@ifdefinable\UD@stopromannumeral{\chardef\UD@stopromannumeral=`\^^00}%

% Assume \initial is a function/macro-mechanism which at some point defines the macro 
% \ScratchmacroForFirstLetter to deliver the first letter of the first name.
% ( \initial itself probably is not fully expandable, e.g., due to calling \StrLeft
%   or ehatever,  which in turn is not fully expandable. In any case in the end
%   the macro \ScratchmacroForFirstLetter is defined. )
\newcommand\ScratchmacroForFirstLetter{}%
\@ifdefinable\initial{%
  \DeclareRobustCommand\initial[1]{\StrLeft{#1}{1}[\ScratchmacroForFirstLetter]}%
}%

\newcounter{authors@count}
\newcommand\authors[2]{%
    \stepcounter{authors@count}%
    \global\@namedef{@author@first\arabic{authors@count}}{#1}%
    \global\@namedef{@author@last\arabic{authors@count}}{#2}%
}

\newcounter{i}%
\newcommand*\authorsliststring{}%
\newcommand*\authorslist{%
    \ifnum\value{i} < \value{authors@count}% Why \whiledo if the recursion can be done without? ;-)
        \stepcounter{i}%
        % ==============================================================================================
        % Typeset the name of the author:
        % ----------------------------------------------------------------------------------------------
        % - Typeset the first name and a space:
        \@nameuse{@author@last\thei} %
        % - Somehow get the first letter of the first name of the author into the macro
        %   \ScratchmacroForFirstLetter - this is the job of \initial:
        \initial{\@nameuse{@author@first\thei}}%
        % - Apply  \ScratchmacroForFirstLetter for typesetting the first letter of the first name of
        %   the author, trailed by a dot:
        \ScratchmacroForFirstLetter.%
        % - Probably typeset a comma and a space:
        \ifthenelse{\thei = \theauthors@count}{}{, }%
        % ==============================================================================================
        % Add the name of the author to the macro \authorsliststring:
        % ----------------------------------------------------------------------------------------------
        %  - Somehow get the first letter of the first name of the author into the macro
        %    \ScratchmacroForFirstLetter - this is the job of \initial:
        \initial{\@nameuse{@author@first\thei}}%
        %  - From now on everything else can be done by means of expansion-methods -
        %    do some argument-exchanging-trickery via \UD@Exchange for lining up the tokens within
        %    the brace-group which encloses the last argument of the \g@addto@macro-directive;
        %    within the brace-group which encloses the last argument of the \g@addto@macro-directive
        %    use \romannumeral for keeping expansion going until TeX sees \UD@stopromannumeral:
        \expandafter\g@addto@macro\expandafter\authorsliststring\expandafter{%
          \romannumeral
          \ifnum\value{i}=\value{authors@count}\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi    
          {\UD@Exchange{}}{\UD@Exchange{, }}%
          {%
            \expandafter\UD@Exchange\expandafter{%
               \ScratchmacroForFirstLetter.%
            }{%
              \expandafter\expandafter\expandafter\UD@stopromannumeral
              \csname @author@last\thei\expandafter\endcsname\@firstofone{ }%
            }%
          }%
        }%
    \expandafter\authorslist\fi
}

\newoutputstream{txt}%
\newcommand\writetoc{%
    \openoutputfile{\jobname.txt}{txt}%
    \addtostream{txt}{\authorsliststring}%
    \closeoutputstream{txt}%
}

\makeatother

\begin{document}

\authors{Alex}{Andersson}
\authors{Barbara}{Brown}
\authors{Charles}{Chaplin}

\setcounter{i}{0}%
\authorslist

\texttt{\vrule\string\authorsliststring=\meaning\authorsliststring\vrule}

\authorsliststring

\writetoc

\end{document}

enter image description here


To be honest, to me this seems unnecessarily complicated. Using expl3 instead of xstring's \StrLeft, the first letter of the first name can probably be obtained via \str_head_ignore_spaces:n, which is expandable:

\documentclass{article}

\usepackage{newfile}

\ExplSyntaxOn
\clist_new:N  \__MyStuff_Authors_clist
\cs_new_protected:Npn \authors #1#2 
  {
    \clist_gput_right:Nx \__MyStuff_Authors_clist 
                         {{#2~\str_head_ignore_spaces:n{#1}.}}
  }
\cs_new:Npn \authorslist 
  {  \clist_use:Nnnn \__MyStuff_Authors_clist {,~}{,~}{,~}  }
\ExplSyntaxOff

\newoutputstream{txt}%
\newcommand\writetoc{%
    \openoutputfile{\jobname.txt}{txt}%
    \addtostream{txt}{\authorslist}%
    \closeoutputstream{txt}%
}

\begin{document}

\authors{Alex}{Andersson}
\authors{Barbara}{Brown}
\authors{Charles}{Chaplin}

\noindent
\authorslist

\writetoc

% Let's see the content of  \jobname.txt - with older LaTeX-releases
% you might need to add   \usepackage{verbatim}  to the preamble for
% this to work out, but seems the package  newfile  already does that
% for you:

\verbatiminput{\jobname.txt}

\end{document}

enter image description here

1

As mentioned in comment, \thei needs to be expanded in the argument of \g@addto@macro. Here, I do that by saving the fully expanded definition of \thei as \Thei (several expansions are required), and then using enough \expandafters (shorthanded as \z) in the definition to replace occurrences of \Thei with its expansion.

\documentclass{article}

\usepackage{ifthen}
\usepackage{newfile}
\usepackage{xstring}

\makeatletter

\DeclareRobustCommand\@initial[1]{#1}
\DeclareRobustCommand\initial[1]{%
    \StrLeft{#1}{1}[\firstletter]%
    \@initial{\firstletter}}

\newcounter{authors@count}
\newcommand\authors[2]{
    \stepcounter{authors@count}
    \expandafter\gdef\csname @author@first\arabic{authors@count}\endcsname{#1}
    \expandafter\gdef\csname @author@last\arabic{authors@count}\endcsname{#2}
}

\newcounter{i}
\def\authorsliststring{}
\def\Al{@author@last}
\def\Af{@author@first}
\let\z\expandafter
\def\authorslist{%
    \whiledo{\value{i} < \value{authors@count}}{%
        \stepcounter{i}%
        \edef\Thei{\thei}%
%        \@nameuse{\Al\thei}\space% for test only
%        \initial{\@nameuse{\Af\thei}}.% for test only
        \z\z\z\g@addto@macro\z\z\z\authorsliststring\z\z\z{\z\z\z%
            \@nameuse\z\z\z{\z\z\z\Al\z\Thei\z}\z\ \z 
            \initial\z{\z\@nameuse\z{\z\Af\Thei}}.}%
        \ifthenelse{\thei = \theauthors@count}{}
            {%,\space% for test only
                \g@addto@macro\authorsliststring{, }%
        }%
    }
}

\newcommand\writetoc{
    \newoutputstream{txt}
    \openoutputfile{\jobname.txt}{txt}
    \addtostream{txt}{\authorsliststring}
    \closeoutputstream{txt}
}

\makeatother

\begin{document}

\authors{Alex}{Andersson}
\authors{Barbara}{Brown}
\authors{Charles}{Chaplin}

\authorslist

\authorsliststring

\writetoc

\end{document}

enter image description here

3
  • Thank you for your reply! Very useful. I know xstring commands are not expandable. Can you tell how to write the final result to the file as Andersson A., Brown B., Chaplin C., but not Andersson \initial {Alex}., Brown \initial {Barbara}., Chaplin \initial {Charles}..
    – Crosfield
    Nov 19, 2022 at 18:43
  • @Crosfield Since \initial is not expandable, there is no way to do this, unless you reformulate your approach avoiding the xstring commands. Nov 19, 2022 at 19:38
  • I got it. Just I thought that there is some trick that will allow to return the 'final' value through another function and this 'final' value will be just a string of characters.
    – Crosfield
    Nov 19, 2022 at 21:17

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .