使用 \DeclareKeys 的良好做法

使用 \DeclareKeys 的良好做法

我正在尝试为具有自定义名称的定理创建一个“一次性”定理环境,这些定理根本不应该被编号。目标是复制thmtools

\declaretheorem[thmbox=M]{theorem}

通过直接使用thmbox环境并实现thmtools 的labelname选项,将未知键解释为键的值name。由于定理没有编号,\ref应该给出定理名称。没有太多可继续的内容clsguide,我的目标是不使用 keyvals 的“编程”接口(等)来做到这一点\keys_define:nn。这是我的尝试,似乎有效:

\documentclass{article}

\usepackage{kantlipsum,thmtools}

\makeatletter

\newif\if@hasname@

\DeclareKeys[oneoffthm]{
    label .store = \oneoffthm@label,
    storename .store = \oneoffthm@name,
    hasname .if = @hasname@,
    name .meta:n = {hasname,storename=#1},
    }
\DeclareUnknownKeyHandler[oneoffthm]{\SetKeys[oneoffthm]{name=#1}}
    
\NewDocumentEnvironment{oneoffthm}{m o}{%
    \vspace*{0.4\baselineskip}%
    \begin{thmbox}[M]{%
    \bfseries%
    #1%
    \IfValueT{#2}{%
        \SetKeys[oneoffthm]{#2}%
        \def\@currentlabel{#1}%
        \label{\oneoffthm@label}%
        \if@hasname@\;(\textit{\oneoffthm@name})\fi%
        }%
    }%
    \noindent%
    \ignorespaces%
    }
    {\end{thmbox}\vspace*{0.4\baselineskip}}
    
\makeatother

\declaretheorem[thmbox=M]{theorem}

\thmboxoptions{bodystyle=\normalfont\noindent}

\begin{document}

\begin{theorem}
\kant[1][1]
\end{theorem}

\begin{oneoffthm}{Custom Name A}
\kant[2][1]
\end{oneoffthm}

\begin{theorem}[a heading]
\kant[3][1]
\end{theorem}

\begin{oneoffthm}{Custom Name B}[a heading]
\kant[4][1]
\end{oneoffthm}

\begin{theorem}[label=abc]
\kant[5][1]
\end{theorem}

\begin{oneoffthm}{Custom Name C}[label=customC]
\kant[6][1]
\end{oneoffthm}

\begin{theorem}[name=a heading,label=def]
\kant[7][1]
\end{theorem}

\begin{oneoffthm}{Custom Name D}[name=a heading,label=customD]
\kant[8][1]
\end{oneoffthm}

\ref{abc}

\ref{customC}

\ref{def}

\ref{customD}

\end{document}

文档

我的方法\DeclareKeys似乎不太理想。有没有办法访问关键参数而不将它们存储在宏中?出于扩展原因,这在某些设置中似乎不太好。此外,有没有办法实现该name键而不需要设置其他两个键?当然可以说

name .code = \@hasname@true\def\oneoffthm@name{#1}

但这似乎不符合 keyvals 的精神。

不幸的是我无法适应这个优秀的答案因为 thmbox 不允许无编号定理。

附言手动垂直间距oneoffthm很糟糕,但我不知道有更好的选择。普通thmbox环境的默认间距似乎不正确,但使用 thmbox 声明的定理的间距很好,直接使用 thmtools 或 thmbox 包即可。编辑:为了模仿 thmbox 声明定理的行为,将开头和结尾分别替换\vspace*{0.4\baselineskip}\medbreak\smallbreak\@endpetrue

答案1

您可以使用修饰符来表示不是 key=value 列表的参数应该被视为键的参数,而不是将其视为[a heading]未知键。=name

\documentclass{article}

\usepackage{kantlipsum,thmtools}

\makeatletter

\newif\if@hasname@

\DeclareKeys[oneoffthm]{
    label .store = \oneoffthm@label,
    storename .store = \oneoffthm@name,
    hasname .if = @hasname@,
    name .meta:n = {hasname,storename=#1},
    }

    
\NewDocumentEnvironment{oneoffthm}{m ={name} o}{%
    \vspace*{0.4\baselineskip}%
    \begin{thmbox}[M]{%
    \bfseries%
    #1%
    \IfValueT{#2}{%
        \SetKeys[oneoffthm]{#2}%
        \def\@currentlabel{#1}%
        \label{\oneoffthm@label}%
        \if@hasname@\;(\textit{\oneoffthm@name})\fi%
        }%
    }%
    \noindent%
    \ignorespaces%
    }
    {\end{thmbox}\vspace*{0.4\baselineskip}}
    
\makeatother

\declaretheorem[thmbox=M]{theorem}

\thmboxoptions{bodystyle=\normalfont\noindent}

\begin{document}

\begin{theorem}
\kant[1][1]
\end{theorem}

\begin{oneoffthm}{Custom Name A}
\kant[2][1]
\end{oneoffthm}

\begin{theorem}[a heading]
\kant[3][1]
\end{theorem}

\begin{oneoffthm}{Custom Name B}[a heading]
\kant[4][1]
\end{oneoffthm}

\begin{theorem}[label=abc]
\kant[5][1]
\end{theorem}

\begin{oneoffthm}{Custom Name C}[label=customC]
\kant[6][1]
\end{oneoffthm}

\begin{theorem}[name=a heading,label=def]
\kant[7][1]
\end{theorem}

\begin{oneoffthm}{Custom Name D}[name=a heading,label=customD]
\kant[8][1]
\end{oneoffthm}

\ref{abc}

\ref{customC}

\ref{def}

\ref{customD}

\end{document}

您可以简化键结构,删除元键和内部布尔值:

\makeatletter

\DeclareKeys[oneoffthm]{
    label .store = \oneoffthm@label,
    name .store = \oneoffthm@name,
    }

    
\NewDocumentEnvironment{oneoffthm}{m ={name} o}{%
    \vspace*{0.4\baselineskip}%
    \begin{thmbox}[M]{%
    \bfseries%
    #1%
    \IfValueT{#2}{%
        \let\oneoffthm@name\relax
        \SetKeys[oneoffthm]{#2}%
        \def\@currentlabel{#1}%
        \label{\oneoffthm@label}%
        \ifx\oneoffthm@name\relax\else\;(\textit{\oneoffthm@name})\fi
        }%
    }%
    \noindent%
    \ignorespaces%
    }
    {\end{thmbox}\vspace*{0.4\baselineskip}}
    
\makeatother

答案2

expkv-cs在我们可以的帮助下

  1. 访问未知的密钥名称而不对其进行解密
  2. 访问不同键的值,不存在任何扩展问题

下面使用内部键将带有格式指令的键值传递name给底层宏(这样我们就不需要条件来检查它是否被使用,如果没有使用它就会是空的)。

\documentclass{article}

\usepackage{kantlipsum,thmtools}

\usepackage{expkv-cs}

\makeatletter

\ekvcSplit\oneoffthm@KV
  {
     label         = {}
    ,name-internal = {}
  }
  {\label{#1}#2}
\ekvcSecondaryKeys\oneoffthm@KV{meta name = name-internal={\;(\textit{#1})}}
\ekvdefunknownNoVal{\string\oneoffthm@KV}{\ekvmorekv{name={#2}}}

\NewDocumentEnvironment{oneoffthm}{m o}{%
    \vspace*{0.4\baselineskip}%
    \begin{thmbox}[M]{%
    \bfseries%
    #1%
    \IfValueT{#2}
      {%
        \def\@currentlabel{#1}%
        \oneoffthm@KV{#2}%
      }%
    }%
    \noindent%
    \ignorespaces%
    }
    {\end{thmbox}\vspace*{0.4\baselineskip}}

\makeatother

\declaretheorem[thmbox=M]{theorem}

\thmboxoptions{bodystyle=\normalfont\noindent}

\begin{document}

\begin{theorem}
\kant[1][1]
\end{theorem}

\begin{oneoffthm}{Custom Name A}
\kant[2][1]
\end{oneoffthm}

\begin{theorem}[a heading]
\kant[3][1]
\end{theorem}

\begin{oneoffthm}{Custom Name B}[a heading]
\kant[4][1]
\end{oneoffthm}

\begin{theorem}[label=abc]
\kant[5][1]
\end{theorem}

\begin{oneoffthm}{Custom Name C}[label=customC]
\kant[6][1]
\end{oneoffthm}

\begin{theorem}[name=a heading,label=def]
\kant[7][1]
\end{theorem}

\begin{oneoffthm}{Custom Name D}[name=a heading,label=customD]
\kant[8][1]
\end{oneoffthm}

\ref{abc}

\ref{customC}

\ref{def}

\ref{customD}

\end{document}

一种使用expkv-def而不是 的变体expkv-cs。通过这种方式,我们仍然可以访问未知的键名而无需对其进行去标记化,但也可以使用更传统的 key=value 方法,即定义宏来存储键值。

此外,dataT的键类型expkv-def对于手头的任务来说似乎非常方便(\label如果label使用了键,则仅设置,如果name使用了键,则仅格式化名称)。dataT如果未使用键,则键将吞噬存储它的宏后面的标记或组(因此行为类似于\@gobble),但如果使用了它,它将扩展为标记或组,后跟括号中的值(因此这里也没有扩展问题,和\label\oneoffthm@name@format获取值而不是存储它的宏)。

\documentclass{article}

\usepackage{kantlipsum,thmtools}

\usepackage{expkv-def}

\makeatletter

\ekvdefinekeys{oneoffthm}
  {
     dataT   label = \oneoffthm@label
    ,dataT   name  = \oneoffthm@name 
    ,unknown noval = \ekvmorekv{name={#2}}
  }
\newcommand\oneoffthm@name@format[1]{\;(\textit{#1})}

\NewDocumentEnvironment{oneoffthm}{m o}{%
    \vspace*{0.4\baselineskip}%
    \begin{thmbox}[M]{%
    \bfseries%
    #1%
    \IfValueT{#2}
      {%
        \def\@currentlabel{#1}%
        \ekvset{oneoffthm}{#2}%
        \oneoffthm@label\label
        \oneoffthm@name\oneoffthm@name@format
      }%
    }%
    \noindent%
    \ignorespaces%
    }
    {\end{thmbox}\vspace*{0.4\baselineskip}}

\makeatother

\declaretheorem[thmbox=M]{theorem}

\thmboxoptions{bodystyle=\normalfont\noindent}

\begin{document}

\begin{theorem}
\kant[1][1]
\end{theorem}

\begin{oneoffthm}{Custom Name A}
\kant[2][1]
\end{oneoffthm}

\begin{theorem}[a heading]
\kant[3][1]
\end{theorem}

\begin{oneoffthm}{Custom Name B}[a heading]
\kant[4][1]
\end{oneoffthm}

\begin{theorem}[label=abc]
\kant[5][1]
\end{theorem}

\begin{oneoffthm}{Custom Name C}[label=customC]
\kant[6][1]
\end{oneoffthm}

\begin{theorem}[name=a heading,label=def]
\kant[7][1]
\end{theorem}

\begin{oneoffthm}{Custom Name D}[name=a heading,label=customD]
\kant[8][1]
\end{oneoffthm}

\ref{abc}

\ref{customC}

\ref{def}

\ref{customD}

\end{document}

相关内容