1

I am writing a macro to put code and a figure side by side. There are three relevant files: main.tex, figure.tex, and ch1.tex.

main.tex is roughly this:

...
\usepackage{listings}
...
\lstdefinestyle{base}{
...
}
\lstdefinestyle{C}{
    style=base,
    language=C,
}
...
\input{figure}
...
\begin{document}
...
\include{ch1}
...
\end{document}

figure.tex defines my macro, and it has (among other things) this:

....
\usepackage{pgfkeys}
\def\figpath{./figs/}
\def\codepath{./code/}
\makeatletter
...
% set of keys and default values for group "fig"
\pgfkeys{/codefig/entities/.cd,
  code-file/.initial=DUMMY.txt,
  code-width/.initial=0.5,
  code-opts/.initial={style=Plain},
  fig-file/.initial=DUMMY,
  fig-page/.initial=1,
  fig-width/.initial=0.5,
  fig-inner-width/.initial=0.8,
  caption/.initial={},
  label/.initial={},
}

% practical "setter" and "getter" functions
\def\codefig@set@keys#1{\pgfkeys{/codefig/entities/.cd,#1}}
\def\codefig@get#1{\pgfkeysvalueof{/codefig/entities/#1}}

\newcommand\CodeFigure[1]{%%
\bgroup%%
\codefig@set@keys{#1}%%
\vspace{\figAir}
\begin{figure}[htbp]\centering%%
\begin{minipage}[b]{\codefig@get{code-width}\textwidth}
\lstinputlisting[\codefig@get{code-opts}]{\codepath\codefig@get{code-file}}
\centering (a)
\end{minipage}%%
\begin{minipage}[b]{\codefig@get{fig-width}\textwidth}
\centering\includegraphics%%
    [width=\codefig@get{fig-inner-width}\textwidth,page=\codefig@get{fig-page}]%%
    {\figpath\codefig@get{fig-file}}\\%%
\centering (b)
\end{minipage}%%
\caption{{\codefig@get{caption}}}%%
\label{\codefig@get{label}}%%
\end{figure}%%
\vspace{\figAir}
\egroup%%
}

\makeatother

Finally, ch1.tex intends to use this macro like this:

\CodeFigure{
  code-file={my-code.c},
  code-width=0.6,
  code-opts={style=C},
  fig-file=the-fig-filename,
  fig-page=2,
  fig-width=0.4,
  fig-inner-width=0.6,
  caption={Some Nice Caption},
  label={some-label},
} %% this is line 643

However, latex yells at me saying:

./ch1.tex:643: Package keyval Error: style=C undefined.

I am guessing that the "name=value" string is not being correctly parsed, but I don't know how to achieve that. If I change the line:

\lstinputlisting[\codefig@get{code-opts}]{\codepath\codefig@get{code-file}}

for this:

\lstinputlisting[style=C]{\codepath\codefig@get{code-file}}

it works as expected. However, hardcoding the options 'is not an option' as I need to pass different ones in different parts of my document.

3
  • Please provide code we can test. But controlling expansion might help.
    – cfr
    Commented Mar 5 at 5:20
  • Try \expandafter\lstinputlisting\expandafter[\codefig@get{code-opts}]{\codepath\codefig@get{code-file}}... Commented Mar 5 at 6:20
  • @PaulGaborit a single \expandafter won't be enough, \codefig@get needs four steps of expansion...
    – Skillmon
    Commented Mar 5 at 8:15

1 Answer 1

3

Expansion control is really the key here. You'll want to have \lstinput only see the value as you passed it in, not the entire \codefig@get thing (as the underlying keyval parser then doesn't see an equals sign or comma on which to split, and thinks the entire thing is a single key, during the error message typeout the thing will get expanded, hence you see style=C as an error).

The following defines another auxiliary called \codefig@eget that will fully expand the \pgfkeysvalueof using \romannumeral-expansion (so if a value would have a leading space that would be gobbled, and it continues to expand the value until it hits the first unexpandable token -- at least in your usage this shouldn't be a problem, if it turns out to be one you could rely on the \pgfkeysvalueof implementation to always need a known number of expansion steps, though this could change in the future and hence might be a bad idea), the auxiliary always needs exactly two steps to expand to the value, and protects its result from further expanding inside an \expand or \edef context.

I've removed the \vspaces you put around your figure environment, those are just wrong. The figure might float to another place, in which case you put two \vspace in the midst of your document leading to a weird looking unwanted space. If you want to control the spacing around your floats there are different means to that, search the site.

I also removed the group around the figure and instead moved the key-parsing inside it, as the figure forms a group of its own and you don't need the values outside of it.

The \figpath macro is also a bad idea, you could use the \graphicspath command of graphics/graphicx instead. I still left your \figpath in the code.

\documentclass{article}

\usepackage{listings}
\usepackage{graphicx}
\usepackage{pgfkeys}
\def\figpath{}
\def\codepath{./}

\makeatletter
% set of keys and default values for group "fig"
\pgfkeys{/codefig/entities/.cd,
  code-file/.initial=DUMMY.txt,
  code-width/.initial=0.5,
  code-opts/.initial={style=Plain},
  fig-file/.initial=DUMMY,
  fig-page/.initial=1,
  fig-width/.initial=0.5,
  fig-inner-width/.initial=0.8,
  caption/.initial={},
  label/.initial={},
}

% practical "setter" and "getter" functions
\def\codefig@set@keys#1{\pgfkeys{/codefig/entities/.cd,#1}}
\def\codefig@get#1{\pgfkeysvalueof{/codefig/entities/#1}}
\def\codefig@eget#1%
  {%
    \unexpanded\expandafter
      {\romannumeral`\^^@\pgfkeysvalueof{/codefig/entities/#1}}%
  }

\newcommand\CodeFigure[1]
  {%
    \begin{figure}[htbp]%
      \codefig@set@keys{#1}%
      \centering
      \begin{minipage}[b]{\codefig@get{code-width}\textwidth}%
        \expanded{\noexpand\lstinputlisting[{\codefig@eget{code-opts}}]}%
          {\codepath\codefig@get{code-file}}
        \centering (a)
      \end{minipage}%
      \begin{minipage}[b]{\codefig@get{fig-width}\textwidth}%
        \centering
        \includegraphics
          [{width=\codefig@get{fig-inner-width}\textwidth,page=\codefig@get{fig-page}}]%
          {\figpath\codefig@get{fig-file}}\\
        (b)
      \end{minipage}%%
      \caption{\codefig@get{caption}\label{\codefig@get{label}}}%
    \end{figure}%%
  }
\makeatother

\begin{document}
\CodeFigure{
  code-file={\jobname.tex},
  code-width=0.6,
  code-opts={language=[LaTeX]TeX,firstline=1,lastline=4},
  fig-file=example-image-duck,
  fig-page=2,
  fig-width=0.4,
  fig-inner-width=0.6,
  caption={Some Nice Caption},
  label={some-label},
}
\end{document}

Alternative approach: Don't use pgfkeys but expkv-cs (handy, because the individual keys can be directly accessed via #1...#9 in \ekvcSplit and you don't have to care about expansion), and expkv-def (interface to define keys with predefined key types, similar to pgfkeys handler approach, though defining and setting are in separate commands).

Also, instead of prefixing all the keys with fig- or code- I simply defined two wrapper keys to set the options instead.

\documentclass{article}

\usepackage{listings}
\usepackage{graphicx}
\usepackage{expkv-cs,expkv-def}
\def\figpath{}
\def\codepath{./}

\makeatletter
\ekvcSplit\codefig@code
  {
     file  = DUMMY.txt
    ,width = .5
    ,opts  = style=Plain
  }
  {%
    \begin{minipage}[b]{#2\textwidth}%
      \lstinputlisting[{#3}]{\codepath#1}
      \centering (a)
    \end{minipage}%
  }
\ekvcSplit\codefig@fig
  {
     file  = DUMMY
    ,page  = 1
    ,width = .5
    ,inner-width = .8
  }
  {%
    \begin{minipage}[b]{#3\textwidth}%
      \centering
      \includegraphics
        [{width=#4\textwidth,page=#2}]%
        {\figpath#1}\\
      (b)
    \end{minipage}%%
  }

\ekvsetdef\codefig@set@keys{codefig@keys}
\ekvdefinekeys{codefig@keys}
  {%
     dataT label   = \codefig@label
    ,dataT caption = \codefig@caption
    ,data  code    = \codefig@codekeys
    ,data  fig     = \codefig@figkeys
  }
\newcommand\codefig@caption@aux[1]{\caption{#1\codefig@label\label}}

\newcommand\CodeFigure[1]
  {%
    \begin{figure}[htbp]%
      \centering
      \codefig@set@keys{#1}%
      \codefig@codekeys\codefig@code{\codefig@code{}}%
      \codefig@figkeys\codefig@fig{\codefig@fig{}}%
      \codefig@caption\codefig@caption@aux
    \end{figure}%%
  }
\makeatother

\begin{document}
\CodeFigure{
  code = {
    file={\jobname.tex},
    width=0.6,
    opts={language=[LaTeX]TeX,firstline=1,lastline=4},
  },
  fig = {
    file=example-image-duck,
    page=2,
    width=0.4,
    inner-width=0.6,
  },
  caption={Some Nice Caption},
  label={some-label},
}
\end{document}

Result of both:

enter image description here

1
  • Wow, what a thorough explanation, it works perfectly on my computer.
    – onlycparra
    Commented Mar 5 at 23:46

You must log in to answer this question.

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