Расширение макроса в аргументе окружения

Расширение макроса в аргументе окружения

Я хотел бы создать среду, которая немного меняется в зависимости от аргумента, указанного для нее. Причина в наличии разных локалей в документе. Я определил команду, EmitLanguageStrкоторая определяет аргумент в otherlanguageсоответствии с переданным аргументом. Например, \EmitLanguageStr[en] -> english. См. эти вопросы для дальнейшего контекста: [1,2] Вот окружающая среда в сокращенном виде.

\renewenvironment{abstract}[1][en]{%
    \section*{\EmitAbstractStr[#1]}
    \begin{otherlanguage}{\EmitLanguageStr[#1]}
        \@author\par
        \textbf{\EmitTitle[#1]}\par
}{%
    \end{otherlanguage}
}

Оказывается, у других людей была такая же проблема (В), но решение не совсем сработало для меня. Ответ предполагает, что обертывание окружения одним из двух способов позволит расширить аргумент:

% Create a macro
\newcommand\testlang{english}

% Expand the argument once:
\newenvironment{Otherlanguage}[1]{%
    \expandafter\otherlanguage\expandafter{#1}%
}{\endotherlanguage}

% Alternative: argument is fully expanded
\newenvironment{Otherlanguage}[1]{%
    \begingroup
    \edef\temp{\endgroup\noexpand\otherlanguage{#1}}%
    \temp
}{\endotherlanguage}

% usage    
\begin{Otherlanguage}{\testlang}
    ...
\end{Otherlangauge}

Во-первых, без этих оберток babel выдает сообщение об ошибке, что язык EmitLanguageStr[en]не определен. Это было рассмотрено в связанном вопросе. Однако представленные решения приводят к другому виду ошибок: Use of \EmitLanguageStr doesn't match its definition.

Как можно правильно расширить команду?


Вот полный пример ситуации. Это работает, но при замене аргумента на Otherlanguageтот, что в комментарии, возникает описанная ошибка.

\documentclass[a4paper,12pt]{article}

\usepackage{ifthen}
\usepackage{pgffor}

\makeatletter

\newcommand\SetDefaultValue[2]{%
    \global\@namedef{#2:#1}{%
        {\scriptsize (Use {\tt\textbackslash #2[#1]} to replace this text.)}%
    }%
}

\newcommand\MakeLocaleVar[2][en,fi]{%
    \foreach \n in {#1}{%
        \expandafter\SetDefaultValue\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}}%
}

% Variables set in document
\MakeLocaleVar{Title}
\MakeLocaleVar{Year}

%\newenvironment{Otherlanguage}[1]{%
%   \expandafter\otherlanguage\expandafter{#1}%
%}{\endotherlanguage}

\newenvironment{Otherlanguage}[1]{%
    \begingroup
    \edef\temp{\endgroup\noexpand\otherlanguage{#1}}%
    \temp
}{\endotherlanguage}

\MakeLocaleVar{AbstractStr}
\AbstractStr[en]{ABSTRACT}
\AbstractStr[fi]{TIIVISTELMÄ}

\MakeLocaleVar{LanguageStr}
\LanguageStr[en]{english}
\LanguageStr[fi]{finnish}

\renewenvironment{abstract}[1][en]{%
    \section*{\EmitAbstractStr[#1]}
    \begin{Otherlanguage}{english}%\EmitLanguageStr[#1]
        \@author\par
        \textbf{\EmitTitle[#1]}\par
        \EmitYear\par
        \par
    }{%
    \end{Otherlanguage}
}

\makeatother

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

\author{Handsome Devil}
\Year{2019}

\Title[en]{Title in English}
\Title[fi]{Otsikko suomeksi}

\begin{document}
\selectlanguage{english}

\begin{abstract}
    Abstract environment in English.
\end{abstract}

\begin{abstract}[fi]
    Abstract environment in Finnish.
\end{abstract}
\end{document}

решение1

\MakeLocaleVar{LanguageStr}причины, которые \EmitLanguageStrследует определить как:

\newcommand\EmitLanguageStr[1][en]{\@nameuse{LanguageStr:#1}}%

\EmitLanguageStrобрабатывает необязательный аргумент.

Если вы посмотрите \EmitLanguageStrvia \show\EmitLanguageStr, вы найдете:

> \EmitLanguageStr=macro:
->\@protected@testopt \EmitLanguageStr \\EmitLanguageStr {en}.

Рассматриваем \@protected@testoptурожайность:

> \@protected@testopt=macro:
#1->\ifx \protect \@typeset@protect \expandafter \@testopt \else \@x@protect #1
\fi .

Рассматриваем \@testoptурожайность:

> \@testopt=\long macro:
#1#2->\kernel@ifnextchar [{#1}{#1[{#2}]}.

Рассматриваем \kernel@ifnextcharурожайность:

> \kernel@ifnextchar=\long macro:
#1#2#3->\let \reserved@d =#1\def \reserved@a {#2}\def \reserved@b {#3}\futurele
t \@let@token \@ifnch .

Рассматриваем \@ifnchурожайность:

> \@ifnch=macro:
->\ifx \@let@token \@sptoken \let \reserved@c \@xifnch \else \ifx \@let@token \
reserved@d \let \reserved@c \reserved@a \else \let \reserved@c \reserved@b \fi 
\fi \reserved@c .

Рассматриваем \@xifnchурожайность:

> \@xifnch=macro:
 ->\futurelet \@let@token \@ifnch .

Как вы можете видеть , при проверке наличия необязательного аргумента через . задействовано много манипуляций с нерасширяемыми \let- и -присваиваниями.\futurelet\kernel@ifnextchar [

Поэтому \EmitLanguageStrне только доставляет те токены символов, которые формируют имя языка. Он также выдает множество нерасширяемых токенов присваивания, таких как \letи \futureletдля проверки наличия необязательного аргумента.
Эти токены не исчезнут в чистом контексте расширения, в то время как получение имени языка для передачи его в качестве аргумента в среду было бы чистым контекстом расширения. После расширения эти нерасширяемые токены присваивания остаются в потоке токенов, потому что в то время как расширение происходит в «пищеводе» (La)TeX, выполнение присваиваний происходит в «желудке» (La)TeX.

Итак, вкратце:

Пока \EmitLanguageStrобрабатывается необязательный аргумент, а методы обнаружения наличия необязательного аргумента сами по себе включают нерасширяемые токены присваивания, которые не исчезают во время расширения, вы не можете использовать его \EmitLanguageStrв чистом контексте расширения для получения только тех токенов, которые образуют имя языка.

Но вы можете, например, создать вариант \EmitLanguageStrс дополнительным аргументом, содержащим токены, которым будет передана выдаваемая строка в качестве аргумента. Я называю этот макрос \PassLanguageStrTo:

\documentclass[a4paper,12pt]{article}

\usepackage{ifthen}
\usepackage{pgffor}

\makeatletter

\newcommand\SetDefaultValue[2]{%
    \global\@namedef{#2:#1}{%
        {\scriptsize (Use {\tt\textbackslash #2[#1]} to replace this text.)}%
    }%
}

\newcommand\PassFirstToSecond[2]{#2{#1}}%

\newcommand\MakeLocaleVar[2][en,fi]{%
    \foreach \n in {#1}{%
        \expandafter\SetDefaultValue\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}}%
    \expandafter\newcommand\csname Pass#2To\endcsname[2][en]{%
      \expandafter\expandafter\expandafter\PassFirstToSecond
      \expandafter\expandafter\expandafter{\csname#2:##1\endcsname}{##2}%
    }%
}

% Variables set in document
\MakeLocaleVar{Title}
\MakeLocaleVar{Year}


\MakeLocaleVar{AbstractStr}
\AbstractStr[en]{ABSTRACT}
\AbstractStr[fi]{TIIVISTELMÄ}

\MakeLocaleVar{LanguageStr}
\LanguageStr[en]{english}
\LanguageStr[fi]{finnish}

\renewenvironment{abstract}[1][en]{%
    \section*{\EmitAbstractStr[{#1}]}%%%%
    \PassLanguageStrTo[{#1}]{\begin{otherlanguage}}%
        \@author\par
        \textbf{\EmitTitle[{#1}]}\par
        \EmitYear\par
        \par
    }{%
    \end{otherlanguage}%%%%
}

\makeatother

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

\author{Handsome Devil}
\Year{2019}

\Title[en]{Title in English}
\Title[fi]{Otsikko suomeksi}

\begin{document}
\selectlanguage{english}

\begin{abstract}
    Abstract environment in English.
\end{abstract}

\begin{abstract}[fi]
    Abstract environment in Finnish.
\end{abstract}
\end{document}

Связанный контент