Generalizando uma macro de segunda ordem com loops

Generalizando uma macro de segunda ordem com loops

Gostaria de construir uma macro que possa ser usada para definir comandos. Esses comandos se comportam como variáveis ​​ou estruturas de variáveis, de modo que podem conter vários valores. O "membro" é passado através de um argumento opcional. Eu o utilizo para definir ambientes de templates e para ter strings diferentes para linguagens diferentes, que precisam estar presentes no documento simultaneamente.

Aqui está o que eu tinha e funciona.

\newcommand\MakeLocaleVar[1]{%
    \global\@namedef{#1:en}{{\scriptsize (Use {\tt\textbackslash #1[en]} to replace this text.)}}%
    \global\@namedef{#1:fi}{{\scriptsize (Use {\tt\textbackslash #1[fi]} to replace this text.)}}%
    \expandafter\newcommand\csname #1\endcsname[2][]{%
        \ifthenelse{\equal{##1}{}}{%
            \global\@namedef{#1:en}{##2}%
            \global\@namedef{#1:fi}{##2}%
        }{%
            \global\@namedef{#1:##1}{##2}%
        }%
    }%
    \expandafter\newcommand\csname Emit#1\endcsname[1][en]{\@nameuse{#1:##1}}%
}

Primeiro, os valores padrão são definidos. Em seguida, é criado um comando que define os valores de acordo com o argumento opcional. Se nenhum, defina o valor para todas as localidades.

% In cls: define command
\MakeLocaleVar{Faculty}

% In main tex: set values
\Faculty{This faculty} % for all values
\Faculty[fi]{Tämä tiedekunta} % for a specific one

% In cls environments: use values
\EmitFaculty[en]
\EmitFaculty[fi]

% Now in addition I'd like to be able to:
\MakeLocaleVar[en,fi,de]{Faculty}

Tentei modificar o comando para aceitar localidades arbitrárias, mas algo não está funcionando.

\newcommand\MakeLocaleVar[2][en,fi]{%
    \foreach \n in {#1}{%
        \global\@namedef{#2:\n}{%
            {\scriptsize (Use {\tt\textbackslash #2[\n]} to replace this text.)}%
        }%
    }%
    \expandafter\newcommand\csname #2\endcsname[2][]{%
        \ifthenelse{\equal{##1}{}}{%
            \foreach \n in {#1}{%
                \global\@namedef{#2:\n}{##2}%
            }%
        }{%
            \global\@namedef{#2:##1}{##2}%
        }%
    }%
    \expandafter\newcommand\csname Emit#2\endcsname[1][en]{\@nameuse{#2:##1}}%
}

Se eu definir valores que serão usados, tudo ficará bem e elegante. É quando os valores não são definidos que meus ambientes personalizados quebram e a mensagem padrão mostrada é apenas Use \Cmd[] to..., portanto, sem os nomes de localidade.

Alguma ideia do que está acontecendo?

Responder1

Conforme mencionado no comentário, seu principal problema foi que o \nno texto de substituição do \@namedefnão foi ampliado para seu valor. O texto de substituição, Faculty:fipor exemplo, permaneceu assim literalmente

{\scriptsize (Use {\tt\textbackslash #2[\n]} to replace this text.)}%

com \n. Na maioria dos contextos onde a macro seria chamada \nseria indefinida e você receberia um erro. Você deseja que o texto de substituição da macro seja construído de forma que \nse torne sua expansão, ou seja, fiou en. A maneira mais fácil de fazer isso corretamente é provavelmente uma macro auxiliar. Esta resposta mostrará duas abordagens: uma com etoolboxsuas macros de lista e outra com seu código que usa \expandafteruma macro auxiliar. etoolboxOs loops de funcionam passando o valor da variável do loop diretamente como um argumento para uma macro auxiliar.

No primeiro argumento de \@namedef(ou seja, o #2:\n), o \nseria expandido automaticamente para que realmente se tornasse filá.


Aqui está uma solução usando etoolboxe suas macros de lista. Algumas explicações estão embutidas. A vantagem desta abordagem é que não há necessidade de expansão da variável de loop ( \n), pois o loop é implementado diretamente como uma macro (geralmente exigindo uma macro auxiliar).

\documentclass[english,finnish]{article}
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage{babel}

\usepackage{etoolbox}

\makeatletter
% {<name>}{<lang>}
\newcommand*{\mlv@defaultvalue}[2]{%
  \csdef{#1@#2}{%
    {\scriptsize (Use {\ttfamily\textbackslash #1[#2]} to replace this text.)}}}
% {<repl. text>}{<name>}{<lang>}
% the unusual order instead of the more natural {<name>}{<lang>}{<repl. text>}
% makes for a more elegant call to \forcsvlist
\newcommand*{\mlv@realvalue}[3]{%
  \csdef{#2@#3}{#1}}

% [<langs>]{<name>}
% \forcsvlist{<macro>}{<item_1, item_2, ..., item_n>}
% calls <macro> once with each item_i as last argument
% <macro>{<item_1>}, <macro>{<item_2>}
% <macro> can already bring a few arguments of its own
% it could be <marco>{<fixed_1>}...{<fixed_n>} and then the invocations
% would become <marco>{<fixed_1>}...{<fixed_n>}{<item_1>} etc.
% Since the items are the <lang> argument it must be the last
% argument to \mlv@defaultvalue and \mlv@realvalue
\newcommand\MakeLocaleVar[2][en,fi]{%
  \forcsvlist{\mlv@defaultvalue{#2}}{#1}%
  \expandafter\newcommand\csname #2\endcsname[2][]{%
    \ifblank{##1}
      {\forcsvlist{\mlv@realvalue{##2}{#2}}{#1}}%
      {\mlv@realvalue{##2}{#2}{##1}}}%
  \expandafter\newcommand\csname Emit#2\endcsname[1][en]{\csuse{#2@##1}}%
}
\makeatother

\begin{document}

\MakeLocaleVar{Faculty}

% In main tex: set values
\Faculty{This faculty} % for all values
\Faculty[fi]{Tämä tiedekunta} % for a specific one

% In cls environments: use values
\EmitFaculty[en]
\EmitFaculty[fi]

% Now in addition I'd like to be able to:
\MakeLocaleVar[en,fi,de]{Gaculty}

\EmitGaculty[en]
\EmitGaculty[fi]
\EmitGaculty[de]

\Gaculty{Foo}
\EmitGaculty[en]
\EmitGaculty[fi]
\EmitGaculty[de]

\Gaculty[fi]{Föö}
\EmitGaculty[en]
\EmitGaculty[fi]
\EmitGaculty[de]
\end{document}

Esta faculdade Tämä tiedekunta//(Use \Gaculty[en] para substituir este texto.) (Use \Gaculty[fi] para substituir este texto.) (Use \Gaculty[de] para substituir este texto.)//Foo Foo Foo //Foo Foo Foo


Se quiser manter sua versão com \foreache \ifthenelse, você pode usar uma função auxiliar.

O truque é que precisamos expandir \npara obtermos seu valor real. Isso é possível com \expandafter, mas para evitar ter que usá-lo com precisão cirúrgica para pular muitos tokens, uma função auxiliar é útil. Como é um pouco mais fácil expandir o primeiro argumento de uma macro com \expandafters em vez de um argumento posterior, a função auxiliar usa o argumento ligeiramente inesperado order {<lang>}{<name>}.

\documentclass[english,finnish]{article}
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage{babel}

\usepackage{ifthen}
\usepackage{tikz}

\makeatletter
% helper for easier control of expansion
% {<lang>}{<name>}
\newcommand*{\mlv@helper}[2]{%
  \global\@namedef{#2:#1}{%
    {\scriptsize (Use {\ttfamily\textbackslash #2[#1]} to replace this text.)}%
  }%
}

% \n must be expanded to be useful.
% The first argument of \@namedef automatically expands it,
% but the second does not.
% Here we use a helper function to expand \n
% before it is processed.
\newcommand\MakeLocaleVar[2][en,fi]{%
    \foreach \n in {#1}{%
        \expandafter\mlv@helper\expandafter{\n}{#2}%
    }%
    \expandafter\newcommand\csname #2\endcsname[2][]{%
        \ifthenelse{\equal{##1}{}}{%
            \foreach \n in {#1}{%
                \global\@namedef{#2:\n}{##2}%
            }%
        }{%
            \global\@namedef{#2:##1}{##2}%
        }%
    }%
    \expandafter\newcommand\csname Emit#2\endcsname[1][en]{\@nameuse{#2:##1}}%
}
\makeatother

\begin{document}

\MakeLocaleVar{Faculty}

% In main tex: set values
\Faculty{This faculty} % for all values
\Faculty[fi]{Tämä tiedekunta} % for a specific one

% In cls environments: use values
\EmitFaculty[en]
\EmitFaculty[fi]

% Now in addition I'd like to be able to:
\MakeLocaleVar[en,fi,de]{Gaculty}

\EmitGaculty[en]
\EmitGaculty[fi]
\EmitGaculty[de]

\Gaculty{Foo}
\EmitGaculty[en]
\EmitGaculty[fi]
\EmitGaculty[de]

\Gaculty[fi]{Föö}
\EmitGaculty[en]
\EmitGaculty[fi]
\EmitGaculty[de]
\end{document}

BTW: usei \ttfamilyem vez de \tt, porque os comandos de fonte de duas letras estão obsoletos no LaTeX2e (https://texfaq.org/FAQ-2letterfontcmd,Os comandos de estilo de fonte de duas letras (\bf , \it ,…) serão ressuscitados no LaTeX?).

informação relacionada