ループを使った2次マクロの一般化

ループを使った2次マクロの一般化

コマンドを定義するために使用できるマクロを作成したいと思います。これらのコマンドは、変数または変数の構造体のように動作し、複数の値を含めることができます。「メンバー」はオプションの引数を介して渡されます。これを使用して、テンプレート環境を定義し、ドキュメントに同時に存在する必要があるさまざまな言語に異なる文字列を設定します。

これが私が持っていて、機能するものなのです。

\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}}%
}

まずデフォルト値が設定されます。次に、オプションの引数に従って値を設定するコマンドが作成されます。何もない場合は、すべてのロケールの値を設定します。

% 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}

任意のロケールを受け入れるようにコマンドを変更しようとしましたが、うまくいきません。

\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}}%
}

使用される値を設定すれば、すべてうまくいきます。値が設定されていない場合、カスタム環境が壊れ、表示されるデフォルト メッセージは だけになりUse \Cmd[] to...、ロケール名は表示されません。

何が起こっているのか分かりますか?

答え1

コメントで述べたように、主な問題は、\nの置換テキストの が\@namedefその値に展開されなかったことです。Faculty:fiたとえば、の置換テキストは文字通りのままでした。

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

を使用します\n。マクロが呼び出されるほとんどのコンテキストでは、\nは未定義になり、エラーが発生します。マクロの置換テキストは、 が\nその展開、つまりfiまたはになるように構築する必要がありますen。これを適切に行う最も簡単な方法は、おそらくヘルパー マクロです。この回答では、2 つのアプローチを示します。1 つは とそのリスト マクロを使用する方法、もう 1 つはとヘルパー マクロをetoolbox使用するコードを使用する方法です。 のループは、ループ変数の値をヘルパー マクロへの引数として直接渡すことによって機能します。\expandafteretoolbox

の最初の引数\@namedef(つまり#2:\n) では、 は\n自動的に展開され、実際にfiは になります。


ここでは、 とそのリスト マクロを使用したソリューションを示します。一部の説明はインラインで示されています。このアプローチの利点は、ループがマクロとして直接実装されるため (通常はヘルパー マクロが必要)、ループ変数 ( )etoolboxを展開する必要がないことです。\n

\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}

この学部は関係ありません//(このテキストを置き換えるには、\Gaculty[en] を使用してください。) (このテキストを置き換えるには、\Gaculty[fi] を使用してください。) (このテキストを置き換えるには、\Gaculty[de] を使用してください。)//Foo Foo Foo//Foo Föö Foo


\foreachおよびを使用したバージョンに固執したい場合は\ifthenelse、ヘルパー関数を使用できます。

問題は、\n実際の値を取得するために を展開する必要があることです。 これは で可能ですが\expandafter、多くのトークンを飛び越えるために外科手術のような精度で を使用する必要がないようにするには、ヘルパー関数が役立ちます。 マクロの最初の引数を\expandafters で展開する方が、後の引数で展開するよりも少し簡単なので、ヘルパー関数では、少し予想外の引数順序 を使用します{<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}

ところで、 2文字のフォントコマンドはLaTeX2eでは非推奨となっているため、\ttfamilyの代わりにを使用しました(\tthttps://texfaq.org/FAQ-2letterfontcmd2 文字のフォント スタイル コマンド (\bf、\it、…) は LaTeX で復活するでしょうか?)。

関連情報