我有一个环境,其中部分内容根据传递给环境的选项进行有条件排版。此环境的内容在其他地方已预定义,因此根据选项会产生不同的输出。这部分代码运行良好。
现在,我尝试在环境结束时报告错误,如果指定了无法识别的选项来检测任何可能被忽视的拼写错误,并且遇到了看起来像是扩展问题的问题。我尝试了所有可能对我有意义的\edef
和用法\expandafter
,但没有找到正确的组合。
我这样做是为了存储所有指定选项的列表,最后检查\<optionName>Encountered
每个指定选项的控制序列是否已定义。在环境主体中,我定义:
\global\expandafter\def\csname <optionName> Encountered\endcsname{}%
对于<optionName>
可能已经指定的每个可能,并根据指定的选项列表进行检查。
笔记:
- 选项列表中还有其他选项不适用此类检查,因此我需要将它们单独添加到列表中,而不是仅仅存储
#1
并\foreach
在最后对该值执行循环。 - 我希望选项本身存储在列表中(而不是它们对应的 csname),因为这样我可以在环境结束时更好地报告错误消息。
- 此外,也可以在宏中定义选项。
参考:
预期输出:
当代码\ReportAnyUnusedOptions
取消注释并且一切正常时,应该会产生:
代码:
问题代码\ReportAnyUnusedOptions
已被注释,以允许编译。
\documentclass{article}
\usepackage{pgffor}
\def\ListOfOptions{}
\makeatletter
\newcommand{\AddToListOfOptions}[1]{%
\g@addto@macro\ListOfOptions{#1}%
\typeout{***** Added option to check: #1}%
}
\makeatother
\newcommand{\ReportAnyUnusedOptions}{%
Checking options: \par%
\foreach \y in \ListOfOptions {%
% \y%
% \ifcsname\y Encountered\endcsname%
% ~Encountered!\par%
% \else%
% ~Not Encountered!\par%
% \fi%
}%
}%
\newenvironment{ApplyOptions}[1]{%
% Add to list of options to later check that they were used
\foreach \x in {#1} {%
% There are cases here where an option would not be
% check later, so some will be skipped
\edef\Option{\x}%
\AddToListOfOptions{\Option}%
}%
}{%
\ReportAnyUnusedOptions%
}
\begin{document}
\begin{ApplyOptions}{Option A, Option C}%
% Option A:
% if "Option A" was specified, it would be typset here
\global\expandafter\def\csname Option A Encountered\endcsname{}%
%
% Option B:
% if "Option B" was specified, it would be typset here
\global\expandafter\def\csname Option B Encountered\endcsname{}%
\end{ApplyOptions}
% Also possible to specify the options via a macro
\newcommand*{\MyOptions}{Option AA, Option CC}%
\begin{ApplyOptions}{\MyOptions}%
% Option A:
% if "Option AA" was specified, it would be typset here
\global\expandafter\def\csname Option AA Encountered\endcsname{}%
%
% Option B:
% if "Option BB" was specified, it would be typset here
\global\expandafter\def\csname Option BB Encountered\endcsname{}%
\end{ApplyOptions}
\end{document}
答案1
这是一个expl3
版本:
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\seq_new:N \g_optchk_options_seq
\cs_new_protected:Npn \optchk_report:
{
Checking~options: \par
\seq_map_inline:Nn \g_optchk_options_seq
{
##1 ~ \cs_if_exist:cF { ##1 ~ Encountered } {~ Not} ~ Encountered \par
}
}
\cs_new_protected:Npn \optchk_add:n #1
{
\seq_gclear:N \g_optchk_options_seq
% Add to list of options to later check that they were used
\seq_gset_split:Nno \g_optchk_options_seq { , } { #1 }
\seq_map_inline:Nn \g_optchk_options_seq
{ \typeout{*****~Added~option~to~check:~##1} }
}
\cs_generate_variant:Nn \seq_gset_split:Nnn {Nno}
\NewDocumentEnvironment{ApplyOptions}{ m }
{ \optchk_add:n { #1 } }
{ \optchk_report: }
\ExplSyntaxOff
\begin{document}
\begin{ApplyOptions}{Option A, Option C}%
% Option A:
% if "Option A" was specified, it would be typset here
\expandafter\gdef\csname Option A Encountered\endcsname{}%
%
% Option B:
% if "Option B" was specified, it would be typset here
\expandafter\gdef\csname Option B Encountered\endcsname{}%
\end{ApplyOptions}
\end{document}
该方法的局限性\foreach
在于,所有循环都在不同的组中执行。使用序列(或列表)的方法允许在需要时选择全局分配,并使管理更加容易。
这里我们定义了环境以将指定的选项添加到序列中(并且我们没有定界问题)。环境的“结束部分”使用\ReportAnyUnusedOptions
不同的名称。
注意:我已将\seq_gset_split:Nnn
其改为\seq_gset_split:Nno
,以便传递给环境的参数被扩展一次。根据选项的性质,人们可能更喜欢使用\seq_gset_split:Nnx
(生成适当的变体),但如果选项本身包含宏,这可能是危险的。
答案2
您的选项列表并不像您想象的那样。在两个选项之后,它被定义为
\ListOfOptions=macro:
->\Option \Option .
即,它仅告诉您选项的数量,而不是选项的数量。
所以改变它
> \ListOfOptions=macro:
->\Option A Encountered \Option C Encountered .
然后:
\documentclass{article}
\usepackage{pgffor}
\def\ListOfOptions{}
\makeatletter
\newcommand{\AddToListOfOptions}[1]{%
\expandafter\g@addto@macro\expandafter\ListOfOptions \csname #1 Encountered\endcsname
\typeout{***** Added option to check: #1}%
}
\def\foo#1>{}
\newcommand{\ReportAnyUnusedOptions}{%
Checking options: \par%
\def\x{\@tfor\y:=}%
\expandafter\x\ListOfOptions\do{%
\expandafter\show\y\relax
\expandafter\ifx\y\@undefined
\expandafter\foo\meaning\y!\par%
\else%
NOT \expandafter\foo\meaning\y!\par%
\fi}%
}%
\newenvironment{ApplyOptions}[1]{%
% Add to list of options to later check that they were used
\foreach \x in {#1} {%
% There are cases here where an option would not be
% check later, so some will be skipped
\edef\Option{\x}%
\AddToListOfOptions{\Option}%
}%
}{%
\ReportAnyUnusedOptions%
}
\makeatother
\begin{document}
\begin{ApplyOptions}{Option A, Option C}%
% Option A:
% if "Option A" was specified, it would be typset here
\global\expandafter\def\csname Option A Encountered\endcsname{}%
%
% Option B:
% if "Option B" was specified, it would be typset here
\global\expandafter\def\csname Option B Encountered\endcsname{}%
\end{ApplyOptions}
\end{document}
答案3
我想让 Peter 在每个环境调用中节省一个完整的循环。这就是我发布此解决方案的原因。此外,\cptdocommalist
规范化列表。
\documentclass{article}
\usepackage{catoptions}
\makeatletter
\newcommand{\optionsnotused}{%
Checking options:
\def\do##1##2{\endgraf{\tt % You can remove \tt if you like.
\ifdefTF{##2}{%
Option `##1' used: \expandafter\strip@prefix\meaning##2%
\undefcs{##2}% remove any \gdef'ed temporary commands.
}{%
Option `##1' not used%
}%
}}%
\listofoptions
}
\newenvironment{applyoptions}[1]{%
% \listofoptions is re-initialized by the environment. I am not sure
% if this is what Peter intended. If not, take '\def\listofoptions{}'
% outside the environment definition:
\def\listofoptions{}%
\ifmacroTF{#1}{\let\cpt@tempa#1}{\def\cpt@tempa{#1}}%
\cptdocommalist*\cpt@tempa{%
\edef\option{##1}% Does Peter really need to define \option here?
\xdef\listofoptions{\expandcsonce\listofoptions
\unexpanded{\do{##1}}{\noexpandcsn{##1-used}}}%
\typeout{Option '\detokenize{##1}' added, to check at end of environment.}%
}%
}{%
\optionsnotused
\ignorespacesafterend
}
\makeatother
\begin{document}
\begin{applyoptions}{Option A, Option B}
% If 'Option A' was specified, it would be typeset here.
% Let us simulate its use here:
\csngdef*{Option A-used}{Used here by Peter Gill}%
% Let's assume that 'Option B' wasn't used here.
\end{applyoptions}
\end{document}