4

This question arose while responding to another inquiry on SE Tex. Currently, there is no practical application for this, but I want to be prepared in case I encounter a similar situation when designing a command in the future. If we create a command with three optional arguments for text color (using three different delimiters: <>, [], and ()), we need to avoid compilation errors when users provide one, two, or even three empty inputs, such as \testinga[](blue)... or \testinga<>(blue).... We could use \ifblank to check for these scenarios and set the command to use default values if any optional argument is left empty. However, this method can make the code quite lengthy and difficult to manage. Additionally, with only three optional arguments, it’s manageable, but adding more could complicate things significantly. I'm curious if there is a more efficient way to achieve this. Here’s an example: is it possible to simplify the lengthy conditional section in the code definition? Any suggestions?

\documentclass{report}
\usepackage{xcolor}
\usepackage{etoolbox}
\NewDocumentCommand{\testinga}{D<>{red} O{blue} D(){green} m m m}{%
\ifblank%
{#1}%
{\ifblank%
{#2}%
{\ifblank%
{#3}%
{\textcolor{red}{#4}\textcolor{blue}{#5}\textcolor{green}{#6}}%
{\textcolor{red}{#4}\textcolor{blue}{#5}\textcolor{#3}{#6}}%
}%
{\ifblank%
{#3}%
{\textcolor{red}{#4}\textcolor{#2}{#5}\textcolor{green}{#6}}%
{\textcolor{red}{#4}\textcolor{#2}{#5}\textcolor{#3}{#6}}%
}%
}%
{\ifblank%
{#2}%
{\ifblank%
{#3}%
{\textcolor{#1}{#4}\textcolor{blue}{#5}\textcolor{green}{#6}}%
{\textcolor{#1}{#4}\textcolor{blue}{#5}\textcolor{#3}{#6}}%
}%
{\ifblank%
{#3}%
{\textcolor{#1}{#4}\textcolor{#2}{#5}\textcolor{green}{#6}}%
{\textcolor{#1}{#4}\textcolor{#2}{#5}\textcolor{#3}{#6}}%
}%
}%
}
\parindent0pt
\begin{document}  
All seven empty option cases are tested here:

\testinga<yellow>[purple](){test1}{test2}{test3}

\testinga<yellow>[](purple){test1}{test2}{test3}

\testinga<>[yellow](purple){test1}{test2}{test3}

\testinga<yellow>[](){test1}{test2}{test3}

\testinga<>[yellow](){test1}{test2}{test3}

\testinga<>[](yellow){test1}{test2}{test3}

\testinga<>[](){test1}{test2}{test3}
\end{document} 

enter image description here

3
  • @cfr I guess not. Just curious if there is a better solution. If this is for the user without too much experience of latex, they could potential make these mistakes. Maybe when they do so, give a warning that a empty optional argument is detected use the default instead. Maybe the question is pointless. I don't know. Commented Nov 27, 2024 at 1:33
  • 1
    fwiw, I don't think it is a pointless question at all. my comment was a genuine question. Commented Nov 27, 2024 at 1:57
  • cleaned up obsolete comments ... Commented Nov 27, 2024 at 4:31

2 Answers 2

4

(With more recent releases of the LaTeX 2ε-kernel, \IfBlankTF can be used instead of loadng the package etoolbox and using its \ifblank.)


xcolor lets you do this:

\documentclass{report}
\usepackage{xcolor}
\usepackage{etoolbox}
\NewDocumentCommand{\testinga}{D<>{red} O{blue} D(){green} m m m}{%
  \textcolor{\ifblank{#1}{red}{#1}}{#4}%
  \textcolor{\ifblank{#2}{blue}{#2}}{#5}%
  \textcolor{\ifblank{#3}{green}{#3}}{#6}%
}
\parindent0pt
\begin{document}  
All seven empty option cases are tested here:

\testinga<yellow>[purple](){test1}{test2}{test3}

\testinga<yellow>[](purple){test1}{test2}{test3}

\testinga<>[yellow](purple){test1}{test2}{test3}

\testinga<yellow>[](){test1}{test2}{test3}

\testinga<>[yellow](){test1}{test2}{test3}

\testinga<>[](yellow){test1}{test2}{test3}

\testinga<>[](){test1}{test2}{test3}
\end{document} 

Alternatively do:

\documentclass{report}
\usepackage{xcolor}
\usepackage{etoolbox}
\NewDocumentCommand{\testinga}{D<>{red} O{blue} D(){green} m m m}{%
  \ifblank{#1}{\textcolor{red}}{\textcolor{#1}}{#4}%
  \ifblank{#2}{\textcolor{blue}}{\textcolor{#2}}{#5}%
  \ifblank{#3}{\textcolor{green}}{\textcolor{#3}}{#6}%
}
\parindent0pt
\begin{document}  
All seven empty option cases are tested here:

\testinga<yellow>[purple](){test1}{test2}{test3}

\testinga<yellow>[](purple){test1}{test2}{test3}

\testinga<>[yellow](purple){test1}{test2}{test3}

\testinga<yellow>[](){test1}{test2}{test3}

\testinga<>[yellow](){test1}{test2}{test3}

\testinga<>[](yellow){test1}{test2}{test3}

\testinga<>[](){test1}{test2}{test3}
\end{document} 

Or - if expandability is not needed - define your own xparse-argument-preprocessor for checking blankness:

\documentclass{report}
\usepackage{xcolor}
\usepackage{etoolbox}

% \edef-\unexpanded-thingie in case a mean user defines colors which
% have hashes in their names.
\newcommand\CheckBlank[2]{%
  \edef\ProcessedArgument{%
    \ifblank{#2}{\unexpanded{#1}}{\unexpanded{#2}}%
  }%
}%
\NewDocumentCommand{\testinga}{%
  >{\CheckBlank{red}}D<>{red} 
  >{\CheckBlank{blue}}O{blue} 
  >{\CheckBlank{green}}D(){green} 
  m m m
}{%
  \textcolor{#1}{#4}%
  \textcolor{#2}{#5}%
  \textcolor{#3}{#6}%
}
\parindent0pt
\begin{document}  
All seven empty option cases are tested here:

\testinga<yellow>[purple](){test1}{test2}{test3}

\testinga<yellow>[](purple){test1}{test2}{test3}

\testinga<>[yellow](purple){test1}{test2}{test3}

\testinga<yellow>[](){test1}{test2}{test3}

\testinga<>[yellow](){test1}{test2}{test3}

\testinga<>[](yellow){test1}{test2}{test3}

\testinga<>[](){test1}{test2}{test3}
\end{document} 

If \textcolor was not a command where expansion of the argument denoting the name of the color is triggered, then with newer TeX-engines, where \expanded and \unexpanded are available, expansion of \ifblank before carrying out \textcolor could be triggered as follows:

\documentclass{report}
\usepackage{xcolor}
\usepackage{etoolbox}
\NewDocumentCommand{\testinga}{D<>{red} O{blue} D(){green} m m m}{%
  \expanded{\noexpand\textcolor{\ifblank{#1}{\unexpanded{red}}{\unexpanded{#1}}}}{#4}%
  \expanded{\noexpand\textcolor{\ifblank{#2}{\unexpanded{blue}}{\unexpanded{#2}}}}{#5}%
  \expanded{\noexpand\textcolor{\ifblank{#3}{\unexpanded{green}}{\unexpanded{#3}}}}{#6}%
}
\parindent0pt
\begin{document}  
All seven empty option cases are tested here:

\testinga<yellow>[purple](){test1}{test2}{test3}

\testinga<yellow>[](purple){test1}{test2}{test3}

\testinga<>[yellow](purple){test1}{test2}{test3}

\testinga<yellow>[](){test1}{test2}{test3}

\testinga<>[yellow](){test1}{test2}{test3}

\testinga<>[](yellow){test1}{test2}{test3}

\testinga<>[](){test1}{test2}{test3}
\end{document} 

E.g., with older TeX-engines, where \expanded and probably \unexpanded are not available, expansion of \ifblank before carrying out \textcolor could be triggered via \romannumeral:

\documentclass{report}
\usepackage{xcolor}
\usepackage{etoolbox}

\csname @ifdefinable\endcsname\stopromannumeral{\chardef\stopromannumeral=0 }%
\NewDocumentCommand{\testinga}{D<>{red} O{blue} D(){green} m m m}{%
  \expandafter\textcolor\expandafter{\romannumeral\ifblank{#1}{\stopromannumeral red}{\stopromannumeral #1}}{#4}%
  \expandafter\textcolor\expandafter{\romannumeral\ifblank{#2}{\stopromannumeral blue}{\stopromannumeral #2}}{#5}%
  \expandafter\textcolor\expandafter{\romannumeral\ifblank{#3}{\stopromannumeral green}{\stopromannumeral #3}}{#6}%
}
\parindent0pt
\begin{document}

All seven empty option cases are tested here:

\testinga<yellow>[purple](){test1}{test2}{test3}

\testinga<yellow>[](purple){test1}{test2}{test3}

\testinga<>[yellow](purple){test1}{test2}{test3}

\testinga<yellow>[](){test1}{test2}{test3}

\testinga<>[yellow](){test1}{test2}{test3}

\testinga<>[](yellow){test1}{test2}{test3}

\testinga<>[](){test1}{test2}{test3}
\end{document} 

(In expl3, the L3 programming layer of LaTeX 2ε, the pair \romannumeral / \stopromannumeral is available as \exp:w / \exp_end: .)

1
  • First two is more specific for this example. The case could way more complicate than just color the text. The idea of argument-preprocessor is brilliant. It can be adapt to more general cases. Thank you so much. Commented Nov 27, 2024 at 3:21
4

No unique answer, because this mostly depends on what you want to do with the arguments and where (in expansion context or elsewhere).

In this particular case you can make the code much more compact, because the color argument to \textcolor is fully expanded.

\documentclass{report}
\usepackage{xcolor}

\NewExpandableDocumentCommand{\DefaultForNoValueOrEmpty}{mm}{%
  \IfNoValueTF{#1}{#2}{\IfBlankTF{#1}{#2}{#1}}%
}

\NewDocumentCommand{\testinga}{d<> o d() m m m}{%
  \textcolor{\DefaultForNoValueOrEmpty{#1}{red}}{#4}%
  \textcolor{\DefaultForNoValueOrEmpty{#2}{blue}}{#5}%
  \textcolor{\DefaultForNoValueOrEmpty{#3}{green}}{#6}%
}

\setlength{\parindent}{0pt}

\begin{document}  

All seven empty option cases are tested here:

\testinga<yellow>[purple](){test1}{test2}{test3}

\testinga<yellow>[](purple){test1}{test2}{test3}

\testinga<>[yellow](purple){test1}{test2}{test3}

\testinga<yellow>[](){test1}{test2}{test3}

\testinga<>[yellow](){test1}{test2}{test3}

\testinga<>[](yellow){test1}{test2}{test3}

\testinga<>[](){test1}{test2}{test3}

But there are more to test

\testinga{test1}{test2}{test3}

\testinga(purple){test1}{test2}{test3}

\testinga<>(purple){test1}{test2}{test3}

and so on

\end{document} 

output

However, I warn against such practice: three consecutive optional arguments may be used, but only if they are part of a “chain”: the second one makes sense when the first optional argument is present and so on.

With your code, users (including you) will have a hard time in remembering what the <> [] and () refer to and, even more difficult, what's their order. A more sensible syntax would be

\NewDocumentCommand{\testingb}{O{red}m O{blue}m O{green}m}{%
  \textcolor{#1}{#2}%
  \textcolor{#3}{#4}%
  \textcolor{#5}{#6}%
}

\testingb[yellow]{test1}[purple]{test2}{test3}

\testinga[yellow]{test1}{test2}[purple]{test3}

\testingb{test1}[yellow]{test2}[purple]{test3}

A different strategy would be using a key-value syntax.

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.