推廣帶有循環的二階宏

推廣帶有循環的二階宏

我想建構一個可用於定義指令的巨集。這些命令的行為類似於變數或變數結構,因此它們可以包含多個值。 “成員”透過可選參數傳遞。我用它來定義模板環境,並為不同的語言提供不同的字串,這些字串需要同時出現在文件中。

這就是我所擁有的和所做的工作。

\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成為其擴展,即fien。正確完成此操作的最簡單方法可能是輔助巨集。這個答案將展示兩種方法:一種使用etoolbox其清單宏,另一種使用使用\expandafter輔助宏的程式碼。etoolbox的循環透過將循環變數的值直接作為參數傳遞給輔助宏來運作。

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

此教師 Tämä titekunta //(使用 \Gaculty[en] 替換此文本。)(使用 \Gaculty[fi] 替換此文本。)(使用 \Gaculty[de] 替換此文本。)//Foo Foo Foo //Foo Foo Foo //Foo Föö Foo


如果您想堅持使用 和 的版本\foreach\ifthenelse您可以使用輔助函數。

訣竅在於我們需要擴展\n才能獲得其實際價值。這對於 是可能的\expandafter,但為了避免必須以外科手術般的精度使用它來跳過許多標記,輔助函數很有用。由於使用 s 而不是後面的參數來擴展巨集的第一個參數稍微容易一些\expandafter,因此輔助函數使用了稍微出乎意料的參數順序{<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}

順便一提:我使用\ttfamily而不是\tt,因為在 LaTeX2e 中不建議使用兩個字母的字體命令(https://texfaq.org/FAQ-2letterfontcmd,兩個字母的字體樣式指令(\bf、\it、...)會在 LaTeX 中復活嗎?)。

相關內容