
Я пытаюсь создать много разных команд на основе одной команды (по сути, пользовательского заголовка) предсказуемым образом. Есть ли способ сделать это в TeX/LaTeX?
Мой конкретный пример:
Я пишу спецификацию модуля, где каждый из подзаголовков обозначается как \modulespec{description}{the description}
или \modulespec{learning outcomes}{the learning outcomes}
и т.д. Я надеюсь, что TeX сможет генерировать команды типа \mdescription{the description}
и\mlearningoutcomes{the learning outcomes}
Я знаю, что в bash-подобном расширении в сочетании с TeX я бы написал
for i in 'description' 'learning outcomes'
do
\def\csname m\getRidOfSpaces{$i} \endcsname{\modulespec{$i}{#1}}
done
(если вы извините за сочетание языков) где \getRidOfSpaces
изменения learning outcomes
в learningoutcomes
. Возможно ли это с TeX/LaTeX?
М(не)МЫ:
\documentclass{article}
\usepackage{blindtext}
% In the real thing, this definition is much more complicated
\newcommand{\modulespec}[2]{\section{#1} #2}
% The code I want in pure TeX/LaTeX
for i in 'description' 'learning outcomes'
do
\def\csname m\getRidOfSpaces{$i} \endcsname{\modulespec{$i}{#1}}
done
% end of non TeX code
\begin{document}
% Both work
\modulespec{description}{\blindtext}
\modulespec{learning outcomes}{\blindtext}
% These two commands should produce the same output as the previous two
\mdescription{\blindtext}
\mlearningoutcomes{\blindtext}
\end{document}
решение1
Вот реализация LaTeX3.
\documentclass{article}
\usepackage[margin=1cm]{geometry} % to fit in one page
\usepackage{blindtext}
\usepackage{xparse}
\newcommand{\modulespec}[2]{\section{#1} #2}
\ExplSyntaxOn
\NewDocumentCommand{\definemodules}{m}
{
\farley_definemodules:n { #1 }
}
\tl_new:N \l_farley_temp_tl
\cs_new_protected:Npn \farley_definemodules:n #1
{
\clist_map_inline:nn { #1 }
{
\tl_set:Nn \l_farley_temp_tl { ##1 }
\tl_replace_all:Nnn \l_farley_temp_tl { ~ } { }
\cs_new_protected:cpn { m \l_farley_temp_tl } { \modulespec { ##1 } }
}
}
\ExplSyntaxOff
\definemodules{
description,
learning outcomes
}
\begin{document}
\modulespec{description}{\blindtext}
\modulespec{learning outcomes}{\blindtext}
% These two commands should produce the same output as the previous two
\mdescription{\blindtext}
\mlearningoutcomes{\blindtext}
\end{document}
Некоторые комментарии по коду.
Мы определяем макрос уровня пользователя для определения всех модулей за один раз; он \definemodules
просто передает управление функции уровня программиста \farley_definemodules:n
(это лучшая практика согласно рекомендациям по программированию LaTeX3).
Теперь функция \farley_definemodules:n
разделяет свой аргумент по запятым и выполняет цикл по элементам. Текущий элемент доступен как #1
, но нам нужно, ##1
потому что мы внутри определения.
Каждый цикл сохраняет элемент в переменной списка скретч-токенов, \l_farley_temp_tl
чтобы мы могли применить \tl_replace_all:Nnn
его. Замена — «любой пробел заменяется ничем»; в среде программирования пробелы игнорируются, поэтому «реальный пробел» обозначается как ~
.
Теперь мы используем \cs_new_protected:cpn
функцию, которая является аналогом старого стиля \@namedef
: первый аргумент преобразуется в имя управляющей последовательности с помощью \csname...\endcsname
(список токенов здесь расширяется по правилу TeX).
решение2
Похоже, вы хотите сделать цикл 'for each'. В ядре LaTeX есть макрос \@for
, который делает это для каждого элемента списка, разделенного запятыми. Вам также нужно \zap@space
или похожее, чтобы удалить пробелы. Например:
\newcommand{\modulespec}[2]{\section{#1} #2}
\makeatletter
\@for\@tempa:=description,learning outcomes\do{%
\edef\@tempa
{\expandafter\expandafter\expandafter\zap@space
\expandafter\@tempa\space\@empty}%
\expandafter\edef\csname m\@tempa\endcsname
{\noexpand\modulespec{\@tempa}}%
}
\makeatother
определит \mdescription
и \mlearningoutcomes
по мере необходимости. Я использовал \edef
для принудительного расширения переменной в правильных местах. (Применять \protected@edef
здесь бесполезно, так как «текст» должен быть «безопасным» внутри \csname
.)
решение3
Использование \xintFor
и утилит ядра LaTeX2e (с символом @
в названии):
\documentclass{article}
\usepackage[english]{babel}
\usepackage{blindtext}
\usepackage{xinttools}
% In the real thing, this definition is much more complicated
\newcommand{\modulespec}[2]{\section{#1} #2}
% % The code I want in pure TeX/LaTeX
% for i in 'description' 'learning outcomes'
% do
% \def\csname m\getRidOfSpaces{$i} \endcsname{\modulespec{$i}{#1}}
% done
% % end of non TeX code
% Nota bene: in the above you probably meant \long\def, not \def
\makeatletter
\xintFor #1 in {description, learning outcomes}\do
{\long\@namedef{m\zap@space #1 \@empty}##1{\modulespec{#1}{##1}}}
\makeatother
\begin{document}
% Both work
\modulespec{description}{\blindtext}
\modulespec{learning outcomes}{\blindtext}
% These two commands should produce the same output as the previous two
\mdescription{\blindtext}
\mlearningoutcomes{\blindtext}
\end{document}