我正在编写一个宏来将代码和图形并排放置。有三个相关文件:main.tex
、figure.tex
和ch1.tex
。
main.tex
大致是这样的:
...
\usepackage{listings}
...
\lstdefinestyle{base}{
...
}
\lstdefinestyle{C}{
style=base,
language=C,
}
...
\input{figure}
...
\begin{document}
...
\include{ch1}
...
\end{document}
figure.tex
定义我的宏,它有(除其他外)这个:
....
\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
最后,ch1.tex
打算这样使用这个宏:
\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
然而,Latex 却对我大喊:
./ch1.tex:643: Package keyval Error: style=C undefined.
我猜想“name=value”字符串没有被正确解析,但我不知道如何实现这一点。如果我更改该行:
\lstinputlisting[\codefig@get{code-opts}]{\codepath\codefig@get{code-file}}
为了这:
\lstinputlisting[style=C]{\codepath\codefig@get{code-file}}
它按预期工作。但是,硬编码选项“不是一个选择”因为我需要在文档的不同部分传递不同的内容。
答案1
扩展控制在这里确实是关键。您将只希望\lstinput
看到传入的值,而不是整个值\codefig@get
(因为底层keyval
解析器看不到要拆分的等号或逗号,并且认为整个值是一个键,在错误消息输入期间,该值将被扩展,因此您会看到style=C
错误)。
下面定义了另一个辅助函数\codefig@eget
,它将完全扩展\pgfkeysvalueof
使用\romannumeral
扩展(所以如果一个值有一个被吞噬的前导空格,它将继续扩展该值,直到它到达第一个不可扩展的标记 - 至少在您的使用中这不应该是一个问题,如果事实证明这是一个问题,您可以依赖实现\pgfkeysvalueof
来始终需要已知数量的扩展步骤,尽管这可能会在未来发生变化,因此可能是一个坏主意),辅助函数始终需要恰好两个步骤来扩展为值,并保护其结果免于在或\expand
上下文中进一步扩展\edef
。
我删除了\vspace
你放在figure
环境周围的 s,那些是错误的。它们figure
可能会浮动到另一个地方,在这种情况下,你会\vspace
在文档中间放置两个 s,从而导致出现奇怪的不必要的空间。如果你想控制浮动元素周围的间距,有多种方法,请搜索网站。
我还删除了周围的组figure
,而是将键解析移到其中,因为它figure
形成了自己的组,并且您不需要它外面的值。
宏\figpath
也不是个好主意,你可以改用/\graphicspath
命令。我仍然将你的留在代码中。graphics
graphicx
\figpath
\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}
替代方法:不要使用pgfkeys
但是(方便,因为可以通过...expkv-cs
直接访问各个键,而不必关心扩展),以及(用于定义具有预定义键类型的键的接口,类似于处理程序方法,尽管定义和设置在单独的命令中)。#1
#9
\ekvcSplit
expkv-def
pgfkeys
另外,我没有在所有键前面加上或作为前缀fig-
,而是code-
简单地定义了两个包装器键来设置选项。
\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}
两者的结果: