0

I am trying to write a command using xparse and xkeyval. The goal is to write a command that can take optionnal key-value arguments to make things easier and more readable for my users who have just been forced to switch to LaTeX for almost everything.

As an example I'll use this command that should display an image. Note that the caption for the image is entirely optional and the width of the image is optional with a fallback default.

This is where I am at so far:

\documentclass{article}

\usepackage{graphicx}
\usepackage{xparse}
\usepackage{xkeyval}
\usepackage{mwe}

\makeatletter
\define@key{test}{caption}{\def\captiontext{#1}}
\define@key{test}{width}{\def\width{#1}}
\makeatother
\NewDocumentCommand{\displayImage}{o m}{
  \begingroup
    \setkeys{test}{caption={},width={0.5\textwidth}, #1}
    \begin{figure}[h]
      \displayImage[width=\width]{#2}
      \IfValueT{\captiontext}{\caption{\captiontext}}
    \end{figure}
  \endgroup
}

\begin{document}
  \displayImage{example-image}
\end{document}

This does not even compile. And I don't understand the error code as to why it doesn't compile.

Despite extensive research on the internet and reading the official documentation for both packages I am no closer to making this work.

Any help to show me how to make it work and explain the solution for very near future uses would be very appreciated.

Note: I am open to using other packages that would help me accomplish this goal.

3
  • 1
    The \IfValueT test isn't meant for testing any token list whether it's empty or no. That test is only meant to check whether a value was provided to one of the optional argument types of xparse itself, so \IfValueT{\captiontext} doesn't do what you think.
    – Skillmon
    Commented Aug 31, 2021 at 14:45
  • @Skillmon Thanks for the reply! Yes I expected as much but as I did not find a better solution so I chose to include it in this post in the hope that someone might find a way to test for the presence of a value. Developer by trade I think I might be expecting too much from LaTeX in that regard.
    – alex_bits
    Commented Aug 31, 2021 at 14:48
  • EDIT: Looks like using expl3 I might be able to do more complexe things than I thought!
    – alex_bits
    Commented Aug 31, 2021 at 15:01

2 Answers 2

2

\IfValueT is meaningful only in combination with optional argument specified with o, not for testing whether something has a value. And you obviously want \includegraphics in the definition text, not again \displayImage.

I wouldn't use xkeyval: expl3 has better options. I wouldn't use this approach either, as I see no real advantage, but it's your document…

\documentclass{article}

\usepackage{graphicx}
%\usepackage{xparse} % not needed for LaTeX 2020-10-01 or later

\ExplSyntaxOn

\keys_define:nn { alexbits/figure }
 {
  width .dim_set:N = \l__alexbits_figure_width_dim,
  caption .tl_set:N = \l__alexbits_figure_caption_tl,
 }

\NewDocumentCommand{\displayImage}{O{} m}
 {
  \group_begin:
  \keys_set:nn { alexbits/figure }
   {
    width=0.5\textwidth, #1
   }
    \begin{figure}[htp] % not just h
    \centering
    \includegraphics[width=\l__alexbits_figure_width_dim]{#2}
    \tl_if_empty:NF \l__alexbits_figure_caption_tl
     {
      \caption{\l__alexbits_figure_caption_tl}
     }
    \end{figure}
  \group_end:
}

\ExplSyntaxOff

\begin{document}

\displayImage{example-image}

\displayImage[width=3cm,caption={Some caption text}]{example-image}

\end{document}

enter image description here

1
  • Thank for your answer. It does solve my problem. I understand why you don't see the use of this macro. It was more of an example as to how to get the logic to work in general. It will be used in more complex macros for production. The goal is to simplify complex common use cases for people who are not particularly tech-savy. Now I need to go read expl3 documentation!
    – alex_bits
    Commented Aug 31, 2021 at 14:59
1

The following implements this using the expkv-def package (disclaimer: I'm the author of it). That package has the (imho) handy key types data and dataT. Those incorporate the tests whether the key has been used or not and the storage of the value in a single macro. For example, after

\ekvdefinekeys{foo}{data d=\foo@d, dataT t=\foo@t}

The macro \foo@d will be defined to take the arguments \foo@d{<used>}{<not-used>} and if the key d has been used it will expand to <used>{<data>} (with <data> being the value it got when it was used), and if it wasn't used will expand to <not-used>. And the macro \foo@t will be defined to take the argument \foo@t{<used>}, and if it was used will expand to <used>{<data>}, if it wasn't used it will just expand to nothing.

The following would define your macro \displayImage to know the keys caption, label, loc, and width. Every key it doesn't know it'll just forward to \includegraphics assuming that it is a key meant for that.

\documentclass{article}

\usepackage{expkv-def}
\usepackage[]{graphicx}

\makeatletter
\newcommand*\dI@unknown{}
\ekvdefinekeys{displayImage}
  {
     dataT   caption = \dI@caption
    ,data    label   = \dI@label
    ,data    loc     = \dI@loc
    ,store   width   = \dI@width
    ,initial width   = .5\linewidth
    % forward all unknown keys to \includegraphics
    ,protected unknown code  =
      {\edef\dI@unknown{\unexpanded\expandafter{\dI@unknown,#1= {#2}}}}
    ,protected unknown noval =
      {\edef\dI@unknown{\unexpanded\expandafter{\dI@unknown,#1}}}
  }
\newcommand\dI@figure[1]{\begin{figure}[{#1}]}
\newcommand\dI@capWlabel[2]{\caption{#2\label{#1}}}
\newcommand\displayImage[2][]
  {%
    \begingroup
      \ekvset{displayImage}{#1}%
      \dI@loc\dI@figure{\begin{figure}}%
        \centering
        \expanded
          {%
            \noexpand\includegraphics
              [{width=\noexpand\dI@width,\unexpanded\expandafter{\dI@unknown}}]%
          }%
          {#2}%
        \dI@caption{\dI@label\dI@capWlabel\caption}%
      \end{figure}%
    \endgroup
  }
\makeatother

\begin{document}
Abc, but see also fig.~\ref{fig:ex}.
\displayImage[caption={An example},label=fig:ex]{example-image}
\displayImage[height=2cm,keepaspectratio,caption=A duck]{example-image-duck}
\displayImage[width=2cm,loc=bp]{example-image-duck-portrait}
\end{document}

enter image description here

You must log in to answer this question.

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