作为一个练习,我试图以 的方式创建一个 keyval 接口amsthm
,\newtheoremstyle
但thmtools
使用内核的\DeclareKeys
而不是keyval
和kvsetkeys
。 的每个参数都\newtheoremstyle
可以轻松存储和检索,除了包含宏参数的最终“head spec”参数#
。
以下是不起作用但无错误的示例:
\documentclass{article}
\usepackage{amsthm,kantlipsum}
\DeclareKeys[thmstyle]{
spaceabove .store = \thmstylespaceabove,
spaceabove .initial:n = \topsep,
spacebelow .store = \thmstylespacebelow,
spacebelow .initial:n = \topsep,
bodyfont .store = \thmstylebodyfont,
bodyfont .initial:n = \itshape,
headindent .store = \thmstyleheadindent,
headindent .initial:n = 0pt,
headfont .store = \thmstyleheadfont,
headfont .initial:n = \bfseries,
headpunct .store = \thmstyleheadpunct,
headpunct .initial:n = {.},
postheadspace .store = \thmstylepostheadspace,
postheadspace .initial:n = 5pt plus 1pt minus 1pt,
headstyle .code = \def\thmstyleheadcmd##1##2##3{#1},
% doubling the # below does not work either
headstyle .initial:n = {\thmname{#1}\thmnumber{ #2}\thmnote{ #3}},
}
\newcommand{\NewThmStyle}[2]{
\begingroup
\SetKeys[thmstyle]{#2}
\newtheoremstyle{#1}
{\thmstylespaceabove}
{\thmstylespacebelow}
{\thmstylebodyfont}
{\thmstyleheadindent}
{\thmstyleheadfont}
{\thmstyleheadpunct}
{\thmstylepostheadspace}
{\thmstyleheadcmd{##1}{##2}{##3}}
\endgroup
}
\NewThmStyle{teststyle}{}
\newtheorem{theorem}{Theorem}
\theoremstyle{teststyle}
\newtheorem{test}{Test}
\begin{document}
\begin{theorem}
\kant[2][1]
\end{theorem}
\begin{test}
\kant[2][1]
\end{test}
\end{document}
我尝试定义一个\thmstyleheadcmd
带有三个参数的命令,将其插入到 的最后一个参数中\newtheoremstyle
,但它似乎被忽略了。然而,它并没有被视为空,因为对于 amsthm 来说,这应该意味着“默认”,但你可以看到名称和数字之间没有空格。
上述尝试是在这次更简单的尝试之后进行的:
headstyle .store = \thmstyleheadstyle,
headstyle .initial:n = {\thmname{#1}\thmnumber{ #2}\thmnote{ #3}},
并在 的定义\thmstyleheadcmd{##1}{##2}{##3}
中用替换。这与上面的结果相同。\thmstyleheadstyle
\NewThmStyle
amsthm
定义中的相关代码块\newtheoremstyle
是
\@ifempty{#9}{%
\let\thmhead\thmhead@plain
}{%
\@namedef{thmhead@#1}##1##2##3{#9}%
\@temptokena\@xp{\the\@temptokena
\@xp\let\@xp\thmhead\csname thmhead@#1\endcsname}%
}%
这让我觉得可能需要增加的数量#
,但我无法让任何变化发挥作用。有没有办法使用将的值传递headstyle
给\newtheoremstyle
“逐字” l3keys
?还是我遗漏了一些显而易见的东西?
答案1
您不能用来\begingroup
保留键的初始值,因为这会使新的定理样式在\endgroup
找到后立即变得未定义。
\documentclass{article}
\usepackage{amsthm,kantlipsum}
\newcommand{\thmstyleheadcmd}{}% initialize
\ExplSyntaxOn
\keys_define:nn { mbert/thmstyle }
{
spaceabove .tl_set:N = \l_mbert_thmstyle_spaceabove_tl,
spacebelow .tl_set:N = \l_mbert_thmstyle_spacebelow_tl,
bodyfont .tl_set:N = \l_mbert_thmstyle_bodyfont_tl,
headindent .tl_set:N = \l_mbert_thmstyle_headindent_tl,
headfont .tl_set:N = \l_mbert_thmstyle_headfont_tl,
headpunct .tl_set:N = \l_mbert_thmstyle_headpunct_tl,
postheadspace .tl_set:N = \l_mbert_thmstyle_postheadspace_tl,
headstyle .code = \cs_set:Nn \mbert_thmstyle_headcmd:nnn{#1},
}
\tl_new:N \l_mbert_thmstyle_default_tl
\keys_precompile:nnN { mbert/thmstyle }
{
spaceabove = \topsep,
spacebelow = \topsep,
bodyfont = \itshape,
headindent = 0pt,
headfont = \bfseries,
headpunct = {.},
postheadspace = 5pt plus 1pt minus 1pt,
headstyle = \mbert_thmstyle_name:n{#1}\mbert_thmstyle_number:n{~#2}\mbert_thmstyle_note:n{~#3},
}
\l_mbert_thmstyle_default_tl
\cs_new_protected:Nn \mbert_thmstyle_name:n { \thmname{#1} }
\cs_new_protected:Nn \mbert_thmstyle_number:n { \thmnumber{#1} }
\cs_new_protected:Nn \mbert_thmstyle_note:n { \thmnote{#1} }
\NewDocumentCommand{\NewThmStyle}{mm}
{
\tl_use:N \l_mbert_thmstyle_default_tl
\keys_set:nn { mbert/thmstyle } {#2}
\mbert_thmstyle_new:nVVVVVVVe{#1}
\l_mbert_thmstyle_spaceabove_tl
\l_mbert_thmstyle_spacebelow_tl
\l_mbert_thmstyle_bodyfont_tl
\l_mbert_thmstyle_headindent_tl
\l_mbert_thmstyle_headfont_tl
\l_mbert_thmstyle_headpunct_tl
\l_mbert_thmstyle_postheadspace_tl
{\mbert_thmstyle_headcmd:nnn{##1}{##2}{##3}}
}
\cs_new_eq:NN \mbert_thmstyle_new:nnnnnnnnn \newtheoremstyle
\cs_generate_variant:Nn \mbert_thmstyle_new:nnnnnnnnn { nVVVVVVVe }
\ExplSyntaxOff
\NewThmStyle{teststyle}{
spaceabove=20pt,
headfont=\normalfont,
bodyfont=\bfseries\itshape,
headstyle={#2 -- #1}
}
\NewThmStyle{foo}{}
\newtheorem{theorem}{Theorem}
\theoremstyle{teststyle}
\newtheorem{test}{Test}
\theoremstyle{foo}
\newtheorem{testnormal}{Testnormal}
\begin{document}
\begin{theorem}
\kant[2][1]
\end{theorem}
\begin{test}
\kant[2][1]
\end{test}
\begin{testnormal}
\kant[2][1]
\end{testnormal}
\end{document}
答案2
如果你对 TeX 底层的编程范式不太熟悉,我会尝试给出一个粗略的概述:
当 TeX 处理 .tex 输入文件时,该文件的内容将被视为一组指令,用于创建所谓的标记并将其附加到标记流。
例如,如果 .tex-input-file 包含This is a \macro{with argument} in \LaTeX.
,TeX 会根据需要逐渐形成以下标记,以便为进一步处理做好准备:
T
类别 11(字母)的显式字符标记:类别 11(字母)的 显式字符标记 :类别 11(字母)的显式字符标记:类别 11(字母)的 显式字符标记: 类别 10(空格)的显式空格字符标记 -表示空格,即,在 ASCIII 和 unicode 中代码点编号均为 32 的字符:类别 11(字母)的 显式字符标记:类别 11(字母)的 显式字符标记: 类别 10(空格)的显式空格字符标记:类别 11(字母)的 显式字符标记:类别 10(空格)的显式空格字符标记:类别 11(字母)的显式字符标记: 类别 10(空格)的 显式空格字符 标记:控制字标记:类别 1(开始组) 的 显式字符标记:类别 11(字母)的 显式字符标记:类别 11(字母)的 显式字符标记:类别 11(字母)的 显式字符标记: 显式类别 10(空格)的空格字符标记:类别 11(字母)的 显式字符标记:类别 11(字母)的显 式字符标记:类别 11(字母)的 显式字符标记:类别 11(字母)的 显式字符标记:类别 11(字母)的 显式字符标记:类别 11(字母)的显式 字符标记:类别 11(字母)的 显式字符标记:类别 11(字母)的显 式字符标记:类别 2(结束组)的 显式字符标记: 类别 10(空格)的显式空格字符标记:类别 11(字母)的 显式字符标记: 类别 11(字母)的 显式字符标记: 类别 10(空格)的显式空格字符标记: 控制字标记:类别 12(其他)的 显式字符标记:T11h
h11i
i11s
s11␣
␣10i
i11s
s11
␣10a
a11
␣10\macro
\macro{
{1w
w11i
i11t
t11h
h11
␣10a
a11r
r11g
g11u
u11m
m11e
e11n
n11t
t11}
}2
␣10i
i11n
n11
␣10\LaTeX
\LaTeX.
.12
即,根据需要,您将获得附加在标记流中的以下标记:
T11。h11i11s11␣10i11s11␣10a11␣10\macro{1w11i11t11h11␣10a11r11g11u11m11e11n11t11}2␣10i11n11␣10\LaTeX.12
因此,可以将标记看作是将小项一个接一个地放置,从而形成一个标记队列/标记流。TeX 中的大多数处理都与此类标记有关。
TeX 中的扩展概念是关于通过其他方式获取这些漂亮的小标记项,而不仅仅是“查看”.tex 输入文件并根据其中的内容形成标记。TeX 中的标记可以通过读取和标记 .tex 输入来生成。标记也可以在扩展过程中生成。
TeX 中的宏编程概念是让 TeX 的“扩展站”从标记流中移除当前定义为宏的标记项,并从标记流中抓取一组标记项作为宏参数,并在标记流中用另一组标记替换已移除的标记项,该组标记由在定义宏时形成 ⟨ 的那些标记组成定义⟩ 的 ⟨替换文本⟩,但有⟨替换文本⟩ 的参数被作为参数抓取的标记所替换。
因此,(La)TeX 中的宏编程就是翻转标记流中这些漂亮的小标记项,从而修改标记流中标记的顺序。
TeX 的“token-production-station”一点一点地只生成后续站能够执行下一项任务所需的 token 数量。
因此,当 TeX 的“token-production-station”生成一个 token 并将其附加到 token 流中以传输到后续的处理“站”时,该 token 到达的第一个站是“expansion-station”。如果“expansion-station”发现此 token 是可扩展的,例如,是宏 token,则“expansion-station”会友好地要求“token-production-station”生成并发送适合形成宏 token 参数的 token 集。仅此而已。然后进行扩展,其中的内容被替换。使用生成的 token 集(其中的内容被替换),如果“expansion-station”发现第一个 token 是可扩展的,则以相同的方式对该 token 进行扩展。如果“扩展站”发现该令牌不可扩展,则该令牌将被发送到后续的处理“站”。因此,扩展可以描述为“反刍过程”。
在后续的“站”中,将查看标记并完成与扩展无关的工作。例如,在后续的“站”中,可以将标记用作定义宏、打开或关闭本地范围 (!)、创建框/执行排版工作以生成文档页面、将消息写入屏幕或 .log 文件、写入外部文本文件、将某些内容添加到 .dvi 或 .pdf 文件等的指令。
如果后续“站”需要更多令牌才能完成工作,它们就需要从“扩展站”提供令牌。“扩展站”又需要从“令牌生成站”提供令牌,进行扩展并交付生成的令牌。
这就是为什么你可以\hbox\macro
在“说”之后再说“说” \def\macro{{stuff}}
。
\hbox
在 token-production-station 中生成控制字 token \hbox。这恰好是一个不可扩展的原语,因此会通过扩展站而不会被替换/移除。当它到达生产水平框的站时,“水平框生产站”需要更多 token。因此 token-production-station 生成 token \macro。这在“扩展站”中被扩展,因此被 token 替换。 这些 token 中的每一个都不是可扩展的,因此“扩展站”不会对它们中的任何一个进行任何替换,而是将它们留在原处,这样它们都会进入“水平框生产站”,现在,形成指令的所有 token都已到达,并且生成 a 的工作已完成。{1s11t11u11f11f11}2
\hbox{1s11t11u11f11f11}2\hbox
但在此之前有一个指令\def\macro{{stuff}}
。使用该指令,“令牌生产站”生产令牌\def并将其发送到“扩展站”。扩展站发现它是一个不可扩展的原语并将其发送出去。当该令牌到达“宏定义站”时,“宏定义站”要求“扩展站”提供更多令牌,但不进行扩展。因此,“扩展站”要求“令牌生产站”生产和交付更多令牌。“令牌生产站”生产并交付令牌, “扩展站”将它们原封不动地发送到后续站,以便它们到达“宏定义站”。 (使用而不是“宏定义站”将不需要“扩展站”在不扩展令牌的情况下交付令牌,但允许“扩展站”照常工作。){1{1s11t11u11f11f11}2}2\edef
\def
在尝试粗略概述之后,让我们来看看您的问题中提到的问题:
- 如果不在
\ExplSyntaxOn
/expl3 模式下,代码中的空格在很多地方都会起作用,您需要对此挑剔,并且可能需要%
在这里和那里使用以防止出现空格标记。 - 使用最新的 TeX 发行版,并且可以使用原语
\expanded
和,你可以结合和通过 -handler toplevel-expanded 定义宏,以获得形成它们的标记 ⟨\unexpanded
\expanded
\unexpanded
.store
替换文本⟩s。(顺便说一句:由于它.initial:n
来自:n
expl3-syntax,我想知道为什么它不是.store:N
。) - 在宏扩展过程中,宏的 ⟨ 中出现单个类别 6(参数)的哈希字符标记 ( )#6定义⟩ 的 ⟨替换文本⟩ 后面跟着一个属于类别 12(其他)的数字字符标记, , , , , , , , , , ,则表示要用宏参数替换的参数。但是,在 ⟨ 中出现了两个属于类别 6(参数)的连续井号字符标记 ( )112212312412512612712812912#6#6替换文本⟩ 只是折叠成一个不作为参数的东西,但是,像 ⟨替换文本⟩,按原样插入到标记流中。因此,哈希值应进入⟨替换文本⟩ 宏
\thmstyleheadcmd
,这样它们就不会被当作 ⟨ 的参数替换文本⟩ 但按原样交付,需要加倍。⟨ 内的哈希值加倍替换文本⟩ 的 ⟨定义\edef
⟩ 可以通过与结合\unexpanded
来实现\edef\macro{\unexpanded{At the time of expanding, the following yields a single hash character token of category 6(parameter): #}}
。
\documentclass{article}
\usepackage{amsthm,kantlipsum}
\DeclareKeys[thmstyle]{%
spaceabove .store = \thmstylespaceabove,
spaceabove .initial:n = \topsep,
spacebelow .store = \thmstylespacebelow,
spacebelow .initial:n = \topsep,
bodyfont .store = \thmstylebodyfont,
bodyfont .initial:n = \itshape,
headindent .store = \thmstyleheadindent,
headindent .initial:n = 0pt,
headfont .store = \thmstyleheadfont,
headfont .initial:n = \bfseries,
headpunct .store = \thmstyleheadpunct,
headpunct .initial:n = {.},
postheadspace .store = \thmstylepostheadspace,
postheadspace .initial:n = 5pt plus 1pt minus 1pt,
% Let's define \thmstyleheadcmd in a way where hashes going
% into the replacement text are doubled and thus not taken for
% parameters of the macro
headstyle .code = \edef\thmstyleheadcmd{\unexpanded{#1}},
headstyle .initial:n = {\thmname{#1}\thmnumber{ #2}\thmnote{ #3}},
}
\newcommand{\NewThmStyle}[2]{%
\begingroup
% \show\thmstyleheadcmd
\SetKeys[thmstyle]{#2}%
% \show\thmstyleheadcmd
\expanded{%
\endgroup
% \show\noexpand\thmstyleheadcmd
\unexpanded{\newtheoremstyle{#1}}%
\unexpanded\expandafter{\expandafter{\thmstylespaceabove}}%
\unexpanded\expandafter{\expandafter{\thmstylespacebelow}}%
\unexpanded\expandafter{\expandafter{\thmstylebodyfont}}%
\unexpanded\expandafter{\expandafter{\thmstyleheadindent}}%
\unexpanded\expandafter{\expandafter{\thmstyleheadfont}}%
\unexpanded\expandafter{\expandafter{\thmstyleheadpunct}}%
\unexpanded\expandafter{\expandafter{\thmstylepostheadspace}}%
\unexpanded\expandafter{\expandafter{\thmstyleheadcmd}}%
}%
}
\NewThmStyle{teststyle}{headstyle=Something\thmname{#1}Something\thmnumber{ #2}Something\thmnote{ #3}}
\newtheorem{theorem}{Theorem}
\theoremstyle{teststyle}
\newtheorem{test}{Test}
\begin{document}
\begin{theorem}
\kant[2][1]
\end{theorem}
\begin{test}
\kant[2][1]
\end{test}
\begin{test}[Note]
\kant[2][1]
\end{test}
\end{document}
如果你碰巧使用的是 TeX\DeclareKeys
发行版,而底层 TeX 引擎由于某些不为人知的原因不提供\expanded
或\unexpanded
,这种情况非常非常不可能,你可以 - 而不是组合\edef
和\unexpanded
-\edef
与\the
令牌寄存器的扩展组合以实现哈希加倍。你可以组合顶层扩展\expanded
通过和\unexpanded
来获取形成由 keyval 接口定义的宏的顶层扩展的令牌然后关闭组,而是通过\expandafter
连续交换参数来组合顶层扩展,保存形成顶层扩展结果的令牌,直到大括号组包含所有宏的顶层扩展的结果并且对 的调用\newtheoremstyle
被放在令牌后面\endgroup
:
\documentclass{article}
\usepackage{amsthm,kantlipsum}
\newcommand\PassFirstToSecond[2]{#2{#1}}%
\newtoks\Scratchtoks
\DeclareKeys[thmstyle]{%
spaceabove .store = \thmstylespaceabove,
spaceabove .initial:n = \topsep,
spacebelow .store = \thmstylespacebelow,
spacebelow .initial:n = \topsep,
bodyfont .store = \thmstylebodyfont,
bodyfont .initial:n = \itshape,
headindent .store = \thmstyleheadindent,
headindent .initial:n = 0pt,
headfont .store = \thmstyleheadfont,
headfont .initial:n = \bfseries,
headpunct .store = \thmstyleheadpunct,
headpunct .initial:n = {.},
postheadspace .store = \thmstylepostheadspace,
postheadspace .initial:n = 5pt plus 1pt minus 1pt,
headstyle .code = \expandafter\PassFirstToSecond\expandafter{\the\Scratchtoks}{%
\Scratchtoks={#1}\edef\thmstyleheadcmd{\the\Scratchtoks}\Scratchtoks=%
},
headstyle .initial:n = {\thmname{#1}\thmnumber{ #2}\thmnote{ #3}},
}
\newcommand{\NewThmStyle}[2]{%
\begingroup
%\show\thmstyleheadcmd
\SetKeys[thmstyle]{#2}%
\expandafter\PassFirstToSecond\expandafter{\thmstyleheadcmd}{% 1
\expandafter\PassFirstToSecond\expandafter{\thmstylepostheadspace}{% 2
\expandafter\PassFirstToSecond\expandafter{\thmstyleheadpunct}{% 3
\expandafter\PassFirstToSecond\expandafter{\thmstyleheadfont}{% 4
\expandafter\PassFirstToSecond\expandafter{\thmstyleheadindent}{% 5
\expandafter\PassFirstToSecond\expandafter{\thmstylebodyfont}{% 6
\expandafter\PassFirstToSecond\expandafter{\thmstylespacebelow}{% 7
\expandafter\PassFirstToSecond\expandafter{\thmstylespaceabove}{% 8
%\show\thmstyleheadcmd
\endgroup
%\show\thmstyleheadcmd
\newtheoremstyle{#1}%
}}}}}}}}% 87654321
}
\NewThmStyle{teststyle}{headstyle=Something\thmname{#1}Something\thmnumber{ #2}Something\thmnote{ #3}}
\newtheorem{theorem}{Theorem}
\theoremstyle{teststyle}
\newtheorem{test}{Test}
\begin{document}
\begin{theorem}
\kant[2][1]
\end{theorem}
\begin{test}
\kant[2][1]
\end{test}
\begin{test}[Note]
\kant[2][1]
\end{test}
\end{document}
答案3
如果您在 a 中定义某些内容\begingroup ... \endgroup
(并且您没有将其设为全局),则该定义不会比组存在更长时间。如果您删除包含线,则样式不再被忽略。我添加了一个冒号以使这一点更清晰(尽管很丑陋)。
\documentclass{article}
\usepackage{amsthm,kantlipsum}
\DeclareKeys[thmstyle]{
spaceabove .store = \thmstylespaceabove,
spaceabove .initial:n = \topsep,
spacebelow .store = \thmstylespacebelow,
spacebelow .initial:n = \topsep,
bodyfont .store = \thmstylebodyfont,
bodyfont .initial:n = \itshape,
headindent .store = \thmstyleheadindent,
headindent .initial:n = 0pt,
headfont .store = \thmstyleheadfont,
headfont .initial:n = \bfseries,
headpunct .store = \thmstyleheadpunct,
headpunct .initial:n = {.},
postheadspace .store = \thmstylepostheadspace,
postheadspace .initial:n = 5pt plus 1pt minus 1pt,
headstyle .code = {\def\thmstyleheadcmd##1##2##3{#1}},
% doubling the # below does not work either
headstyle .initial:n = \thmname{#1}\thmnumber{ #2}:\thmnote{ #3},
}
\newcommand{\NewThmStyle}[2]{
% \begingroup
\SetKeys[thmstyle]{#2}
\newtheoremstyle{#1}
{\thmstylespaceabove}
{\thmstylespacebelow}
{\thmstylebodyfont}
{\thmstyleheadindent}
{\thmstyleheadfont}
{\thmstyleheadpunct}
{\thmstylepostheadspace}
{\thmstyleheadcmd{##1}{##2}{xx##3}}
% \endgroup
}
\NewThmStyle{teststyle}{}
\newtheorem{theorem}{Theorem}
\theoremstyle{teststyle}
\newtheorem{test}{Test}
\begin{document}
\begin{theorem}
\kant[2][1]
\end{theorem}
\begin{test}
\kant[2][1]
\end{test}
\end{document}
答案4
如果您愿意使用另一个 key=value 实现,则可以使用expkv-cs
1,其中您不必应用任何分组或默认重置,因为键由参数转发处理,并且仅在初始定义期间或\ekvcChange
设置/更改默认值。
如果您使用 -Variants,Hash
您可以简单地使用它\expanded
来获取键值,而无需进一步扩展它们(我们需要使用或\ekvcValue
保护我们不想扩展的内容)。而且您也不需要关心哈希加倍,在初始分配期间和内部会为您处理该问题。\noexpand
\unexpanded
expkv-cs
\ekvcChange
\documentclass{article}
\usepackage{amsthm,kantlipsum}
\usepackage{expkv-cs}
\newcommand\NewThmStyle[2]{\NewThmStyleKV{#2}{#1}}
\ekvcHashAndForward\NewThmStyleKV\NewThmStyleDO
{
spaceabove = \topsep
,spacebelow = \topsep
,bodyfont = \itshape
,headindent = 0pt
,headfont = \bfseries
,headpunct = {.}
,postheadspace = 5pt plus 1pt minus 1pt
,headstyle = {\thmname{#1}\thmnumber{ #2}\thmnote{ #3}}
}
\newcommand\NewThmStyleDO[2]
{%
\expanded{\noexpand\newtheoremstyle{#2}%
{\ekvcValue{spaceabove}{#1}}%
{\ekvcValue{spacebelow}{#1}}%
{\ekvcValue{bodyfont}{#1}}%
{\ekvcValue{headindent}{#1}}%
{\ekvcValue{headfont}{#1}}%
{\ekvcValue{headpunct}{#1}}%
{\ekvcValue{postheadspace}{#1}}%
{\ekvcValue{headstyle}{#1}}}%
}
\NewThmStyle{teststyle}{}
\newtheorem{theorem}{Theorem}
\theoremstyle{teststyle}
\newtheorem{test}{Test}
\begin{document}
\begin{theorem}
\kant[2][1]
\end{theorem}
\begin{test}
\kant[2][1]
\end{test}
\end{document}
1expkv-bundle
免责声明:我是其中expkv-cs
一部分的作者。