12

I'm trying to learn how to do macros.

I want to use xparse as it seems the most powerful tool. I also want named arguments, as numbered arguments make me, as a programmer, uncomfortable.

I had some ideas like:

\DeclareDocumentCommand{\myMacro} {o o}
{
   \DeclareDocumentCommand{\foo} {} {#1}
   \DeclareDocumentCommand{\bar} {} {#2}
   The foo argument was \foo and the bar argument was \bar

}

But this wouldn't work, because of nesting and wouldn't extend to DocumentEnviroments, I guess since different scoping for beginning and end sections.

My goal is to have some sections of a document that are highly structured. For example I might have a list of movies, which have parameters like title, date, director etc. and I might manipulate them in complex ways (perhaps building them into a paragraph), and I don't want to have to remember which order the parameters were in when defining this.

Perhaps I am thinking wrong for a LaTeX user, I guess in many programming languages you need to know the order when invoking a function (though in this case, I would like to use the name of arguments when invoking - for clarity)

18

Here is a very elementary view on LaTeX's key-value system using keyval, similar to what was presented in How to create a command with key values?:

enter image description here

\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}

It should be evident that using a key-value approach removes the requirement to remember the order of the keys. That is, unless the keys have some interaction with one another.

Grouping (via \begingroup...\endgroup) localizes the changes to the keys. However, you can make things available globally. It just depends on your application. Moreover, it would be possible to adapt the key-values to actually create key-macros that you can re-use. This may be helpful if you have a area in your document where you perform a bunch of declarations, and want to re-use them later.

The above example translates just as well to document environments.

More extensions are provided by xkeyval, ltxkeys and even l3keys from expl3 (see Programming key–value in expl3).


Here's a practical example of using key-values to store data regarding movies through a movie declaration (\newMovie{<tag>}{<key-values>}) and printing it (\showMovie{<tag>}):

enter image description here

\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}

There are other (perhaps better) ways of dealing with this, but again, this is just to showcase what you can do with key-value approaches to macros (and databases).

  • When mymacro defines macros to be used outside the command localization with \begingroup ... \endgroup does not work, as the macros would be localized aswell. – Heinrich Ody Sep 4 '15 at 13:01
  • @HeinrichOdy: You could define them to survive outside the group as well... – Werner Sep 4 '15 at 16:00
  • Hi @Werner, could you please explain how to display only the first genre from the set of genres? I am trying to pass an array to a new command and process it inside it. Thanks! – gaurav gupta Oct 20 '17 at 5:55
  • @gauravgupta: How would you define a "set of genres"? – Werner Oct 20 '17 at 6:16
6

An implementation entirely in expl3, using key-value pairs and property lists.

Each \newMovie command defines a property list and stores the values. Then \showMovie displays them in the preferred order.

\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}

Thanks to Werner for the data. ;-)

enter image description here

  • Hello, this solution does not work with the latest version of xparse (from TeXLive 2018 update)...saludos – Pablo González L May 19 '18 at 23:28
  • @PabloGonzálezL Yes, it used a now deprecated command: things change in five years. Thanks for noting. – egreg May 20 '18 at 9:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.