xparse 和键值参数

xparse 和键值参数

我正在尝试学习如何做宏。

我想使用它,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)可将更改局部化到键。但是,您可以使内容全局可用。这取决于您的应用程序。此外,可以调整键值以实际创建可以重复使用的键宏。如果您的文档中有一个区域执行一堆声明,并且希望稍后重新使用它们,这可能会有所帮助。

上面的例子也同样适用于文档环境。

更多扩展由xkeyvalltxkeys甚至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 提供数据。;-)

在此处输入图片描述

相关内容