3

After I had a macro that worked, I tried to improve it by making some parameters optional. Unfortunately the macro no longer works. Instead I'm getting errors I do not understand, for example:

LaTeX Warning: Label `####5' multiply defined.
LaTeX Warning: Label `####5' multiply defined.
! LaTeX Error: \fLab undefined.
! Illegal parameter number in definition of \fLab.
! Illegal parameter number in definition of \reserved@a.
! LaTeX Error: \fCap undefined.

...and so on. The last code I tried looked like this:

%% Graphics figure with caption and label
% [1:placement,] 2:relative width, 3:file name[, 4:caption[, 5:label]]
\newcommand{\figCapLab}[5][htbp]{%
\ifthenelse{\equal{#5}{}}%
{\renewcommand{\fLab}{}}%
{\renewcommand{\fLab}{\label{##5}}}%
\ifthenelse{\equal{#4}{}}%
{\renewcommand{\fCap}{\fLab}}%
{\renewcommand{\fCap}{\caption{\fLab{\small{}##4}}}}%
\begin{figure}[#1]%
\centering%
\begin{minipage}[t]{#2\textwidth}%
\includegraphics[width=\textwidth]{#3}% is width of surrounding minipage
\fCap%
\end{minipage}%
\end{figure}
}
%% Graphics figure with caption
% [1:placement,] 2:relative width, 3:file name, 4:caption
\newcommand{\figCap}[4]{%
\ifthenelse{\equal{#1}{}}%
{\figCapLab{#2}{#3}{#4}{}}%
{\figCapLab[#1]{#2}{#3}{#4}{}}%
}
%% Graphics figure with label
% [1:placement,] 2:relative width, 3:file name, 4:label
\newcommand{\figLab}[4]{%
\ifthenelse{\equal{#1}{}}%
{\figCapLab{#2}{#3}{}{#4}}%
{\figCapLab[#1]{#2}{#3}{}{#4}}%
}

Who can explain what went wrong?

People who like complete examples should add this prolog:

\documentclass[a4paper,twoside]{report}
\usepackage{german}
\usepackage[latin1]{inputenc}
\usepackage{a4}
\usepackage{amsmath}
\usepackage{url}
\usepackage{graphicx}
\usepackage{ifthen}

...and this epilog:

\begin{document}
See \ref{foo}.
\figCapLab{0.9}{whatever.pdf}{Caption}{foo}
\end{document}
  • 4
    Instead of identifying mistakes in your code without much context, can you provide information on what you want to achieve ultimately? Perhaps there are better ways of achieving it. – Werner Mar 4 at 2:39
  • please extend your code fragment to complete but small document! – Zarko Mar 4 at 2:40
  • @Werner: There are always different ways to reach a goal, but if you change the way too frequently, you'll never make it. So I'd prefer a fix for my "solution" over a completely new attempt (like xparse). – U. Windl Mar 4 at 2:57
  • 2
    it is much easier for people to debug and help you if you provide the code as a complete document that can be run, here you just provide fragments, don't say what error you get and expect people to construct an example and guess that they get the same error that you meant to ask about. – David Carlisle Mar 4 at 7:53
  • 1
    you have only defined one optional argument, arguments 4 and 5 are mandatory but you are testing if they are {} – David Carlisle Mar 4 at 7:55
1

When I obey your vague instructions for creating a compilable example myself which exhibits the erroneous behavior

\documentclass[a4paper,twoside]{report}
\usepackage{german}
\usepackage[latin1]{inputenc}
\usepackage{a4}
\usepackage{amsmath}
\usepackage{url}
\usepackage{graphicx}
\usepackage{ifthen}

%% Graphics figure with caption and label
% [1:placement,] 2:relative width, 3:file name[, 4:caption[, 5:label]]
\newcommand{\figCapLab}[5][htbp]{%
\ifthenelse{\equal{#5}{}}%
{\renewcommand{\fLab}{}}%
{\renewcommand{\fLab}{\label{##5}}}%
\ifthenelse{\equal{#4}{}}%
{\renewcommand{\fCap}{\fLab}}%
{\renewcommand{\fCap}{\caption{\fLab{\small{}##4}}}}%
\begin{figure}[#1]%
\centering%
\begin{minipage}[t]{#2\textwidth}%
\includegraphics[width=\textwidth]{#3}% is width of surrounding minipage
\fCap%
\end{minipage}%
\end{figure}
}
%% Graphics figure with caption
% [1:placement,] 2:relative width, 3:file name, 4:caption
\newcommand{\figCap}[4]{%
\ifthenelse{\equal{#1}{}}%
{\figCapLab{#2}{#3}{#4}{}}%
{\figCapLab[#1]{#2}{#3}{#4}{}}%
}
%% Graphics figure with label
% [1:placement,] 2:relative width, 3:file name, 4:label
\newcommand{\figLab}[4]{%
\ifthenelse{\equal{#1}{}}%
{\figCapLab{#2}{#3}{}{#4}}%
{\figCapLab[#1]{#2}{#3}{}{#4}}%
}

\begin{document}
See \ref{foo}.
\figCapLab{0.9}{whatever.pdf}{Caption}{foo}
\end{document}

, I don't get any of the errors you describe but I get:

LaTeX Warning: Reference `foo' on page 1 undefined on input line 43.


! LaTeX Error: \fLab undefined.

See the LaTeX manual or LaTeX Companion for explanation.
Type  H <return>  for immediate help.
 ...                                              

l.44 \figCapLab{0.9}{whatever.pdf}{Caption}{foo}

? 
! Illegal parameter number in definition of \fLab.
<to be read again> 
                   5
l.44 \figCapLab{0.9}{whatever.pdf}{Caption}{foo}

? 

! LaTeX Error: \fCap undefined.

See the LaTeX manual or LaTeX Companion for explanation.
Type  H <return>  for immediate help.
 ...                                              

l.44 \figCapLab{0.9}{whatever.pdf}{Caption}{foo}

? 
! Illegal parameter number in definition of \fCap.
<to be read again> 
                   4
l.44 \figCapLab{0.9}{whatever.pdf}{Caption}{foo}

? 

LaTeX Warning: File `whatever.pdf' not found on input line 44.


! Package pdftex.def Error: File `whatever.pdf' not found: using draft setting.


See the pdftex.def package documentation for explanation.
Type  H <return>  for immediate help.
 ...                                              

l.44 \figCapLab{0.9}{whatever.pdf}{Caption}{foo}

? 
! Illegal parameter number in definition of \reserved@a.
<to be read again> 
                   4
l.44 \figCapLab{0.9}{whatever.pdf}{Caption}{foo}

? 
! Illegal parameter number in definition of \reserved@a.
<to be read again> 
                   5
l.44 \figCapLab{0.9}{whatever.pdf}{Caption}{foo}

? 
! You can't use `macro parameter character #' in restricted horizontal mode.
<argument> ...e : \ignorespaces \fLab {\small {}##
                                                  4}
l.44 \figCapLab{0.9}{whatever.pdf}{Caption}{foo}

? 
[1{/var/lib/texmf/fonts/map/pdftex/updmap/pdftex.map}] (./test.aux)

LaTeX Warning: There were undefined references.

When instead I do:

\documentclass[a4paper,twoside]{report}
\usepackage{german}
\usepackage[latin1]{inputenc}
\usepackage{a4}
\usepackage{amsmath}
\usepackage{url}
\usepackage{graphicx}
\usepackage{ifthen}

% As later \renwecommand is done on these macros, 
% they should be defined: !!!!!!!!!!!!
\newcommand{\fLab}{}
\newcommand{\fCap}{}

%% Graphics figure with caption and label
%  #1: optional: placement
%  #2: non-optional: relative width
%  #3: non-optional: file name
%  #4: non-optional: in case not empty: caption
%  #5: non-optional: in case not empty: label
\newcommand{\figCapLab}[5][htbp]{%
\ifthenelse{\equal{#5}{}}%
{\renewcommand{\fLab}{}}%
{\renewcommand{\fLab}{\label{#5}}}% !!!!Don't double the hash!!!!
\ifthenelse{\equal{#4}{}}%
{\renewcommand{\fCap}{\fLab}}%
{\renewcommand{\fCap}{\caption{\fLab{\small{}#4}}}}% !!!!Don't double the hash!!!!
\begin{figure}[#1]%
\centering
\begin{minipage}[t]{#2\textwidth}%
\includegraphics[width=\textwidth]{#3}% is width of surrounding minipage
\fCap
\end{minipage}%
\end{figure}%%%%%%%
}
%% Graphics figure with caption
% [1:placement,] 2:relative width, 3:file name, 4:caption
\newcommand{\figCap}[4]{%
\ifthenelse{\equal{#1}{}}%
{\figCapLab{#2}{#3}{#4}{}}%
{\figCapLab[#1]{#2}{#3}{#4}{}}%
}
%% Graphics figure with label
% [1:placement,] 2:relative width, 3:file name, 4:label
\newcommand{\figLab}[4]{%
\ifthenelse{\equal{#1}{}}%
{\figCapLab{#2}{#3}{}{#4}}%
{\figCapLab[#1]{#2}{#3}{}{#4}}%
}

\begin{document}
See \ref{foo}.
\figCapLab{0.9}{example-grid-100x100pt.pdf}{Caption}{foo}
\end{document}

, then I don't get any errors or warnings with the second compilation.


By the way 1:

I don't have whatever.pdf in the texmf-trees of my system.

If you wish to provide examples that process images, you can use the ready-to-use images that modern LaTeX systems offer to you:

E.g., the documentation of Martin Scharrer's MWE package lists all the images available due to that package. With current TeX-platforms and current releases of the MWE-package, the images are usable without the need of loading the package because they are integrated into the texmf-tree and into the filename-database.

By the way 2:

Under normal circumstances you may indent lines of TeX source code for improving readability. ;-) This is because when (La)TeX begins to read a line of input, the state of the reading-apparatus is in state N (new line) while in state N characters of category code 10(space) will not be tokenized as space tokens but will not yield any tokens at all.

By the way 3:

It is good practice to provide examples which people can compile as they are for exactly reproducing the erroneous behavior. Being able to reproduce erroneous behavior is important for being able to debug the code which produces that erroneous behavior.


Dr. Nicola Talbot's Creating a LaTeX Minimal Example provides some guidelines.

There also is How to make a “minimum example” from texfaq.org which also contains some links to advices for asking questions.

In What is a minimal working example? Christian Faulhammer does not use the term "Minimal Example" but he uses the term "Minimal Working Example" even for examples which work only in the sense that they are sufficient for exhibiting an erroneous behavior.

You may also be interested in the answers to the question I've just been asked to write a minimal example, what is that?

  • I named the file whatever.pdf to emphasize the fact that the actual file name is unimportant for the problem, and you can use any PDF file you like (assuming everyone who is going to answer this problem either has some PDF file or is able to create one). As my text installation (openSUSE) consists of more than 1000 individual packages, I must admit that I lost overview of what is needed for what. On lack of spaces: I'm still unsure which spaces will be ignored and which aren't , so I used no indent. – U. Windl Mar 5 at 1:05
  • Would it be possible to provide a context diff of the changes required to my definitions? I feel you thought "you made it hard for me to understand, so I'll make it hard for you to understand". And as written for another answer: Maybe explain what I did wrong. – U. Windl Mar 5 at 1:22
  • On ##4: I thought if an "inner" \newcommand accesses arguments of an outer \newcommand, the # has to be doubled regardless whether the inner \newcommand has (formal) arguments. – U. Windl Mar 5 at 2:11
  • @U.Windl The inner \newcommand doesn't "access" the argument of the outer \newcommand. At the time of carrying out the "outer command", which was defined at an earlier point in time by what you call "the outer \newcommand", TeX uses the argument of the "outer command" for defining the "inner command" by carrying out the "inner \newcommand". Within the definition of the outer \newcommand the arguments of the inner \newcommand are to be denoted by means of doubled hashes because when carrying out the outer command that defines the inner command, two hashes are collapsed into one. – Ulrich Diez Mar 5 at 9:54
5

The OP asked me for a little explanation to this answer so I will add some text here. The standard LaTeX mechanism for optional arguments allows for one optional argument that comes prior to mandatory arguments. Thus, to create a syntax that provides an optional argument, 2 mandatory arguments, and then 2 optional arguments, requires 3 macros to be strung together successively, since there are 3 optional arguments requested.

The first macro absorbs an optional plus 2 mandatory arguments, and then it must invoke a second macro, and here is the key, as its final action! The reason the 2nd macro invocation must be the final action of the first macro is that the 2nd macro must absorb an optional argument. If there are any tokens in the first macro that follow the invocation of the 2nd macro in the chain, then those other tokens in the 1st macro will be absorbed as the argument, instead of what is intended.

Likewise, the 2nd macro must call on the 3rd macro as its final action.

Certain quirky highlights:

  1. if the successive macro is to be called inside of an \if block, one must \expandafter prior to the successive macro, so that the successive macro doesn't try to absorb the \else or \fi.

  2. In this particular case, the final optional argument, the label, is only callable if a caption has been specified. Thus, the 2nd macro in the chain will only call on the 3rd macro if a caption has been specified. Otherwise, it will truncate the sequence.

The MWE:

\documentclass{article}
\usepackage{graphicx}
\newcommand\addtofigtoks[1]{\expandafter\figtoks\expandafter
  {\the\figtoks#1}}
\newtoks\figtoks
\newcommand\figCapLab[3][htbp]{%
  \figtoks{\begin{figure}[#1]}
  \addtofigtoks{\centering}
  \addtofigtoks{\includegraphics[width=#2\textwidth]{#3}}
  \optcap
}
\newcommand\optcap[1][\relax]{%
  \ifx\relax#1\relax
    \addtofigtoks{\end{figure}}
    \the\figtoks
  \else
    \addtofigtoks{\caption{#1}}%
    \expandafter\labelopt
  \fi
}
\newcommand\labelopt[1][\relax]{%
  \ifx\relax#1\relax\else\addtofigtoks{\label{#1}}\fi
  \addtofigtoks{\end{figure}}
  \the\figtoks
}
\begin{document}
\figCapLab{.2}{example-image-a}
\figCapLab{.2}{example-image-b}[My caption]
\figCapLab{.2}{example-image-c}[My caption][fg:label1]
\figCapLab[p]{.2}{example-image}[Other caption][fg:label2]

In figures \ref{fg:label1} and \ref{fg:label2}...
\end{document}

enter image description here

Ack-shu-ally, the more I think of it, tokens are not even needed:

\documentclass{article}
\usepackage{graphicx}
\newcommand\figCapLab[3][htbp]{%
  \begin{figure}[#1]
  \centering
  \includegraphics[width=#2\textwidth]{#3}
  \optcap
}
\newcommand\optcap[1][\relax]{%
  \ifx\relax#1\relax
    \end{figure}
  \else
    \caption{#1}%
    \expandafter\labelopt
  \fi
}
\newcommand\labelopt[1][\relax]{%
  \ifx\relax#1\relax\else\label{#1}\fi
  \end{figure}
}
\begin{document}
\figCapLab{.2}{example-image-a}
\figCapLab{.2}{example-image-b}[My caption]
\figCapLab{.2}{example-image-c}[My caption][fg:label1]
\figCapLab[p]{.2}{example-image}[Other caption][fg:label2]

In figures \ref{fg:label1} and \ref{fg:label2}...
\end{document}
  • As I tried to point out: Unlike most people asking questions, I don't just want a solution to copy without understanding, but instead I want to learn what I did wrong to use that knowledge for the future. Sorry, I'm pre-Google generation ;-) – U. Windl Mar 5 at 1:17
  • 1
    @U.Windl I will add some context to my answer. – Steven B. Segletes Mar 5 at 1:22
  • @U.Windl Please see my added commentary. – Steven B. Segletes Mar 5 at 1:34
  • Imagine (the truth!) that the user asking the question only knows LaTeX, and not TeX, so the user wonders what \optcap, \relax and \expandafter really do. I guess \ifx, \else, and \fi are TeX's low-level conditionals. When describing the processing in an abstract way (as you do), some concrete examples (e.g. actual arguments) would be helpful for the TeX Dummies like me. – U. Windl Mar 5 at 1:51
  • @U.Windl \optcap is just the 2nd macro I created in the string of 3 macros. \relax is a macro that does nothing---however, it can be tested for, which is how I use it here. The sequence \expandafter\macro\othermacro causes \othermacro to be expanded once, before \macro is executed. So, \expandafter\labelopt\fi causes the \fi to be executed (closing out the conditional), so that \labelopt will absorb the next token in the input stream, rather than absorbing the \fi. Indeed, \ifx...\else...\fi is one type of TeX conditional that compares the next 2 tokens WITHOUT expansion – Steven B. Segletes Mar 5 at 1:59
4

Here is how you can achieve your goal using xparse:

enter image description here

\documentclass{article}

\usepackage{graphicx,xparse}

% \figCapLab
%   [<float spec>]   #1
%   {<width factor>} #2
%   {<image>}        #3
%   [<caption>]      #4
%   [<label>]        #5
\NewDocumentCommand{\figCapLab}{ O{htbp} m m o o }{%
  \begin{figure}[#1]
    \centering
    \includegraphics[width=#2\linewidth]{#3}% Set image at width
    \IfValueT{#4}
      {\caption{#4}\IfValueT{#5}{\label{#5}}}% Set possible \caption and \label
  \end{figure}
}

\begin{document}

\figCapLab{.2}{example-image-a}
\figCapLab{.2}{example-image-b}[My caption]
\figCapLab{.2}{example-image-c}[My caption][fg:label1]
\figCapLab[p]{.2}{example-image}[Other caption][fg:label2]

In figures \ref{fg:label1} and \ref{fg:label2}\ldots

\end{document}

Optional arguments with a default is specified using O{<default>} while optional arguments without a default uses o. Conditioning on whether or not a value is supplied is done using \IfValueTF{<parameter>}{<true>}{<false>}. There are also singular conditionals \IfValueT and \IfValueF, the former of which was used above.

The above code assumes that an empty caption (blank fourth argument) would not need a \label (fifth) argument. If that's needed, move the \IfValueT{#5}{\label{#5}} out of the <true> branch inside \IfValueT{#4}:

\NewDocumentCommand{\figCapLab}{ O{htbp} m m o o }{%
  \begin{figure}[#1]
    \centering
    \includegraphics[width=#2\textwidth]{#3}% Set image at width
    \IfValueT{#4}{\caption{#4}}% Possible \caption
    \IfValueT{#5}{\label{#5}}% Possible \label
  \end{figure}
}

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.