5

I would like to define a function with a fixed (three) number of optional arguments followed by a variable number of mandatory comma separated arguments. Each one of the mandatory argument has to produce a text in a colored box (the three optional arguments defines the color of the boxes).

Following the advices of this thread, I have written this function:

\NewDocumentCommand{\boxed}{O{white}O{gray}O{white}>{\SplitList{,}}m}{%
  \ProcessList{#4}{\func}}
\NewDocumentCommand{\func}{m}{%
  \fcolorbox{white}{gray}{\color{white}#1}\space}

The above function produces the desired boxes, but ignore the optional arguments, because I haven't found a way to pass more than one argument to func.

After some researches, I have found this workaround:

\NewDocumentCommand{\boxed}{O{white}O{gray}O{white}>{\SplitList{,}}m}{%
  \def\@opta{#1}%
  \def\@optb{#2}%
  \def\@optc{#3}%
  \ProcessList{#4}{\func}}
\NewDocumentCommand{\func}{m}{%
  \fcolorbox{\@optc}{\@optb}{\color{\@opta}#1}\space}

The above function does what I want to, but I can't believe there is no easier way to accomplish the task without those ugly def. Am I wrong?

4

The \ProcessList functionality is experimental and suggests that the macro takes a single argument. One way around this is to define \func within \boxed so it grabs the arguments:

enter image description here

\documentclass{article}

\usepackage{xparse,xcolor}

\NewDocumentCommand{\boxed}{ O{white} O{gray} O{white} >{\SplitList{,}}m }{{%
  \NewDocumentCommand{\func}{ m }{%
    \fcolorbox{#3}{#2}{\color{#1}\strut##1}\space}
  \ProcessList{#4}{\func}}}

\begin{document}

\boxed{a,b,c}

\boxed[yellow]{d,e,f}

\boxed[yellow][black]{g,h,i}

\boxed[yellow][black][red]{j,k,l}

\end{document}
  • This is exactly what I was looking for! Just...why/how the boxes became so tall and all of the same size? My previous version (and the other answers too) produced boxes more "fitting" to the content... – Andrea Nov 10 '16 at 7:15
  • @Andrea: I added \strut before ##1 which would make things vertically the same. You can remove that, of course. – Werner Nov 10 '16 at 7:17
6

In this case it's better going with lower level functions:

\documentclass{article}
\usepackage{xparse,xcolor}

\ExplSyntaxOn

\NewDocumentCommand{\myboxed}{O{white}O{gray}O{white}m}
 {
  \clist_map_inline:nn { #4 }
   {
    \andrea_myboxed:nnnn { #1 } { #2 } { #3 } { ##1 }
   }
 }
\cs_new_protected:Nn \andrea_myboxed:nnnn
 {
  \fcolorbox{#3}{#2}{\color{#1}#4}
 }
\ExplSyntaxOff

\begin{document}

\myboxed{a,b,c}

\end{document}

enter image description here

Note that \clist_map_inline:nn splits the first argument at commas, removes leading and trailing spaces and loops over the items, passing each of them to the code in the second argument as #1; here it becomes ##1 because we're in the body of a definition.

Splitting at different characters than a comma can be done with the \seq_set_split:Nnn function and using \seq_map_inline:Nn instead. You find several examples on the site.

However, three optional arguments are a nuisance, as if you want to only specify the third one, you have to explicitly give also the first two. A key-value interface is better. The advantage is being able to specify keys in any order, so you just have to remember their names (which is easier than remembering the order of the optional arguments).

\documentclass{article}
\usepackage{xparse,xcolor}

\ExplSyntaxOn

\keys_define:nn { andrea/myboxed }
 {
  border     .tl_set:N = \l__andrea_myboxed_border_tl,
  background .tl_set:N = \l__andrea_myboxed_background_tl,
  text       .tl_set:N = \l__andrea_myboxed_text_tl,
  border     .initial:n = white,
  background .initial:n = gray,
  text       .initial:n = white,
 }
\NewDocumentCommand{\kmyboxed}{O{}m}
 {
  \group_begin:
  \keys_set:nn { andrea/myboxed } { #1 }
  \clist_map_inline:nn { #2 }
   {
    \andrea_myboxed:VVVn
     \l__andrea_myboxed_border_tl
     \l__andrea_myboxed_background_tl
     \l__andrea_myboxed_text_tl
     {##1}
   }
  \group_end:
 }
\cs_new_protected:Nn \andrea_myboxed:nnnn
 {
  \fcolorbox{#3}{#2}{\color{#1}#4}
 }
\cs_generate_variant:Nn \andrea_myboxed:nnnn { VVV }
\ExplSyntaxOff

\begin{document}

\kmyboxed{a,b,c}

\kmyboxed[text=red,background=green,border=blue]{a,b,c}

\end{document}

enter image description here

We define some keys, set their initial values and essentially execute the same code as before, but in a group to keep the values unchanged when the particular loop is finished. If the group gets into the way, it's possible to reset the initial values at \keys_set:nn.

The variant \andrea_myboxed:VVVn is used to transform the token list variables into braced arguments (with their value between the braces); not essential in this particular application, in others it can be.

  • This is by far the most complete answer, thank you :) – Andrea Nov 10 '16 at 7:17
2

I think \ProcessList must take a single argument from the xparse documentation. You can always use \ExplSyntaxOn \ExplSyntaxOff (xparse loads expl3 after all) and work with \tl_set:Nn \l_boxed_opta_tl {#1} etc. for a fully expl3 solution as below.

\documentclass{article}
\usepackage{xcolor,xparse}

\ExplSyntaxOn
\tl_new:N \l_boxed_opta_tl
\tl_new:N \l_boxed_optb_tl
\tl_new:N \l_boxed_optc_tl

\NewDocumentCommand{\boxed}{O{white}O{gray}O{white}>{\SplitList{,}}m}{
    \tl_set:Nn \l_boxed_opta_tl {#1}
    \tl_set:Nn \l_boxed_optb_tl {#2}
    \tl_set:Nn \l_boxed_optc_tl {#3}
    \ProcessList{#4}{\boxed_func:n}
}

\cs_new:Npn \boxed_func:n #1 {
    \fcolorbox{\l_boxed_optc_tl}{\l_boxed_optb_tl}{\color{\l_boxed_opta_tl}#1}\space
}
\ExplSyntaxOff

\begin{document}
\boxed{a,b,c}
\end{document}

enter image description here

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.