4

In the accepted answer to Code between \begin{environment} and \end{environment} stored in an array it's shown how to create an automatic list of theorems using expl3 syntax. However the \printtheorems command only works if it's called after the theorems.

Is there a way to call it before the theorems? I was thinking a possible way to do it would be to compile the .tex twice, the first time to get the theorems and the second one to display them (like the Table of Contents does with chapters, sections ecc..).

The code linked above and the corresponding output are:

\documentclass{article}
\usepackage{amsmath}
\usepackage{amsthm}
\usepackage{environ}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\newtheoremx}{momo}
{
  % * Let's duplicate the working of \newtheorem; \newtheoremx should
  %   be used for theorems that need to be listed
  % * \newtheoremx{theorem}{Theorem} will actually do
  %   \newtheorem{theorem@inner}{Theorem} (honoring the usual optional arguments)
  % * We also need a property list to store along with the theorem
  %   the one which it is subordinate to
  \IfValueTF{#2}
   {
    \newtheorem{#1@inner}[#2@inner]{#3}
    % #1 is subordinate to #2
    \prop_gput:Nnn \g_riccardo_theorems_prop { #1 } { #2 }
   }
   {
    \IfValueTF{#4}
     {
      \newtheorem{#1@inner}{#3}[#4]
     }
     {
      \newtheorem{#1@inner}{#3}
     }
    % #1 is not subordinate, store the name itself
    \prop_gput:Nnn \g_riccardo_theorems_prop { #1 } { #1 }
   }
  % define a "grabbing" environment #1 with the usual features 
  \NewEnviron{#1}[1][]
   {
    % start the inner environment (without or with optional argument)
    \tl_if_empty:nTF { ##1 }
     { \begin{#1@inner} }
     { \begin{#1@inner}[##1] }
    % save the statement number
    \tl_gset:Nx \g__riccardo_theorems_number_tl { \use:c {@currentlabel} }
    % typeset the statement
    \BODY
    % end the inner environment
    \end{#1@inner}
   % store the statement in a sequence variable, actually as
   % four arguments as shown
   \seq_gput_right:Nx \g_riccardo_theorems_seq
     {
      { #1 } % name
      { \g__riccardo_theorems_number_tl } % number
      { \exp_not:n { ##1 } } % attribution
      { \exp_not:V \BODY } } % body
    }
 }

% allocate the needed variables
\prop_new:N \g_riccardo_theorems_prop
\seq_new:N \g_riccardo_theorems_seq
\tl_new:N \g__riccardo_theorems_number_tl

% print the stored theorems
\NewDocumentCommand{\printtheorems}{}
 {
  % we need a group where nullifying the action of \label
  \group_begin:
  \cs_set_eq:NN \label \use_none:n
  % map the sequence, passing each item to the function that prints a theorem
  \seq_map_function:NN \g_riccardo_theorems_seq \riccardo_printtheorems:n
  % end the group
  \group_end:
 }

\cs_new_protected:Nn \riccardo_printtheorems:n
 {
  % just pass the argument in the form {name}{number}{attribution}{text}
  % to a four argument function
  \__riccardo_printtheorems:nnnn #1
 }
\cs_new_protected:Nn \__riccardo_printtheorems:nnnn
 {
  % redefine \the<statement>@inner to yield the stored number
  % we use the property list to use the correct counter
  % (for instance, in case of "lemma", <statement> will be "theorem"
  \cs_set:cpn { the \prop_item:Nn \g_riccardo_theorems_prop {#1} @inner } { #2 }
  \tl_if_empty:nTF { #3 }
   {
    % no attribution
    \begin{#1@inner} #4 \end{#1@inner}
   }
   {
% attribution
    \begin{#1@inner}[#3] #4 \end{#1@inner}
   }
 }

\ExplSyntaxOff

\newtheoremx{theorem}{Theorem}[section]
\newtheoremx{lemma}[theorem]{Lemma}

\begin{document}

\section{First test}

\begin{theorem}[Important]\label{thm:important}
This is a theorem about $\log_a x$.
\end{theorem}

\begin{lemma}\label{lem:whatever}
This is a lemma.
\end{lemma}

\begin{theorem}\label{thm:unimportant}
This is another theorem
\end{theorem}

\section{Second test}

\begin{theorem}\label{thm:soandso}
Again a theorem.
\end{theorem}

\section{Theorems}

\printtheorems

\end{document}

enter image description here

EDIT: MWE showing the problem

\documentclass{memoir}
\usepackage{amsmath, amsthm, hyperref, environ, xparse, showkeys}

\ExplSyntaxOn

\NewDocumentCommand{\newtheoremx}{momo}
{
  % * Let's duplicate the working of \newtheorem; \newtheoremx should
  %   be used for theorems that need to be listed
  % * \newtheoremx{theorem}{Theorem} will actually do
  %   \newtheorem{theorem@inner}{Theorem} (honoring the usual optional arguments)
  % * We also need a property list to store along with the theorem
  %   the one which it is subordinate to
  \IfValueTF{#2}
   {
    \newtheorem{#1@inner}[#2@inner]{#3}
    % #1 is subordinate to #2
    \prop_gput:Nnn \g_riccardo_theorems_prop { #1 } { #2 }
   }
   {
    \IfValueTF{#4}
     {
      \newtheorem{#1@inner}{#3}[#4]
     }
     {
      \newtheorem{#1@inner}{#3}
     }
    % #1 is not subordinate, store the name itself
    \prop_gput:Nnn \g_riccardo_theorems_prop { #1 } { #1 }
    \seq_gput_right:Nn \g_riccardo_theorems_counters_seq { #1 }
   }
  % define a "grabbing" environment #1 with the usual features 
  \NewEnviron{#1}[1][]
   {
    % start the inner environment (without or with optional argument)
    \tl_if_empty:nTF { ##1 }
     { \begin{#1@inner} }
     { \begin{#1@inner}[##1] }
    % save the statement number
    \tl_gset:Nx \g__riccardo_theorems_number_tl { \use:c {@currentlabel} }
    % typeset the statement
    \BODY
    % end the inner environment
    \end{#1@inner}
   % store the statement in a sequence variable, actually as
   % four arguments as shown
   \seq_gput_right:Nx \g_riccardo_theorems_seq
     {
      { #1 } % name
      { \g__riccardo_theorems_number_tl } % number
      { \exp_not:n { ##1 } } % attribution
      { \exp_not:V \BODY } }% body
    }
 }

% allocate the needed variables
\prop_new:N \g_riccardo_theorems_prop
\seq_new:N \g_riccardo_theorems_seq
\tl_new:N \g__riccardo_theorems_number_tl
\iow_new:N \g_riccardo_theorems_stream
\seq_new:N \g_riccardo_theorems_counters_seq

% print the stored theorems
\NewDocumentCommand{\printtheorems}{}
 {
  % we need a group where nullifying the action of \label
  \group_begin:
  \cs_set_eq:NN \label \use_none:n
  % map the sequence, passing each item to the function that prints a theorem
  \file_if_exist_input:n { \c_sys_jobname_str.thl }
  % end the group
  \group_end:
  \seq_map_inline:Nn \g_riccardo_theorems_counters_seq
   {
    \setcounter{##1@inner}{0}
   }
 }

\cs_new_protected:Nn \riccardo_theorems_save:
 {
  \seq_map_function:NN \g_riccardo_theorems_seq \__riccardo_theorems_save:n
 }
\cs_new_protected:Nn \__riccardo_theorems_save:n
 {
  \iow_now:Nn \g_riccardo_theorems_stream
   {
    \savedtheorem #1
   }
 }

\AtEndDocument
 {
  \iow_open:Nn \g_riccardo_theorems_stream { \c_sys_jobname_str.thl }
  \riccardo_theorems_save:
  \iow_close:N \g_riccardo_theorems_stream
 }

\NewDocumentCommand{\savedtheorem}{mmmm}
 {
  % redefine \the<statement>@inner to yield the stored number
  % we use the property list to use the correct counter
  % (for instance, in case of "lemma", <statement> will be "theorem"
  \cs_set:cpn { the \prop_item:Nn \g_riccardo_theorems_prop {#1} @inner } { #2 }
  \tl_if_empty:nTF { #3 }
   {
    % no attribution
    \begin{#1@inner} #4 \end{#1@inner}
   }
   {
    % attribution
    \begin{#1@inner}[#3] #4 \end{#1@inner}
   }
 }

\ExplSyntaxOff

\newtheoremx{theorem}{Theorem}[chapter]
\newtheoremx{lemma}[theorem]{Lemma}

\begin{document}

\chapter{Theorems}

\printtheorems

\clearpage
\section{First theorem}

\begin{theorem}[Important] \label{thm_1}
This is a theorem about $\log_a x$.
\end{theorem}
\clearpage

This is a link to \hyperref[thm_1]{Theorem 1}.
\end{document}
5
  • Where did you get the idea that \printtheorems can be used more than once?
    – egreg
    Feb 10, 2019 at 19:01
  • Oh, so the problem has nothing to do with the theorems belonging to different chapters, it's calling \printtheorems twice that's causing the issue, right?
    – noibe
    Feb 10, 2019 at 19:07
  • Just tried removing the second \printtheorems from the MWE I posted, but the theorem in the first chapter is still numbered incorrectly, so I guess different chapters are actually part of the problem.
    – noibe
    Feb 10, 2019 at 19:12
  • adding \setcounter{theorem@inner}{0} before the first theorem of every chapter seems to do the job.
    – noibe
    Feb 10, 2019 at 19:31
  • I fixed the issue with a new version, adding the resetting of the theorem numbers.
    – egreg
    Feb 10, 2019 at 20:58

1 Answer 1

6

The theorems can be saved in a sequence as before; at end document they can be written to an auxiliary file and \printtheorems inputs it (if it exists).

\documentclass{article}
\usepackage{amsmath}
\usepackage{amsthm}
\usepackage{xparse,environ}

\ExplSyntaxOn

\NewDocumentCommand{\newtheoremx}{smomo}
 {
  \IfBooleanTF{#1}
   {
    \printthm_nonumber:nn { #2 } { #4 }
   }
   {
    \printthm_number:nnnn { #2 } { #3 } { #4 } { #5 }
   }
 }

\cs_new_protected:Nn \printthm_nonumber:nn
 {
  \newtheorem*{#1@inner}{#2}
  \NewEnviron{#1}[1][]
   {
    % start the inner environment (without or with optional argument)
    \tl_if_empty:nTF { ##1 }
     { \begin{#1@inner} }
     { \begin{#1@inner}[##1] }
    % typeset the statement
    \BODY
    % end the inner environment
    \end{#1@inner}
    % store the statement in a sequence variable, actually as
    % four arguments as shown
    \seq_gput_right:Nx \g_riccardo_theorems_seq
     {
      *
      { #1 } % name
      { } % number
      \tl_if_novalue:nF { ##1 } { [\exp_not:n { ##1 }] } % attribution
      { \exp_not:V \BODY } % body
     }
   }
 }

\cs_new_protected:Nn \printthm_number:nnnn
 {
  % * Let's duplicate the working of \newtheorem; \newtheoremx should
  %   be used for theorems that need to be listed
  % * \newtheoremx{theorem}{Theorem} will actually do
  %   \newtheorem{theorem@inner}{Theorem} (honoring the usual optional arguments)
  % * We also need a property list to store along with the theorem
  %   the one which it is subordinate to
  \tl_if_novalue:nTF { #2 }
   {
    \tl_if_novalue:nTF { #4 }
     {
      \newtheorem{#1@inner}{#3}
     }
     {
      \newtheorem{#1@inner}{#3}[#4]
     }
    % #1 is not subordinate, store the name itself
    \prop_gput:Nnn \g_riccardo_theorems_prop { #1 } { #1 }
    \seq_gput_right:Nn \g_riccardo_theorems_counters_seq { #1 }
   }
   {
    \newtheorem{#1@inner}[#2@inner]{#3}
    % #1 is subordinate to #2
    \prop_gput:Nnn \g_riccardo_theorems_prop { #1 } { #2 }
   }
  % define a "grabbing" environment #1 with the usual features 
  \NewEnviron{#1}[1][]
   {
    % start the inner environment (without or with optional argument)
    \tl_if_empty:nTF { ##1 }
     { \begin{#1@inner} }
     { \begin{#1@inner}[##1] }
    % save the statement number
    \tl_gset:Nx \g__riccardo_theorems_number_tl { \use:c {@currentlabel} }
    % typeset the statement
    \BODY
    % end the inner environment
    \end{#1@inner}
    % store the statement in a sequence variable, actually as
    % four arguments as shown
    \seq_gput_right:Nx \g_riccardo_theorems_seq
     {
      { #1 } % name
      { \g__riccardo_theorems_number_tl } % number
      \IfValueT{##1}{ [\exp_not:n { ##1 }] } % attribution
      { \exp_not:V \BODY } % body
     }
   }
 }

% allocate the needed variables
\prop_new:N \g_riccardo_theorems_prop
\seq_new:N \g_riccardo_theorems_seq
\tl_new:N \g__riccardo_theorems_number_tl
\iow_new:N \g_riccardo_theorems_stream
\seq_new:N \g_riccardo_theorems_counters_seq

% print the stored theorems
\NewDocumentCommand{\printtheorems}{}
 {
  % we need a group where nullifying the action of \label
  \group_begin:
  \cs_set_eq:NN \label \use_none:n
  % map the sequence, passing each item to the function that prints a theorem
  \file_if_exist_input:n { \c_sys_jobname_str.thl }
  % end the group
  \group_end:
  \seq_map_inline:Nn \g_riccardo_theorems_counters_seq
   {
    \setcounter{##1@inner}{0}
   }
 }

\cs_new_protected:Nn \riccardo_theorems_save:
 {
  \seq_map_function:NN \g_riccardo_theorems_seq \__riccardo_theorems_save:n
 }
\cs_new_protected:Nn \__riccardo_theorems_save:n
 {
  \iow_now:Nn \g_riccardo_theorems_stream
   {
    \savedtheorem #1
   }
 }
\AtEndDocument
 {
  \iow_open:Nn \g_riccardo_theorems_stream { \c_sys_jobname_str.thl }
  \riccardo_theorems_save:
  \iow_close:N \g_riccardo_theorems_stream
 }

\NewDocumentCommand{\savedtheorem}{smmom}
 {
  % redefine \the<statement>@inner to yield the stored number
  % we use the property list to use the correct counter
  % (for instance, in case of "lemma", <statement> will be "theorem"
  \IfBooleanTF{#1}
   {
    \IfNoValueTF{#4}
     {\begin{#2@inner}#5\end{#2@inner}}
     {\begin{#2@inner}[#4]#5\end{#2@inner}}
   }
   {
    \cs_set:cpn { the \prop_item:Nn \g_riccardo_theorems_prop {#2} @inner } { #3 }
    \IfNoValueTF{#4}
     {
      % no attribution
      \begin{#2@inner} #5 \end{#2@inner}
     }
     {
      % attribution
      \begin{#2@inner}[#4] #5 \end{#2@inner}
     }
   }
 }

\ExplSyntaxOff

\newtheoremx*{theorem*}{Theorem}
\newtheoremx{theorem}{Theorem}[section]
\newtheoremx{lemma}[theorem]{Lemma}

\begin{document}

\section{Theorems}

\printtheorems

\section{First test}

\begin{theorem*}
This is not numbered.
\end{theorem*}

\begin{theorem}[Important]\label{thm:important}
This is a theorem about $\log_a x$.
\end{theorem}

\begin{lemma}\label{lem:whatever}
This is a lemma.
\end{lemma}

\begin{theorem}\label{thm:unimportant}
This is another theorem
\end{theorem}

\section{Second test}

\begin{theorem}\label{thm:soandso}
Again a theorem.
\end{theorem}

\end{document}

enter image description here

The following version requires xparse released 2019-03-05 or later.

\documentclass{article}
\usepackage{amsmath}
\usepackage{amsthm}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\newtheoremx}{smomo}
 {
  \IfBooleanTF{#1}
   {
    \printthm_nonumber:nn { #2 } { #4 }
   }
   {
    \printthm_number:nnnn { #2 } { #3 } { #4 } { #5 }
   }
 }

\cs_new_protected:Nn \printthm_nonumber:nn
 {
  \newtheorem*{#1@inner}{#2}
  \NewDocumentEnvironment{#1}{ o +b }
   {
    % start the inner environment (without or with optional argument)
    \IfNoValueTF { ##1 }
     { \begin{#1@inner} }
     { \begin{#1@inner}[##1] }
    % typeset the statement
    ##2
    % end the inner environment
    \end{#1@inner}
    % store the statement in a sequence variable, actually as
    % four arguments as shown
    \seq_gput_right:Nx \g_riccardo_theorems_seq
     {
      *
      { #1 } % name
      { } % number
      \tl_if_novalue:nF { ##1 } { [\exp_not:n { ##1 }] } % attribution
      { \exp_not:n { ##2 } } % body
     }
   }
   {}
 }

\cs_new_protected:Nn \printthm_number:nnnn
 {
  % * Let's duplicate the working of \newtheorem; \newtheoremx should
  %   be used for theorems that need to be listed
  % * \newtheoremx{theorem}{Theorem} will actually do
  %   \newtheorem{theorem@inner}{Theorem} (honoring the usual optional arguments)
  % * We also need a property list to store along with the theorem
  %   the one which it is subordinate to
  \tl_if_novalue:nTF { #2 }
   {
    \tl_if_novalue:nTF { #4 }
     {
      \newtheorem{#1@inner}{#3}
     }
     {
      \newtheorem{#1@inner}{#3}[#4]
     }
    % #1 is not subordinate, store the name itself
    \prop_gput:Nnn \g_riccardo_theorems_prop { #1 } { #1 }
    \seq_gput_right:Nn \g_riccardo_theorems_counters_seq { #1 }
   }
   {
    \newtheorem{#1@inner}[#2@inner]{#3}
    % #1 is subordinate to #2
    \prop_gput:Nnn \g_riccardo_theorems_prop { #1 } { #2 }
   }
  % define a "grabbing" environment #1 with the usual features 
  \NewDocumentEnvironment{#1}{ o +b }
   {
    % start the inner environment (without or with optional argument)
    \IfNoValueTF{##1}
     { \begin{#1@inner} }
     { \begin{#1@inner}[##1] }
    % save the statement number
    \tl_gset:Nx \g__riccardo_theorems_number_tl { \use:c {@currentlabel} }
    % typeset the statement
    ##2
    % end the inner environment
    \end{#1@inner}
   % store the statement in a sequence variable, actually as
   % four arguments as shown
   \seq_gput_right:Nx \g_riccardo_theorems_seq
     {
      { #1 } % name
      { \g__riccardo_theorems_number_tl } % number
      \IfValueT{##1}{ [\exp_not:n { ##1 }] } % attribution
      { \exp_not:n { ##2 } } } % body
    }
    {}
 }

% allocate the needed variables
\prop_new:N \g_riccardo_theorems_prop
\seq_new:N \g_riccardo_theorems_seq
\tl_new:N \g__riccardo_theorems_number_tl
\iow_new:N \g_riccardo_theorems_stream
\seq_new:N \g_riccardo_theorems_counters_seq

% print the stored theorems
\NewDocumentCommand{\printtheorems}{}
 {
  % we need a group where nullifying the action of \label
  \group_begin:
  \cs_set_eq:NN \label \use_none:n
  % map the sequence, passing each item to the function that prints a theorem
  \file_if_exist_input:n { \c_sys_jobname_str.thl }
  % end the group
  \group_end:
  \seq_map_inline:Nn \g_riccardo_theorems_counters_seq
   {
    \setcounter{##1@inner}{0}
   }
 }

\cs_new_protected:Nn \riccardo_theorems_save:
 {
  \seq_map_function:NN \g_riccardo_theorems_seq \__riccardo_theorems_save:n
 }
\cs_new_protected:Nn \__riccardo_theorems_save:n
 {
  \iow_now:Nn \g_riccardo_theorems_stream
   {
    \savedtheorem #1
   }
 }
\AtEndDocument
 {
  \iow_open:Nn \g_riccardo_theorems_stream { \c_sys_jobname_str.thl }
  \riccardo_theorems_save:
  \iow_close:N \g_riccardo_theorems_stream
 }

\NewDocumentCommand{\savedtheorem}{smmom}
 {
  % redefine \the<statement>@inner to yield the stored number
  % we use the property list to use the correct counter
  % (for instance, in case of "lemma", <statement> will be "theorem"
  \IfBooleanTF{#1}
   {
    \IfNoValueTF{#4}
     {\begin{#2@inner}#5\end{#2@inner}}
     {\begin{#2@inner}[#4]#5\end{#2@inner}}
   }
   {
    \cs_set:cpn { the \prop_item:Nn \g_riccardo_theorems_prop {#2} @inner } { #3 }
    \IfNoValueTF{#4}
     {
      % no attribution
      \begin{#2@inner} #5 \end{#2@inner}
     }
     {
      % attribution
      \begin{#2@inner}[#4] #5 \end{#2@inner}
     }
   }
 }

\ExplSyntaxOff

\newtheoremx*{theorem*}{Theorem}
\newtheoremx{theorem}{Theorem}[section]
\newtheoremx{lemma}[theorem]{Lemma}

\begin{document}

\section{Theorems}

\printtheorems

\section{First test}

\begin{theorem*}
This is not numbered.
\end{theorem*}

\begin{theorem}[Important]\label{thm:important}
This is a theorem about $\log_a x$.
\end{theorem}

\begin{lemma}\label{lem:whatever}
This is a lemma.
\end{lemma}

\begin{theorem}\label{thm:unimportant}
This is another theorem
\end{theorem}

\section{Second test}

\begin{theorem}\label{thm:soandso}
Again a theorem.
\end{theorem}

\end{document}
3
  • Thanks @egreg for the script, it works wonderfully! However, I found out by accident that this script isn't compatible with the thmtools package. When thmtools is imported, the numbering printed by \printtheorems goes awry. I demonstrate this bug occurring here: overleaf.com/read/ffrwkpymbpdx . (Overleaf lets users edit and compile directly in their browser.) Would you happen to have an idea why the numbering would be printed wrongly?
    – davidleejy
    Sep 2 at 18:10
  • @davidleejy Sorry, this is definitely not compatible with thmtools.
    – egreg
    Sep 2 at 19:51
  • Hi @egreg, not a problem, thanks for responding and sharing your script on stackexchange. And yes, I understand that it can take quite some effort to work a script into compatibility with some package. I'm a stranger to the Expl3 language and was wondering if it'd be not too much of a hassle to ask what your script does? Does it add a hook to the creation of the "theorem" environment so that whenever a theorem environment is instantiated, it is also stored in some variable, and then when \printtheorems is called, this variable is consulted, the elements traversed and printed?
    – davidleejy
    Sep 3 at 7:29

You must log in to answer this question.

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