跟随上一个问题作为我的练习,我尝试使用 l3keys 和 lthooks 模仿 thmtools 与 amsthm 的行为。现在我被困在钩子部分。thmtools 为每个定理环境和通用(应用于所有定理)定义了 prehead、posthead、prefoot 和 postfoot 钩子。prehead 和 postfoot 钩子很简单。对于 posthead 钩子,我发现应该在 amsthm 命令之后添加代码\deferred@thm@head
。(或者也许是\@begintheorem
?区别在于\ignorespaces
。)
对于通用钩子,只需执行
\NewHook{ amsthm-keys/allthms/posthead }
\AddToHook { cmd/deferred@thm@head/after } { \UseHook { amsthm-keys/allthms/posthead } }
但对于本地钩子,我只想在\deferred@thm@head
特定环境之后添加代码。据我所知,添加钩子始终是全局的,因此我不知道如何在不更改定义的情况下做到这一点\deferred@thm@head
。
以下是 MWE:
\documentclass{article}
\usepackage{amsthm,kantlipsum,tcolorbox}
\ExplSyntaxOn
\keys_define:nn { mbert/thm }
{
name .tl_set:N = \l_mbert_thm_name_tl,
preheadhook .tl_set:N = \l_mbert_thm_preheadhook_tl,
postheadhook .tl_set:N = \l_mbert_thm_postheadhook_tl,
prefoothook .tl_set:N = \l_mbert_thm_prefoothook_tl,
postfoothook .tl_set:N = \l_mbert_thm_postfoothook_tl,
}
\tl_new:N \l_mbert_thm_defaultkeys_tl
\keys_precompile:nnN { mbert/thm }
{
name = \text_titlecase:n { \l_mbert_thm_envname_tl },
preheadhook = {},
postheadhook = {},
prefoothook = {},
postfoothook = {},
}
\l_mbert_thm_defaultkeys_tl
\NewHook{ amsthm-keys/allthms/prehead }
\NewHook{ amsthm-keys/allthms/posthead }
\NewHook{ amsthm-keys/allthms/prefoot }
\NewHook{ amsthm-keys/allthms/postfoot }
\AddToHook { cmd/deferred@thm@head/after } { \UseHook { amsthm-keys/allthms/posthead } }
% How to hook into \deferred@thm@head locally?
\NewDocumentCommand { \NewThm } { m O{} }
{
\tl_set:Nn \l_mbert_thm_envname_tl { #1 }
\tl_use:N \l_mbert_thm_defaultkeys_tl
\keys_set:nn { mbert/thm } { #2 }
\exp_args:NnV \AddToHook { env/#1/begin } \l_mbert_thm_preheadhook_tl % local prehead hook
\AddToHook { env/#1/begin } { \UseHook { amsthm-keys/allthms/prehead } } % generic prehead hook
\AddToHook { env/#1/end } { \UseHook { amsthm-keys/allthms/postfoot } } % generic postfoot hook
\exp_args:NnV \AddToHook { env/#1/end } \l_mbert_thm_postfoothook_tl % local postfoot hook
\mbert_thm_new:ne { #1 } { \l_mbert_thm_name_tl }
}
\cs_new_eq:NN \mbert_thm_new:nn \newtheorem
\cs_generate_variant:Nn \mbert_thm_new:nn { ne }
\ExplSyntaxOff
\NewThm{theorem}[
preheadhook=\begin{tcolorbox},
postfoothook=\end{tcolorbox}
]
\NewThm{cor}[
name=Corollary,
postheadhook=ABC
]
\NewThm{lemma}
\AddToHook{amsthm-keys/allthms/posthead}{***}
\AddToHook{amsthm-keys/allthms/postfoot}{END THEOREM}
\begin{document}
\begin{theorem}
\kant[2][1]
\end{theorem}
\begin{cor}
\kant[2][1]
\end{cor}
\begin{lemma}
\kant[2][1]
\end{lemma}
\end{document}
我显然不能添加类似
\exp_args:NnV \AddToHook { cmd/deferred@thm@head/after } \l_mbert_thm_postheadhook_tl
到 的定义\NewThm
,则 的每个声明postheadhook=
都会将代码添加到\deferred@thm@head
。
本质上我想要的效果
\AddToHook{env/theorem/begin}{\apptocmd{\deferred@thm@head}{CODE}{}{}}
但使用内核钩子。从这个答案,我知道没有直接的方法可以获取的行为\apptocmd
。是否有关于本地挂接其他软件包的命令的一般建议?
额外尝试
\AddToHookNext{cmd/deferred@thm@head/after}
在评论中@cfr的帮助下,嵌套的想法\AddToHook{env/<envname>/begin}
看起来很有希望。事实上,添加例如
\AddToHook { env/cor/begin } { \AddToHookNext { cmd/deferred@thm@head/after } {HHH} }
达到了预期的效果。但是,当我尝试在 的定义中自动执行此操作时,在为 赋值\NewThm
时什么也没有发生:postheadhook
\NewDocumentCommand { \NewThm } { m O{} }
{
\tl_set:Nn \l_mbert_thm_envname_tl { #1 }
\tl_use:N \l_mbert_thm_defaultkeys_tl
\keys_set:nn { mbert/thm } { #2 }
\exp_args:NnV \AddToHook { env/#1/begin } \l_mbert_thm_preheadhook_tl % local prehead hook
\AddToHook { env/#1/begin } { \UseHook { amsthm-keys/allthms/prehead } } % generic prehead hook
%%% This next line is new.
\AddToHook { env/#1/begin } { \exp_args:NnV \AddToHookNext { cmd/deferred@thm@head/after } \l_mbert_thm_postheadhook_tl }
\AddToHook { env/#1/end } { \UseHook { amsthm-keys/allthms/postfoot } } % generic postfoot hook
\exp_args:NnV \AddToHook { env/#1/end } \l_mbert_thm_postfoothook_tl % local postfoot hook
\mbert_thm_new:ne { #1 } { \l_mbert_thm_name_tl }
}
这可能是一个扩展问题吗?
又一次尝试
我不明白为什么,但如果我在添加的行前面加上前缀,上述尝试似乎有效\exp_args:Nnf
,如下所示
\exp_args:Nnf \AddToHook { env/#1/begin } { \exp_args:NnV \AddToHookNext { cmd/deferred@thm@head/after } \l_mbert_thm_postheadhook_tl }
postheadhook
o 型扩展不执行任何操作,如果包含纯文本以外的内容,e 型扩展会产生错误。只是幸运,还是这就是使用 f 型扩展的方法?
答案1
但对于本地钩子,我只想在
\deferred@thm@head
特定环境之后添加代码。据我所知,添加钩子始终是全局的,因此我不知道如何在不改变定义的情况下做到这一点\deferred@thm@head
[…]
本质上我想要的效果
\AddToHook{env/theorem/begin}{\apptocmd{\deferred@thm@head}{CODE}{}{}}
但使用内核钩子。
这里有 5 种不同的选择:
\documentclass{article}
\pagestyle{empty}
\makeatletter \ExplSyntaxOn
\def\pretext#1{before <#1>}
\def\posttext#1{after <#1>}
\NewDocumentEnvironment
{ one }
{ }
{ \pretext{one} }
{ \posttext{one} }
\NewDocumentEnvironment
{ two }
{ }
{ \pretext{two} }
{ \posttext{two} }
%%%%%%%%%%%%%%%%
%%% Option 1 %%%
%%%%%%%%%%%%%%%%
%% Use \globaldefs to trick \hook_gput_code into actually behaving like a
%% hypothetical \hook_put_code. This is a complete hack and wildly
%% unsupported, so PLEASE DON'T ACTUALLY DO THIS.
%
% \hook_gput_code:nnn { env / two / begin } { . } {
% \PackageWarning{BAD~ IDEA!}{PLEASE~ DON'T~ ACTUALLY~ DO~ THIS!}
% \int_set:Nn \globaldefs { -1 }
% \hook_gput_code:nnn { cmd / pretext / after } { . } { \textbf { NEW } }
% \int_set:Nn \globaldefs { 0 }
% }
%%% End Option 1
%%%%%%%%%%%%%%%%
%%% Option 2 %%%
%%%%%%%%%%%%%%%%
%% Add the patch using a "cmd/..." hook at the beginning of the
%% environment, and remove it at the end.
%
% \hook_gput_code:nnn { env / two / begin } { . } {
% \hook_gput_code:nnn { cmd / pretext / after } { . } { \textbf { NEW } }
% }
%
% \hook_gput_code:nnn { env / two / end } { . } {
% \hook_gremove_code:nn { cmd / pretext / after } { . }
% }
%%% End Option 2
%%%%%%%%%%%%%%%%
%%% Option 3 %%%
%%%%%%%%%%%%%%%%
%% Add a one-time "cmd/..." hook at the beginning of the environment.
%
% \hook_gput_code:nnn { env / two / begin } { . } {
% \hook_gput_next_code:nn { cmd / pretext / after } { \textbf { NEW } }
% }
%%% End Option 3
%%%%%%%%%%%%%%%%
%%% Option 4 %%%
%%%%%%%%%%%%%%%%
%% Locally patch the command by directly using expl3 commands.
%
% \cs_generate_variant:Nn \cs_set:Npn { NpV }
%
% \cctab_const:Nn \c_package_cctab {
% \cctab_select:N \c_document_cctab
% \char_set_catcode_letter:N @
% }
%
% \hook_gput_code:nnn { env / two / begin } { . } {
% \tl_set_rescan:Nnx
% \l_tmpa_tl
% { \cctab_select:N \c_package_cctab }
% { \cs_replacement_spec:N \pretext }
%
% \tl_put_right:Nn \l_tmpa_tl { \textbf { NEW } }
% \cs_set:NpV \pretext #1 { \l_tmpa_tl }
% }
%%% End Option 4
%%%%%%%%%%%%%%%%
%%% Option 5 %%%
%%%%%%%%%%%%%%%%
%% Globally patch the command, but make the patch value depend on the current
%% environment.
%
% \prop_new:N \g__example_aftertext_prop
%
% \hook_gput_code:nnn { cmd / pretext / after } { . } {
% \prop_item:NV \g__example_aftertext_prop \@currenvir
% }
%
% \prop_gput:Nnn \g__example_aftertext_prop { two } { \textbf { NEW } }
%%% End Option 5
\ExplSyntaxOff \makeatother
\begin{document}
\begin{one}
\emph{body}
\end{one}
\begin{two}
\emph{body}
\end{two}
\begin{one}
\emph{body}
\end{one}
\begin{two}
\emph{body}
\end{two}
\end{document}
我不明白为什么,但如果我在添加的行前面加上前缀,上述尝试似乎有效
\exp_args:Nnf
,如下所示\exp_args:Nnf \AddToHook { env/#1/begin } { \exp_args:NnV \AddToHookNext { cmd/deferred@thm@head/after } \l_mbert_thm_postheadhook_tl }
postheadhook
o 型扩展不执行任何操作,如果包含纯文本以外的内容,e 型扩展会产生错误。只是幸运,还是这就是使用 f 型扩展的方法?
e
如果postheadhook
包含任何易碎内容,-type 扩展将会失败。o
-type 扩展不起作用,因为它只扩展一次,并且\exp_args:NnV
需要更多的扩展才能起作用。
f
类型扩展在 99% 的情况下都不合适,但在这里似乎没问题。我会轻微地更喜欢
\exp_args:Nnf \AddToHook { env/#1/begin } {
% v vvvvvvvvvvvv
\exp_args:NNnV \exp_stop_f: \AddToHookNext { cmd/deferred@thm@head/after }
\l_mbert_thm_postheadhook_tl
}
以防万一\AddToHookNext
在扩展时爆炸,但它(和大多数其他 LaTeX 命令)受到保护,所以在这种情况下没有什么区别。
(最好的情况是类似这样的情况\hook_gput_code:nne … \hook_gput_next_code:ne … \exp_not:V \l_mbert_thm_postheadhook_tl
,但由于某些未知原因,这会引发错误。)