11

I'm trying to define a list environment with multiple optional arguments and with a default values for those arguments. For example, creating a new environment with a name newenv, which accepts two arguments, and if they aren't present uses default values:

\documentclass{article}
\usepackage{array}
\usepackage{multicol}
\usepackage{enumerate}

\begin{document}

\newenvironment{newenv}[2][2]
{\begin{multicols}[#1]\begin{enumerate}[#2]}
{\end{enumerate}\end{multicols}}

\begin{newenv}{2}{2}
  \item a
  \item b
  \item c
  \item d
\end{newenv}

\end{document}

What am I missing?

2
  • 5
    The default \newenvironment (like \newcommand and friends) accepts only one optional argument. For multiple optional arguments, consider the xparse interface: \NewDocumentEnvironment{newenv}{o o}{<beg env>}{<end env>}. However, with multiple optional arguments, the only way to specify the second, would be to also specify the first, even if it could be empty. How else would the parser know that the only optional argument is the second? In such instances, a key-value approach is preferred.
    – Werner
    Feb 15, 2013 at 17:57
  • 2
    have a look at More than one optional argument for newcommand which is for a newcommand, but the syntax is pretty much identical for newenvironment
    – cmhughes
    Feb 15, 2013 at 18:01

3 Answers 3

15

I would use the enumitem package for this- it eases the syntax and does the heavy lifting for us

The first part is to setup a new list environment

\newlist{newenv}{enumerate}{5}
\setlist[newenv]{label=\arabic*.}

which has a default label of 1., 2., etc

We then setup a new key, columns

\SetEnumitemKey{columns}{before=\begin{multicols}{#1},
                         after=\end{multicols}}

which allows us to use all variety of combinations, as demonstrated in the MWE below

screenshot

\documentclass{article}
\usepackage{enumitem}
\usepackage{multicol}

\newlist{newenv}{enumerate}{5}
\setlist[newenv]{label=\arabic*.}

\SetEnumitemKey{columns}{before=\begin{multicols}{#1},
                                               after=\end{multicols}}

\begin{document}
\begin{newenv}[label=\roman*),columns=4]
\item a
\item b
\item c
\item d
\end{newenv}

\hrule

\begin{newenv}[columns=3]
\item a
\item b
\item c
\end{newenv}

\hrule

\begin{newenv}[label=\roman*)]
\item a
\item b
\item c
\item d
\end{newenv}

\hrule

\begin{newenv}[label*=(A\arabic*),columns=3]
\item a
\item b
\item c
\item d
\item e
\item f
\end{newenv}

\end{document}
0
12

I agree with Werner that multiple optional arguments should be replaced by a key-value syntax. Here's a way:

\documentclass{article}
\usepackage{enumerate,multicol,xparse}

\ExplSyntaxOn
\NewDocumentEnvironment{newenv}{ O{} }
 {
  \keys_set:nn { aksr/newenv }
   {
    #1           % use the options
   }
  \int_compare:nT { \l__aksr_columns_tl > 1 } % more than one column
   { \begin{multicols}{ \l__aksr_columns_tl } }
  % We must pass the optional argument expanded
  \use:x { \exp_not:N \begin{enumerate}[ \l__aksr_label_tl ] }
 }
 {
  \end{enumerate}
  \int_compare:nT { \l__aksr_columns_tl > 1 }
   { \end{multicols} }
 }

\tl_new:N \l__aksr_columns_tl
\tl_new:N \l__aksr_label_tl

\keys_define:nn { aksr/newenv }
 {
  columns .tl_set:N  = \l__aksr_columns_tl,
  columns .initial:n = 2,
  label   .tl_set:N  = \l__aksr_label_tl,
  label   .initial   = 1.,
 }
\ExplSyntaxOff

\begin{document}
\begin{newenv}
\item a
\item b
\item c
\item d
\end{newenv}

\hrule

\begin{newenv}[columns=1]
\item a
\item b
\item c
\item d
\end{newenv}

\hrule

\begin{newenv}[label=i)]
\item a
\item b
\item c
\item d
\end{newenv}

\hrule

\begin{newenv}[label=(A),columns=3]
\item a
\item b
\item c
\item d
\item e
\item f
\end{newenv}

\end{document}

It's quite straightforward, once we have some hand with it. The only subtlety is that we must pass the argument to \begin{enumerate} expanded and not the token list variable, so \use:x comes to rescue.

The two keys just store the value in a token list variable for later usage. The defaults are simply established by giving the values before evaluating the optional argument to newenv.

You may consider adopting enumitem rather than enumerate.

enter image description here


Here's a different version that works with enumitem and shows the use of "unknown" keys:

\documentclass{article}
\usepackage{enumitem,multicol,xparse}

\ExplSyntaxOn
\NewDocumentEnvironment{newenv}{ O{} }
 {
  % clear the options passed to enumitem
  \tl_clear:N \l__aksr_enumitem_tl
  \keys_set:nn { aksr/newenv }
   {
    #1 % use the options
   }
  \int_compare:nT { \l__aksr_columns_tl > 1 } % more than one column
   { \begin{multicols}{ \l__aksr_columns_tl } }
  % We must pass the optional argument expanded; we append
  % the label key and value to the other possibly set options
  \tl_put_right:Nn \l__aksr_enumitem_tl { label = }
  \tl_put_right:NV \l__aksr_enumitem_tl \l__aksr_label_tl
  \aksr_beginenum:V \l__aksr_enumitem_tl 
 }
 {
  \end{enumerate}
  \int_compare:nT { \l__aksr_columns_tl > 1 }
   { \end{multicols} }
 }
\cs_new_protected:Npn \aksr_beginenum:n #1
 {
  \begin{enumerate}[#1]
 }
\cs_generate_variant:Nn \aksr_beginenum:n {V}

\tl_new:N \l__aksr_columns_tl
\tl_new:N \l__aksr_label_tl
\tl_new:N \l__aksr_enumitem_tl

\keys_define:nn { aksr/newenv }
 {
  columns .tl_set:N  = \l__aksr_columns_tl,
  columns .initial:n = 2,
  label   .tl_set:N  = \l__aksr_label_tl,
  label   .initial:n = \arabic*.,
  %% unknown keys will be passed to enumitem
  unknown .code:n    = \tl_put_right:Nx \l__aksr_enumitem_tl 
                        { 
                         \l_keys_key_tl % the key name
                         \tl_if_empty:nF { #1 } % the value, if not empty
                          { = \exp_not:n { #1 } } , },
 }
\ExplSyntaxOff

\begin{document}
\begin{newenv}
\item a
\item b
\item c
\item d
\end{newenv}

\hrule

\begin{newenv}[columns=1,noitemsep]
\item a
\item b
\item c
\item d
\end{newenv}

\hrule

\begin{newenv}[label=\roman*)]
\item a
\item b
\item c
\item d
\end{newenv}

\hrule

\begin{newenv}[label=(\alph*),columns=3]
\item a
\item b
\item c
\item d
\item e
\item f
\end{newenv}

\end{document}
6
  • +1: Very nice, but in the second MWE there is an \ExplSyntaxOff missing before \begin{document}. Jul 27, 2014 at 14:09
  • @egreg If I wanted to learn to understand this syntax, what should I start by reading? I have tried to puzzle out some of the LaTeX 3 documentation before in order to learn this but everything seems to assume I already know basically how it works and my efforts have proved fruitless. It just seems too alien and quite unlike anything else I deal with. (Maybe it is more familiar to people with programming knowledge but I can basically manage a simplish shell script and nothing more.) [I have a horrible feeling I've either asked this question before or seen somebody else do so but no idea where.]
    – cfr
    Jul 27, 2014 at 14:54
  • 1
    @cfr I have no background in programming, other than (La)TeX. Look at texdoc expl3 and texdoc interface3; then work out some examples.
    – egreg
    Jul 27, 2014 at 14:58
  • @egreg When activating the columns = 3 key (for example) in the first level and nesting newenv all are in 3 columns. The columns = 3 key should be active only at the level at which it is used and use the default value at the rest of the levels, is what I am saying correct? Mar 28, 2019 at 17:41
  • @PabloGonzálezL Add columns = 1, before #1 % use the options
    – egreg
    Mar 28, 2019 at 18:09
6

At risk of being reprimanded for the suggestion, you could use different delimiters for the different optionals. The line

\NewDocumentEnvironment{newenv}{ O{2} D<>{(a)} }

Specifies that there are two optional arguments, the first is of type O which is a standard square bracketed optional with default whatever is in the following brace group (here 2). The second optional argument is of type D ("D"elimited) with left delimiter <, right delimiter > and has default whatever is given in the following brace group, here (a).

\documentclass{article}
\usepackage{array}
\usepackage{multicol}
\usepackage{enumerate}
\usepackage{xparse}

\NewDocumentEnvironment{newenv}{ O{2} D<>{(a)} }
{\begin{multicols}{#1}\begin{enumerate}[#2]}
{\end{enumerate}\end{multicols}}

\begin{document}

\begin{newenv}[2]<i.>
  \item a
  \item b
  \item c
  \item d
\end{newenv}

\end{document}
2
  • 1
    +1; but the syntax is complicated and the order must be respected.
    – egreg
    Feb 15, 2013 at 18:30
  • @egreg Both true! key-val is indeed the superior approach, I don't know it well enough to have provided a decent answer so left that for the experts and took the low hanging fruit :)
    – Scott H.
    Feb 15, 2013 at 18:49

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .