5

Just as an exercise I'm trying to make a keyval interface to amsthm's \newtheoremstyle in the manner of thmtools, but using the kernel's \DeclareKeys instead of keyval and kvsetkeys. Every argument of \newtheoremstyle can easily be stored and retrieved, except the final "head spec" argument which includes macro parameters #.

Here is the non-working, but non-erroring, example:

\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}

thm

I try to define a command \thmstyleheadcmd with three arguments that gets plugged into the last argument of \newtheoremstyle, but it seems to be simply ignored. It is not, however, treated as empty since to amsthm that should mean "default", but you can see there is no space between the name and number.

The attempt above comes after this more naive try:

headstyle .store = \thmstyleheadstyle,
headstyle .initial:n = {\thmname{#1}\thmnumber{ #2}\thmnote{ #3}},

and replace \thmstyleheadcmd{##1}{##2}{##3} with \thmstyleheadstyle in the definition of \NewThmStyle. This has the same outcome as above.

The relevant chunk of code in amsthm's definition of \newtheoremstyle is

  \@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}%
  }%

This made me think the number of # might need to increase, but I can't get any variation to work. Is there a way to pass the value of headstyle to \newtheoremstyle "verbatim" using l3keys? Or am I missing something obvious?

3
  • What's supposed to happen exactly?
    – cfr
    Commented Sep 12, 2023 at 0:58
  • You're defining it only locally within the group, aren't you? But then you're trying to use it outside?
    – cfr
    Commented Sep 12, 2023 at 1:07
  • If you remove \begingroup and \endgroup, the definition is no longer ignored.
    – cfr
    Commented Sep 12, 2023 at 1:13

4 Answers 4

7

You can't use \begingroup to keep the initial values of the keys, because this would make the new theorem style undefined as soon as \endgroup is found.

\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}

enter image description here

12
  • Did you consider pre-compiling to speed up at point-of-use?
    – Joseph Wright
    Commented Sep 12, 2023 at 11:58
  • @JosephWright Added…
    – egreg
    Commented Sep 12, 2023 at 13:01
  • Thanks. Though I'm confused by your comment about the theorem style being undefined outside the group. The way I defined it in my example, the style does escape the group in the sense that \NewThmStyle{test}{spaceabove=10pt} has an effect and there's no Unknown theoremstyle warning
    – mbert
    Commented Sep 12, 2023 at 13:54
  • @egreg I think I see the issue. \newtheoremstyle does \@xp\xdef\csname th@#1\endcsname{\the\toks@ \the\@temptokena}% which makes the style global, but only does \@namedef{thmhead@#1}##1##2##3{#9}% so the head style doesn't escape
    – mbert
    Commented Sep 12, 2023 at 14:01
  • 1
    @mbert Now it should be really fixed.
    – egreg
    Commented Sep 24, 2023 at 21:39
5

In case you are not all too familiar with the programming-paradigms underlying TeX I try to give a rough outline:

When TeX processes a .tex-input-file, the content of that file is taken for a set of directives for creating so-called tokens and appending them to the token-stream.

E.g., if the .tex-input-file contains This is a \macro{with argument} in \LaTeX., TeX by and by, as needed, forms the following tokens for having them ready for further processing:

Explicit character token T of category 11(letter): T11
Explicit character token h of category 11(letter): h11
Explicit character token i of category 11(letter): i11
Explicit character token s of category 11(letter): s11
Explicit space character token of category 10(space)— shall denote a space, i.e., the character whose code-point-number both in ASCIII and in unicode is 32: 10
Explicit character token i of category 11(letter): i11
Explicit character token s of category 11(letter): s11
Explicit space character token of category 10(space): 10
Explicit character token a of category 11(letter): a11
Explicit space character token of category 10(space): 10
Control word token \macro: \macro
Explicit character token { of category 1(begin group): {1
Explicit character token w of category 11(letter): w11
Explicit character token i of category 11(letter): i11
Explicit character token t of category 11(letter): t11
Explicit character token h of category 11(letter): h11
Explicit space character token of category 10(space): 10
Explicit character token a of category 11(letter): a11
Explicit character token r of category 11(letter): r11
Explicit character token g of category 11(letter): g11
Explicit character token u of category 11(letter): u11
Explicit character token m of category 11(letter): m11
Explicit character token e of category 11(letter): e11
Explicit character token n of category 11(letter): n11
Explicit character token t of category 11(letter): t11
Explicit character token } of category 2(end group): }2
Explicit space character token of category 10(space): 10
Explicit character token i of category 11(letter): i11
Explicit character token n of category 11(letter): n11
Explicit space character token of category 10(space): 10
Control word token \LaTeX: \LaTeX
Explicit character token . of category 12(other): .12

I.e., by and by, as needed, you get the following tokens appended the token-stream:

T11h11i11s1110i11s1110a1110\macro{1w11i11t11h1110a11r11g11u11m11e11n11t11}210i11n1110\LaTeX.12.

So tokens can be seen as nice little items placed one behind another and thus forming a token-queue/a token-stream of nice little items. Most of the processing in TeX deals with such tokens.

The concept of expansion in TeX is about obtaining such nice little token-items in ways other than by just "looking" at the .tex-input-file and forming tokens according to what is written there. Tokens in TeX can come into being by reading and tokenizing .tex-input. Tokens can also come into being in the course of expansion.

The concept of macro-programming in TeX is about having TeX's "expansion-station" remove token-items that currently are defined to be macros from the token stream and grabbing sets of token-items from the token stream, serving as macro-arguments, and in the token stream substituting the removed token-items by another set of tokens which is formed by the those tokens which at the time of defining the macro formed the ⟨definition⟩'s ⟨replacement text⟩, but with the ⟨replacement text⟩'s parameters substituted by the tokens that were grabbed as arguments.

So macro-programming in (La)TeX is about flipping these nice little token-items in the token-stream around and thus modifying the order of tokens in the token-stream.

TeX's "token-producing-station" bit by bit only produces that many tokens as are needed for subsequent stations to be able to do their next thing.
So when TeX's "token-producing-station" produced a token and appended it to the token-stream to be transported to subsequent "stations" of processing, the first station where that token goes is the "expansion-station". If the "expansion-station" finds that this token is expandable, e.g., is a macro-token, the "expansion-station" kindly asks the "token-producing-station" to produce and send sets of tokens that are suitable for forming the macro-token's arguments. And no more. Then expansion takes place, where stuff gets replaced. With the resulting set of tokens, where stuff is replaced, if the "expansion-station" finds the first token to be expandable, expansion of that token takes place in the same way. If the "expansion-station" finds that token not to be expandable, that token is send on to reach subsequent "stations" of processing. So expansion can be described as a "regurgitative process".

In subsequent "stations" tokens are looked at and work is done which is not related to expansion. E.g., in subsequent "stations" tokens may be taken for directives for defining macros, opening up or closing local scopes(!), creating boxes/doing typesetting-work for producing a page of the document, writing messages to the screen or to the .log-file, writing external text-files, adding s.th. to the .dvi- or .pdf-file, etc.

If subsequent "stations" need more tokens for being able to do their work, they require them to be delivered from the "expansion-station". The "expansion-station" in turn requires from the "token-producing-station", does expansion and delivers the resulting tokens.

That's why you can, e.g., "say" \hbox\macro after having "said" \def\macro{{stuff}}.

\hbox in the token-producing-station yields the control-word-token \hbox. That happens to be an unexpandable primitive and thus goes through the expansion-station without being substituted/removed. When it arrives in the station where horizontal boxes are produced, the "horizontal-box-producing-station" requires more tokens. So the token-producing-station produces the token \macro. This is expanded in the "expansion-station" and thus replaced by the tokens {1s11t11u11f11f11}2.
Each of these tokens is not expandabe, so the "expansion-station" does not do whatsoever substitution to any of them but leaves them in place so they all make it into the "horizontal-box-producing-station" where now all in all tokens forming the directive \hbox{1s11t11u11f11f11}2 have have arrived and the work of producing a \hbox is done.

But beforehand there was the directive \def\macro{{stuff}}. With that directive, the "token-producing-station" produces the token \def and sends it towards the "expansion-station". The expansion-station finds it to be an unexpandable primitive and sends it on. When that token reaches the "macro-defining-station", the "macro-defining-station" requires the "expansion-station" to deliver more tokens, but without expansion. So the "expansion-station" requires the "token-producing-station" to produce and deliver more tokens. The "token-producing-station" produces and delivers the tokens {1{1s11t11u11f11f11}2}2 and the "expansion-station" sends them untouched towards subsequent stations so that they reach the "macro-defining-station".
(With \edef instead of \def the "macro-defining-station" would not require the "expansion station" to deliver tokens without expanding them, but would allow the "expansion station" to do its work as usual.)


After this attempt at a rough outline, let's look at the issues mentioned in your question:

  1. If not in \ExplSyntaxOn/expl3-mode, spaces in your code do matter in many places and you need to be picky about that and you may need to use % here and there for preventing the coming into being of a space token.
  2. With TeX distributions which are up to date and where primitives \expanded and \unexpanded are available, you can combine \expanded and \unexpanded to have macros defined via .store-handler toplevel-expanded in order to obtain the tokens that form their ⟨replacement text⟩s. (By the way: As it is .initial:n with :n coming from expl3-syntax, I wonder why it is not .store:N.)
  3. During expansion of a macro a single hash-character-token of category 6(parameter) (#6) occurring in the macro's ⟨definition⟩'s ⟨replacement text⟩ trailed by one of the digit-character-tokens of category 12(other) 112, 212, 312, 412, 512, 612, 712, 812, 912, is taken for a parameter which is to be substituted by a macro argument. But two consecutive hash-character-tokens of category 6(parameter) (#6#6) occurring in the ⟨replacement text⟩ just collapse into a single one which is not taken for a parameter-thingie, but, like other tokens of the ⟨replacement text⟩, is inserted into the token-stream as is. Thus hashes that shall go into the ⟨replacement text⟩ of the macro \thmstyleheadcmd in a way where they are not taken for parameter-thingies of the ⟨replacement text⟩ but are delivered as is, need to be doubled. The doubling of hashes within the ⟨replacement text⟩ of a ⟨definition⟩ can be achieved via combining \edef with \unexpanded as in \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}

enter image description here


If you happen to work with a TeX distribution where \DeclareKeys is available while for some obscure reason the underlying TeX-engine does not provide \expanded or \unexpanded, which is very very very unlikely, you can - instead of combining \edef and \unexpanded - combine \edef with \the-expansion of a token-register for achieving hash doubling. Instead of combining \expanded with \unexpanded for getting the tokens that form the toplevel-expansions of the macros defined by the keyval-interface before closing the group, you can combine toplevel-expanding via \expandafter with successively exchanging arguments holding the tokens that form the result of toplevel-expansion until brace-groups enclosing results of toplevel-expansion of all macros and the call to \newtheoremstyle are placed behind the token \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}

enter image description here

2

If you define something within a \begingroup ... \endgroup (and you don't make it global), the definition doesn't outlast the group. If you remove the containment lines, the style is no longer ignored. I added a colon to make this clearer (albeit ugly).

(ugly) colon shows style isn't ignored

\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
  • Thanks and you are absolutely right, however the reason I put the declaration in a group is to prevent one call of \NewThmStyle from affecting the next, e.g. if you call \NewThmStyle{test1}{spaceabove=100pt} then \NewThmStyle{test2}{}, the test2 style will also have spaceabove=100pt
    – mbert
    Commented Sep 12, 2023 at 1:43
  • So, how can I get around this? I guess I could manually \SetKeys everything back to default at the end of the definition, but there should be a better way, right?
    – mbert
    Commented Sep 12, 2023 at 1:44
  • @mbert Well, you can't have the same macro meaning two things at once. So you could expand the values, make the new definition global and keep the key-settings within the group. So you'd want \newtheoremstyle to get the expansion of the macros, rather than the macros, right? (I don't use amsthm or any of these theorem packages, so I'm just assuming normally you'd have something like \newtheoremstyle{\bfseries}{:} or whatever.
    – cfr
    Commented Sep 12, 2023 at 2:47
  • @mbert Manually setting them back won't work because you'll get the set-back versions when you invoke the definition.
    – cfr
    Commented Sep 12, 2023 at 2:47
2

If you're willing to use another key=value implementation, you could use expkv-cs1, in which you don't have to apply any grouping or default-resetting, because keys are handled by argument forwarding and only during the initial definition or with \ekvcChange the defaults are set/changed.

If you're using the Hash-Variants you can simply use \expanded to get the key values out of \ekvcValue without them further expanding (we need to protect things we don't want to expand with \noexpand or \unexpanded). And you also don't need to care for hash-doubling, expkv-cs handles that for you during the initial assignment and inside of \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}

enter image description here


1Disclaimer: I'm the author of the expkv-bundle of which expkv-cs is a part.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .