我正在尝试学习如何做宏。
我想使用它,xparse
因为它似乎是最强大的工具。我还想要命名参数,因为编号参数让我(作为一名程序员)感到不舒服。
我有一些想法,例如:
\DeclareDocumentCommand{\myMacro} {o o}
{
\DeclareDocumentCommand{\foo} {} {#1}
\DeclareDocumentCommand{\bar} {} {#2}
The foo argument was \foo and the bar argument was \bar
}
但这不起作用,因为嵌套并且不会扩展到 DocumentEnviroments,我猜是因为开始和结束部分的范围不同。
我的目标是让文档中的某些部分高度结构化。例如,我可能有一个电影列表,其中包含标题、日期、导演等参数,我可能会以复杂的方式操作它们(可能将它们构建成一个段落),并且我不想在定义它时记住参数的顺序。
对于 LaTeX 用户来说,也许我的想法是错误的,我猜在许多编程语言中,你需要知道调用函数的顺序(尽管在这种情况下,为了清楚起见,我想在调用时使用参数的名称)
答案1
以下是 LaTeX 键值系统的一个非常基础的视图,使用keyval
,类似于如何创建带有键值的命令?:
\documentclass{article}
\usepackage{keyval,xparse}% http://ctan.org/pkg/{keyval,xparse}
\makeatletter
% ========= KEY DEFINITIONS =========
\define@key{mymacro}{first}{\def\mm@first{#1}}
\define@key{mymacro}{second}{\def\mm@second{#1}}
\define@key{mymacro}{third}{\def\mm@third{#1}}
\define@key{mymacro}{last}{\def\mm@last{#1}}
\DeclareDocumentCommand{\myMacro}{m}{%
\begingroup%
% ========= KEY DEFAULTS + new ones =========
\setkeys{mymacro}{first={FIRST arg},second={SECOND arg},third={THIRD arg},last={LAST arg},#1}%
First arg: \mm@first \par
Second arg: \mm@second \par
Third arg: \mm@third \par
Last arg: \mm@last
\endgroup%
}
\makeatother
\begin{document}
\myMacro{last=LaSt,first=FiRsT} \par \hrulefill
\myMacro{} \par \hrulefill
\myMacro{third={$x^2$ \textbf{stuff}},second={}}
\end{document}
显然,使用键值方法无需记住键的顺序。也就是说,除非键之间存在某种相互作用。
分组(通过\begingroup
... \endgroup
)可将更改局部化到键。但是,您可以使内容全局可用。这取决于您的应用程序。此外,可以调整键值以实际创建可以重复使用的键宏。如果您的文档中有一个区域执行一堆声明,并且希望稍后重新使用它们,这可能会有所帮助。
上面的例子也同样适用于文档环境。
更多扩展由xkeyval
,ltxkeys
甚至l3keys
来自expl3
(看编程中的键值expl3
)。
下面是一个实际的例子,通过电影声明( )使用键值来存储有关电影的数据\newMovie{<tag>}{<key-values>}
并打印它(\showMovie{<tag>}
):
\documentclass{article}
\usepackage{keyval,xparse,url}% http://ctan.org/pkg/{keyval,xparse,url}
\makeatletter
% ========= KEY DEFINITIONS =========
\define@key{movie}{title}{\expandafter\def\csname \movietag @title\endcsname{#1}}
\define@key{movie}{releaseyear}{\expandafter\def\csname \movietag @releaseyear\endcsname{#1}}
\define@key{movie}{genre}{\expandafter\def\csname \movietag @genre\endcsname{#1}}
\define@key{movie}{url}{\expandafter\def\csname \movietag @url\endcsname{#1}}
\DeclareDocumentCommand{\newMovie}{m m}{%
% ========= KEY DEFAULTS + new ones =========
\def\movietag{#1}% Store movie tag (used when setting/storing keys)
\setkeys{movie}{title={},releaseyear={},genre={},url={},#2}% Create keys
}
\DeclareDocumentCommand{\showMovie}{m}{%
\textbf{\csname #1@title\endcsname} (\csname #1@releaseyear\endcsname),
\textit{\csname #1@genre\endcsname},
\expandafter\expandafter\expandafter\url\expandafter\expandafter\expandafter{\csname #1@url\endcsname}.
}
\makeatother
\begin{document}
% Movie declarations
\newMovie{anchorman2}{
url={http://www.imdb.com/title/tt1229340/},
releaseyear=2013,
title={Anchorman: The Legend Continues},
genre=Comedy}
\newMovie{lego}{
title={The Lego Movie},
releaseyear=2014,
url={http://www.imdb.com/title/tt1490017/},
genre={Animation, Action, Comedy}}
\newMovie{magic}{
title={Now You See Me},
url={http://www.imdb.com/title/tt1670345/},
releaseyear=2013,
genre={Crime, Thriller}}
% Show movie details
\showMovie{magic}
\showMovie{anchorman2}
\showMovie{lego}
\end{document}
还有其他(也许更好)的方法来解决这个问题,但同样,这只是为了展示你可以用键值方法对宏(和数据库)做什么。
答案2
完全在 中的实现expl3
,使用键值对和属性列表。
每个\newMovie
命令都定义一个属性列表并存储其值。然后\showMovie
按照首选顺序显示它们。
\documentclass{article}
\usepackage{xparse,url}
\ExplSyntaxOn
% keys
\keys_define:nn { oxinabox/movies }
{
title .tl_set:N = \l_oxinabox_title_tl,
releaseyear .tl_set:N = \l_oxinabox_releaseyear_tl,
genre .tl_set:N = \l_oxinabox_genre_tl,
url .tl_set:N = \l_oxinabox_url_tl,
}
% user level commands
\NewDocumentCommand{\newMovie}{m m}
{
\oxinabox_newmovie:nn { #1 } { #2 }
}
\NewDocumentCommand{\showMovie}{m}
{
\oxinabox_showmovie:n { #1 }
}
% internal functions
\cs_new_protected:Npn \oxinabox_newmovie:nn #1 #2
{
\group_begin: % keep the assignment to the keys local
\prop_new:c { g_oxinabox_movie_#1_prop }
\keys_set:nn { oxinabox/movies } { #2 }
\prop_gput:cnV { g_oxinabox_movie_#1_prop } { title } \l_oxinabox_title_tl
\prop_gput:cnV { g_oxinabox_movie_#1_prop } { releaseyear } \l_oxinabox_releaseyear_tl
\prop_gput:cnV { g_oxinabox_movie_#1_prop } { genre } \l_oxinabox_genre_tl
\prop_gput:cnV { g_oxinabox_movie_#1_prop } { url } \l_oxinabox_url_tl
\group_end:
}
\cs_new:Npn \oxinabox_getvalue:nn #1 #2
{
\prop_item:cn { g_oxinabox_movie_#1_prop } { #2 }
}
\cs_new_protected:Npn \oxinabox_showmovie:n #1
{
\par\noindent
\textbf{ \oxinabox_getvalue:nn { #1 } { title } }, ~ %
\textbf{ \oxinabox_getvalue:nn { #1 } { releaseyear } }, ~ %
\textbf{ \oxinabox_getvalue:nn { #1 } { genre } },
\\
\use:x { \exp_not:N \url { \oxinabox_getvalue:nn { #1 } { url } } }
}
\ExplSyntaxOff
\begin{document}
% Movie declarations
\newMovie{anchorman2}{
url={http://www.imdb.com/title/tt1229340/},
releaseyear=2013,
title={Anchorman: The Legend Continues},
genre=Comedy}
\newMovie{lego}{
title={The Lego Movie},
releaseyear=2014,
url={http://www.imdb.com/title/tt1490017/},
genre={Animation, Action, Comedy}}
\newMovie{magic}{
title={Now You See Me},
url={http://www.imdb.com/title/tt1670345/},
releaseyear=2013,
genre={Crime, Thriller}}
% Show movie details
\showMovie{magic}
\showMovie{anchorman2}
\showMovie{lego}
\end{document}
感谢 Werner 提供数据。;-)