类定义中的 \g@addto@macro 出现意外行为

类定义中的 \g@addto@macro 出现意外行为

我正在为期刊开设一个新课程。

在这个类中,我定义了插入文章作者的命令。我预计在基本形式下一切都能完美运行,也就是说,如果作者姓名以这种方式插入:

\author[1]{Federico Tramarin}

其中索引([1])仅用于与从属关系链接,并且它运行正常,因此我们不应该担心这一点。

出于编辑原因,我被要求要求作者也插入他们姓名的缩写形式。例如:John Smith 和 J. Smith。

为了适应这一点,我创建了一个键值定义,以便允许以这种方式插入作者:

\author[1]{name=Federico,surname=Tramarin,abbreviation=F. Tramarin}

这些键的定义方式很简单:

\def\@tmpauthname{}
\define@key{authornames}{name}{%
    \def\@author@name{#1}}
\define@key{authornames}{surname}{%
    \def\@author@surname{#1}}
  \define@key{authornames}{abbreviation}{%
    \def\@authorshortname{#1}}

然后我修改了现有的宏

  • 使用第二个参数来设置键的值
  • 为完整作者姓名创建一个临时字符串
  • 使用它代替第二个参数

供您参考,该宏实际上定义了两个宏:

  1. \@AIauthors- 创建标题后要排版的最终经典字符串,其中名称和上标包含隶属标记等。
  2. \@AIauthorsNames- 创建一个更简单的字符串,其中仅包含用“;”分隔的全名。

然后在使用 排版标题页时调用这两个宏\maketitle

我在这里放了我正在使用的代码。请注意,下面的代码几乎与工作代码(允许仅插入一个带有名称的参数的代码)相同,因为每次您看到\@tmpauthname原始(和工作)代码时,它都包含一个#2

\newcounter{auth}
\def\@@author[#1]#2{%
    \setkeys{authornames}{#2}%
    \gdef\@tmpauthname{\@author@name\space\@author@surname}%
    \typeout{Debug - @@author macro 1-> auth:\@tmpauthname}
    \g@addto@macro\@AIauthors{%
    \def\baselinestretch{1}%
    \refstepcounter{auth}% increment by 1
    \newif\ifnotyetprocessed% this if is needed to check if this author has already been counted for corresponding
    \notyetprocessedtrue
    \typeout{Debug - @@author macro 2-> auth:\@tmpauthname}
    \authorsep\@tmpauthname\unskip\textsuperscript{%
        \@for\@@affmark:=#1\do{%
            %\typeout{Debug - @@author macro -> auth:\theauth\ affmark:\@@affmark - author:#1}
            \edef\affnum{\@ifundefined{X@\@@affmark}{affn?}{\AIrefMark{\@@affmark}}}%
            \unskip\sep\affnum%
            \ifnotyetprocessed%
                \NewAIaffLabel{auth-\theauth}{+}%
            \fi%
            \edef\cormark{\@ifundefined{X@cor-\theauth}{}{%
                \if@showcorr
                    {,\AIrefMark{cor-\theauth}}%
                \else
                    {}%
                \fi}}%
            %           \unskip\sep\cormark\let\sep=,% this line was wrong, since it will add a extra comma in case of multiple affiliations
            \unskip\cormark\let\sep=,% now the issue is for the corresponding author with multiple affiliations
            \ifx\cormark\@empty%
            \else
                \ifnotyetprocessed% if the author is corresponding and is the first time we are here
                    \g@addto@macro\@CorrespondingAuthorName{\@tmpauthname}% add the author name to the output
                    \notyetprocessedfalse %if it is a subsequent round, do nothing
                \fi
            \fi
            }%
        }%
    \gdef\authorsep{\unskip,\space}%
    \global\let\sep\@empty%
    }
    \ifx\@AIauthorsNames\@empty
        \typeout{Debug - @@author macro 3-> auth:\@tmpauthname}
        \g@addto@macro\@AIauthorsNames{\@tmpauthname}%
        \typeout{Debug - @@author macro 3-> AIauth:\@AIauthorsNames}
    \else
        \typeout{Debug - @@author macro 4-> auth:\@tmpauthname}
        \g@addto@macro\@AIauthorsNames{;\space\@tmpauthname}%
        \typeout{Debug - @@author macro 4-> AIauth:\@AIauthorsNames}
    \fi
}

问题是,经过这个我认为微不足道的修改后,我得到了一个只有姓氏重复的字符串n次(与n作者人数)。

正如您所看到的,我插入了一些 \typeout 来进行非常基本的调试以查看发生了什么。

我在日志中看到的内容是这样的:

  • 对宏进行第一次扫描,其中\@tmpauthname包含当前作者的姓名,但跳过了 内的所有部分\g@addto@macro\@AIauthors{%。实际上,如果您查看调试消息中的数字,我会看到数字 1 和 3 或 4。
  • 对宏进行第二次扫描(我在调试中看到数字 2)执行,\g@addto@macro\@AIauthors{%但副作用是这次仅使用保存的最后一个值来执行\@tmpauthname

我已经尽我所能尝试了,但还是无法解决问题。我的理解(希望正确)是,\@AIauthors稍后会评估,因为只有当我请求标题页时才需要它,因此 latex 仅在需要时才扩展该部分。不幸的是,这种情况只发生在指定所有作者后,当它扩展的值时,后者仅包含最后一个值。当考虑\@tmpauthname经典论点时,这显然不会发生。#2

在我看来,我需要一种方法来告诉 LaTeX 在插入每个新作者后立即评估所有内容,但我不知道 1) 如何实现这一点,2) 这是否是正确的解决方案。

我请求您的帮助来解决这个问题,这让我抓狂并且已经尝试了一整天(这显然是由于我对宏定义的无知)。

非常感谢您给予我任何帮助。

谨致问候 Federico

答案1

当询问与错误跟踪和修改代码有关的问题时,请始终提供最小可重现示例(MRE)/最小完整可验证示例(MCVE),以便测试对代码的更改是否真的解决了问题!

如果你不这样做,那么愿意提供帮助的人注定会猜测他们的尝试/修改可能会解决问题,但这实际上不是一个有效的解决问题的策略。

这次我会做一些猜测。

但这样做的时候我感觉不太舒服。


由于使用,\g@addto@macro我假设它与 LaTeX 2ε 有关。

你说:

我在这里放了我正在使用的代码。请注意,下面的代码几乎与工作代码(允许仅插入一个带有名称的参数的代码)相同,因为每次您看到\@tmpauthname原始(和工作)代码时,它都包含一个#2

为什么不去替换#2而是\@tmpauthname“外包”那些代码部分,将#2工作代码中曾经保存的内容保存到持久辅助宏中,并调用该持久辅助宏,同时将工作宏中曾经形成的那些标记作为其第二#2参数提供?

可能是这样的——未经测试,因为您没有提供可以测试事物的最小可重现/完整且可验证的示例!!!:

\newcounter{auth}
\newif\ifnotyetprocessed

\def\@@author[#1]#2{%
  \setkeys{authornames}{#2}%
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  \begingroup
  % In LaTeX 2e \toks@ is a token register.
  \toks@{\endgroup\PERSISTINGHELPERMACRO{#1}}%
  \protected@edef\TEMPORARYHELPERMACRO{%
    \the\toks@{\@author@name\space\@author@surname}%
  }%
  \TEMPORARYHELPERMACRO
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %% Alternatively, if \expanded and \unexpanded are available:
  %\begingroup
  %\let\protect\@unexpandable@protect
  %\expanded{%
  %  \endgroup
  %  \unexpanded{\PERSISTINGHELPERMACRO{#1}}{\@author@name\space\@author@surname}%
  %}%
}%
\long\def\PERSISTINGHELPERMACRO#1#2{%
  \g@addto@macro\@AIauthors{%
    \def\baselinestretch{1}%
    \refstepcounter{auth}% increment by 1
    \notyetprocessedtrue
    \authorsep#2\unskip
    \textsuperscript{%
      \@for\@@affmark:=#1\do{%
        \edef\affnum{%
          \@ifundefined{X@\@@affmark}{affn?}{\AIrefMark{\@@affmark}}%
        }%
        \unskip\sep\affnum
        \ifnotyetprocessed
          \NewAIaffLabel{auth-\theauth}{+}%
        \fi
        \edef\cormark{%
          \@ifundefined{X@cor-\theauth}{}{%
            \if@showcorr
              {,\AIrefMark{cor-\theauth}}%
            \else
              {}%
            \fi
          }%
        }%
        \unskip\cormark
        \let\sep=,% now the issue is for the corresponding author with multiple affiliations
        \ifx\cormark\@empty
        \else
          \ifnotyetprocessed % if the author is corresponding and is the first time we are here
            \g@addto@macro\@CorrespondingAuthorName{#2}% add the author name to the output
            \notyetprocessedfalse % if it is a subsequent round, do nothing
          \fi
        \fi
      }%
    }%
    \gdef\authorsep{\unskip,\space}%
    \global\let\sep\@empty
  }%%
  \ifx\@AIauthorsNames\@empty
    \g@addto@macro\@AIauthorsNames{#2}%
  \else
    \g@addto@macro\@AIauthorsNames{;\space#2}%
  \fi
}%

答案2

您不必担心扩展,只需使用 key=value 解析器即可,它不会将值存储在宏内,而是将它们作为单独的参数转发给另一个宏。

这是通过expkv-cs免责声明:我是作者)使用宏\ekvcSplitAndForward

请注意,我并没有对您的代码进行太多更改,我只是\@tmpauthname用转发的正确内容进行了替换#1(并且由于我们将名称作为正常参数,因此我们不必包含任何扩展控制来实现这一点)。

不过我做了一些改变:

  • 用作\NewDocumentCommand的前置宏\author
  • 用于\NewDocumentCommand定义\@@author受保护的。
  • 移到\newif函数体之外。
  • 通过使用辅助工具将\g@addto@macro和作者的格式分离\output@author(这样代码看起来更干净,性能也应该更好)。
  • 我删除了你的一些调试消息
  • 我把 改为\g@addto@macroa 表示\gdef为空\@AIauthorsNames
  • 我通过%在某些行尾添加一些不需要的空格来删除。
  • 最后,我重新格式化了你的代码以适合我的口味(缩进 2 个空格,代码行不超过 80 个字符(包括注释)...)
\RequirePackage{expkv-cs}

\NewDocumentCommand\author{O{} m}
  {\@author{#2}{#1}}
\ekvcSplitAndForward\@author\@@author
  {
     name         = {}
    ,surname      = {}
    ,abbreviation = {}
  }
\ekvcSecondaryKeys\@author
  {
    % abbreviation is such a long word... Let's define an abbreviated form.
    alias abbr = abbreviation
  }

\newcounter{auth}
% this if is needed to check if this author has already been counted for
% corresponding
\newif\ifnotyetprocessed
\NewDocumentCommand\@@author{m m m m}
  {%
    % #1: name
    % #2: surname
    % #3: abbreviation
    % #4: affiliations
    % handle mandatory keys (throw an error if they were omitted)
    \IfEmptyT{#1#2}
      {\PackageError{trama}{Authors must have names, please provide them.}{}}%
    \IfEmptyT{#3}
      {\PackageError{trama}{Missing name abbreviation.}{}}%
    \g@addto@macro\@AIauthors{\output@author{#1 #2}{#4}}%
    \ifx\@AIauthorsNames\@empty
      \gdef\@AIauthorsNames{#1 #2}%
      \typeout{Debug - @@author macro 3-> AIauth:\@AIauthorsNames}%
    \else
      \g@addto@macro\@AIauthorsNames{; #1 #2}%
      \typeout{Debug - @@author macro 4-> AIauth:\@AIauthorsNames}%
    \fi
  }
\newcommand\output@author[2]
  {%
    % #1: <name> <surname>
    % #2: affiliations
    \def\baselinestretch{1}%
    \refstepcounter{auth}% increment by 1
    \notyetprocessedtrue
    \typeout{Debug - @@author macro 2-> auth:#1}%
    \authorsep#1\unskip\textsuperscript
      {%
        \@for\@@affmark:=#2\do
          {%
            %\typeout
            %  {%
            %    Debug - @@author macro -> auth:\theauth\ affmark:\@@affmark -
            %    author:#4
            %  }%
            \edef\affnum
              {\@ifundefined{X@\@@affmark}{affn?}{\AIrefMark{\@@affmark}}}%
            \unskip\sep\affnum
            \ifnotyetprocessed
              \NewAIaffLabel{auth-\theauth}{+}%
            \fi
            \edef\cormark
              {%
                \@ifundefined{X@cor-\theauth}
                  {}%
                  {%
                    \if@showcorr
                      {,\AIrefMark{cor-\theauth}}%
                    \else
                      {}%
                    \fi
                  }%
              }%
            % this line was wrong, since it will add a extra comma in case of
            % multiple affiliations:
            %           \unskip\sep\cormark\let\sep=,%
            % now the issue is for the corresponding author with multiple
            % affiliations
            \unskip\cormark\let\sep=,%
            \ifx\cormark\@empty
            \else
              % if the author is corresponding and is the first time we are here
              \ifnotyetprocessed
                % add the author name to the output
                \g@addto@macro\@CorrespondingAuthorName{#1}%
                \notyetprocessedfalse %if it is a subsequent round, do nothing
              \fi
            \fi
          }%
      }%
    \gdef\authorsep{\unskip,\space}%
    \global\let\sep\@empty
  }

答案3

就像更新一样,我创建了一个可以工作的 MWE,它基于 @Ulrich Diez 的帮助。我感谢他的建议,使代码可以工作。

在接下来的几天里,我也会尝试@Skillmon 的解决方案,因为我发现它非常简洁(好吧,在我看来)。

如果有人发现我的代码中存在一些潜在问题,我会很高兴听到并修改它。

再次感谢你

费德里科

\documentclass{article}
\usepackage{keyval}

\makeatletter

\def\author{\@@author}
\def\@AIaffiliations{}
\def\@AIauthors{}
\def\@AIauthorsNames{}
\def\@AIauthorsNamesCitation{}

%some temporary variables for internal use
\let\authorsep\@empty
\let\sep\@empty
\let\@cormark\@empty
\def\CorrespondingAuthor{}
\def\@CorrespondingAuthorName{}

\def\@author@name{}
\def\@author@surname{}
\def\@authorshortname{}

\def\@tmpauthname{}

\define@key{authornames}{name}{%
    \def\@author@name{#1}}
\define@key{authornames}{surname}{%
\def\@author@surname{#1}}
\define@key{authornames}{abbreviation}{%
\def\@authorshortname{#1}}

\newif\ifnotyetprocessed

\def\@@author[#1]#2{%
    \setkeys{authornames}{#2}%
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    \begingroup
    % In LaTeX 2e \toks@ is a token register.
    \toks@{\endgroup\processauthor{#1}}%
    \protected@edef\tmpauthname{%
        \the\toks@{\@author@name\space\@author@surname}{\@authorshortname}%
    }%
    \tmpauthname
}%

\newcounter{auth}
\long\def\processauthor#1#2#3{%
    \g@addto@macro\@AIauthors{%
        \def\baselinestretch{1}%
        \refstepcounter{auth}% increment by 1
        \notyetprocessedtrue
        \authorsep#2\unskip
        \textsuperscript{%
            \@for\@@affmark:=#1\do{%
                \edef\affnum{%
                    \@ifundefined{X@\@@affmark}{affn?}{\AIrefMark{\@@affmark}}%
                }%
                \unskip\sep\affnum%
                \edef\cormark{%
                    \@ifundefined{X@cor-\theauth}{}{{}}%
                }%
                \unskip\cormark
                \let\sep=,%
                \ifx\cormark\@empty
                \else
                    \ifnotyetprocessed
                        \g@addto@macro\@CorrespondingAuthorName{#2}
                        \notyetprocessedfalse 
                    \fi
                \fi
            }%
        }%
        \gdef\authorsep{\unskip,\space}%
        \global\let\sep\@empty
    }%%
    \ifx\@AIauthorsNames\@empty
        \g@addto@macro\@AIauthorsNames{#2}%
    \else
        \g@addto@macro\@AIauthorsNames{;\space#2}%
    \fi
    \ifx\@AIauthorsNamesCitation\@empty
        \g@addto@macro\@AIauthorsNamesCitation{#3}%
    \else
        \g@addto@macro\@AIauthorsNamesCitation{;\space#3}%
    \fi
}%

\def\affiliation{\@@affiliation}
\newcounter{affn}
\renewcommand\theaffn{\arabic{affn}}
\long\def\@@affiliation[#1]#2{%
    \g@addto@macro\@AIaffiliations{%
        \def\baselinestretch{1}%
        \refstepcounter{affn}%
        \AIaffLabel{#1}
        {\upshape\textsuperscript{\theaffn}}\space#2\par\vspace{1pt}}%
}

\def\AIaffLabel#1{\@bsphack\protected@write\@auxout{}%
    {\string\NewAIaffLabel{#1}{\@currentlabel}}\@esphack}
\def\NewAIaffLabel#1#2{\expandafter\xdef\csname X@#1\endcsname{#2}}

\def\AIrefMark#1{\@ifundefined{X@#1}{0}{\csname X@#1\endcsname}%
}

\def\CorrespondingAuthorNumber#1{\NewAIaffLabel{cor-#1}{$\ast$}}

\newcounter{cnote}
\def\CorrespondingAuthorEmail#1{\refstepcounter{cnote}%
    \AIaffLabel{#1}%
    \g@addto@macro\CorrespondingAuthor{%
        \@CorrespondingAuthorName, e-mail: #1}}

\def\@Citation{\@AIauthorsNamesCitation\space-\space\@title}

\renewcommand{\@maketitle}{%
        \begingroup
        \huge{\bfseries\@title}\par%
        \vskip15pt%
        \large{\bfseries\@AIauthors}\par%
        \vskip14pt%
        \itshape\small\@AIaffiliations\par%
        {\bfseries Corresponding Author:}\space\CorrespondingAuthor\par
        \vskip6pt%pt%
        {\bfseries Citation:}\space\@Citation\par
        \endgroup%%
    }
    
\makeatother

\title{Test}
\author[a1,a2]{name=Tom,surname=Smith,abbreviation=T. Smith}%
\author[a2,a3]{name=Jane,surname=Doe,abbreviation=J. Doe}%
\author[a3,a4]{name=Alfred,surname=Einstein,abbreviation=A. Einstein}%

\affiliation[a1]{First Affiliation}
\affiliation[a2]{Second Affiliation}
\affiliation[a3]{Another Affiliation}
\affiliation[a4]{Just another Affiliation}
\CorrespondingAuthorNumber{3}
\CorrespondingAuthorEmail{[email protected]}

\begin{document}
    \maketitle
\end{document}

相关内容