
Estoy intentando crear muchos comandos diferentes basados en un solo comando (esencialmente un encabezado personalizado) de una manera predecible. ¿Hay alguna manera de hacer esto en TeX/LaTeX?
Mi ejemplo específico:
Estoy escribiendo una especificación de módulo, donde cada uno de los subtítulos se indica con \modulespec{description}{the description}
o \modulespec{learning outcomes}{the learning outcomes}
, etc. Espero que TeX pueda generar comandos como \mdescription{the description}
y\mlearningoutcomes{the learning outcomes}
Sé que en una expansión tipo bash combinada con TeX escribiría
for i in 'description' 'learning outcomes'
do
\def\csname m\getRidOfSpaces{$i} \endcsname{\modulespec{$i}{#1}}
done
(si disculpa la combinación de idiomas) donde \getRidOfSpaces
cambia learning outcomes
a learningoutcomes
. ¿Es esto posible/factible con TeX/LaTeX?
M(no)NOSOTROS:
\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}
Respuesta1
Aquí hay una implementación de 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}
Algunos comentarios sobre el código.
Definimos una macro a nivel de usuario para definir todos los módulos de una sola vez; esto es \definemodules
simplemente entregar el control a la función de nivel del programador \farley_definemodules:n
(esta es la mejor práctica según las pautas de programación LaTeX3).
Ahora la función \farley_definemodules:n
divide su argumento entre comas y realiza un bucle sobre los elementos. El elemento actual está disponible como #1
, pero lo necesitamos ##1
porque estamos dentro de una definición.
Cada ciclo almacena el elemento en la variable de lista de tokens reutilizables \l_farley_temp_tl
para que podamos aplicarlo \tl_replace_all:Nnn
. El reemplazo es “cualquier espacio es reemplazado por nada”; en el entorno de programación los espacios se ignoran, por lo que un "espacio real" se indica con ~
.
Ahora usamos la \cs_new_protected:cpn
función que es análoga al estilo antiguo \@namedef
: el primer argumento se convierte en un nombre de secuencia de control con \csname...\endcsname
(la lista de tokens se expande aquí mediante la regla TeX).
Respuesta2
Parece que quieres hacer un bucle "para cada". El kernel de LaTeX tiene una macro \@for
que hace esto para cada elemento de una lista separada por comas. También necesitas \zap@space
o similar para eliminar espacios. Por ejemplo:
\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
definirá \mdescription
y \mlearningoutcomes
según sea necesario. He usado un \edef
para forzar la expansión de la variable en los lugares correctos. (No sirve de nada aplicar \protected@edef
aquí ya que el 'texto' tiene que ser 'seguro' en el interior \csname
).
Respuesta3
Uso \xintFor
de las utilidades del kernel LaTeX2e (con un @
en sus nombres):
\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}