1

As question Count and use the number of items in advance mentioned, I have the same demand, but mine is more advanced.

Here I have some enumerates, some of them begin with [resume] paramater, some of them have nested enumerate. I want to compute the number of the first level items of each enumerate. For example

enter image description here

I wrote codes below:

\documentclass{article}
\usepackage{enumitem}
\makeatletter
\newcounter{totalitems}
\newcounter{beginitems}
\newcounter{enditems}
\let\@numerate\enumerate

\def\enumerate{% \setcounter{beginitems}{\arabic{enumi}} @numerate } \let\end@numerate\endenumerate \def\endenumerate{ \end@numerate% \setcounter{enditems}{\arabic{enumi}} \setcounter{totalitems}{\numexpr \c@enditems - \c@beginitems \relax} } \makeatother \begin{document} \begin{enumerate} \item one \item two \end{enumerate} total = \thetotalitems, begin = \thebeginitems, end = \theenditems

\begin{enumerate}[resume]
    \item three
    \begin{enumerate}
        \item a nested enumerate
    \end{enumerate}
\end{enumerate}
total = \thetotalitems, begin = \thebeginitems, end = \theenditems

\begin{enumerate}
    \item one 
    \item two 
\end{enumerate}
total = \thetotalitems, begin = \thebeginitems, end = \theenditems

\end{document}

it produced:

enter image description here

As we can see there are some things goes wrong

  1. When there is nested enumerate, the beginitems counter is wrong
  2. When there is no [resume] paramater, the beginitems counter didn't been reset to 0.

Where should I change to reach my demand?

Syvshc
  • 1,328
  • You need to postpone \setcounter{beginitems}{\arabic{enumi}} to after the enumerate environment was initiated, so essentially after \begin{enumerate} or \begin{enumerate}[resume]. – Jasper Habicht Nov 10 '21 at 07:38
  • yeah, I tried to put it after \@numerate, but this would lead an error: I can't add optional paramaters included [resume] of enumerate evironment, – Syvshc Nov 10 '21 at 07:41
  • It might be easier to use the internal counters: enumi, enumii, enumiii and enumiv. You can also use \theenumi etc. Enumitem still uses them. It also adds a count for a series name: \csname enitdp@\endcsname. – John Kormylo Nov 10 '21 at 17:22
  • REALY Thanks to @JohnKormylo !! I solve this question with your hint. I found the \enit@depth cotrol sequence to test which level is. Could you please write an answer? I'll give you the acceptance, or not I'll update my question and accept @Jasper's answer.Also a huge thanks for @JasperHabicht . – Syvshc Nov 11 '21 at 01:27
  • I assumed this was just a test document to figure out how to get the counters. Go ahead and give it to Jasper. – John Kormylo Nov 11 '21 at 14:22
  • Since Jasper didn't reply for a period of time, I'll answer my question myself – Syvshc Nov 12 '21 at 02:46

2 Answers2

1

You need to postpone \setcounter{beginitems}{\arabic{enumi}} to after the enumerate environment was initiated, so essentially after \begin{enumerate} or \begin{enumerate}[resume] respectively. You need to take into account that \begin{environment} may have one optional argument.

I am not totally sure, but I think, using \LetLtxMacro instead of a plain \let and \renewcommand instead of \def is a bit more secure here:

\documentclass{article}
\usepackage{enumitem}

\makeatletter \newcounter{totalitems} \newcounter{beginitems} \newcounter{enditems}

\usepackage{letltxmacro} \LetLtxMacro{@numerate}{\enumerate} \LetLtxMacro{\end@numerate}{\endenumerate}

\renewcommand{\enumerate}[1][]{% @numerate[#1]% \setcounter{beginitems}{\arabic{enumi}} } \renewcommand{\endenumerate}{ \end@numerate% \setcounter{enditems}{\arabic{enumi}} \setcounter{totalitems}{\numexpr \c@enditems - \c@beginitems \relax} } \makeatother

\begin{document}

\begin{enumerate}
    \item one
    \item two
\end{enumerate}
total = \thetotalitems, begin = \thebeginitems, end = \theenditems

\begin{enumerate}[resume]
    \item three
    \begin{enumerate}
        \item a nested enumerate
    \end{enumerate}
\end{enumerate}
total = \thetotalitems, begin = \thebeginitems, end = \theenditems

\begin{enumerate}
    \item one 
    \item two 
\end{enumerate}
total = \thetotalitems, begin = \thebeginitems, end = \theenditems

\end{document}

enter image description here

Now, you still may have the problem that the counters are affected every time, an enumerate environment starts, which may cause wrong results when enumerate environments are nested. I did not test this thouroughly.

  • 1
    Thanks for your answer! I was always thinking how to check if the optional paramater is resume or not. You saved me from this question. maybe I have to read some books to find what happens when nesting. – Syvshc Nov 10 '21 at 08:11
  • 1
    Since the enumerate environment (if used with the enumitem) package only can take exactly one optional parameter (at least as far as I know), this will more or less cover all use cases. – Jasper Habicht Nov 10 '21 at 08:16
  • 1
    Ah, I know why counters after (a) went wrong. When we use the second level of enumerate, \begin of second level and \end of first level determined the value of beginitems and enditems. Both of them are 3, so we got a 0 of totalitems. – Syvshc Nov 10 '21 at 08:28
  • I wrote an answer which reach my demand, thanks for your answer :) – Syvshc Nov 12 '21 at 03:32
1

THANKS FOR @JohnKormylo and @JasperHabicht

method from @JasperHabicht

I found a control sequence \enit@depth which show the enumerate depth. So I can use \if to test if I'm in the first level, like this:

\renewcommand{\enumerate}[1][]{%
    \@numerate[#1]%
    \ifnum\enit@depth=\@ne
        \setcounter{beginitems}{\arabic{enumi}}
    \fi
}
\renewcommand{\endenumerate}{
    \ifnum\enit@depth=\@ne
        \setcounter{enditems}{\arabic{enumi}}
        \setcounter{totalitems}{\numexpr \c@enditems - \c@beginitems \relax}
    \fi
    \end@numerate%
}

If we do this, when we aren't in the first level, \setcounters won't work. The whole codes are:

\documentclass{article}
\usepackage{enumitem}

\makeatletter \newcounter{totalitems} \newcounter{beginitems} \newcounter{enditems}

\usepackage{letltxmacro} \LetLtxMacro{@numerate}{\enumerate} \LetLtxMacro{\end@numerate}{\endenumerate}

\renewcommand{\enumerate}[1][]{% @numerate[#1]% \ifnum\enit@depth=@ne \setcounter{beginitems}{\arabic{enumi}} \fi } \renewcommand{\endenumerate}{ \ifnum\enit@depth=@ne \setcounter{enditems}{\arabic{enumi}} \setcounter{totalitems}{\numexpr \c@enditems - \c@beginitems \relax} \fi \end@numerate% } \makeatother

\begin{document}

\begin{enumerate}
    \item one
    \item two
\end{enumerate}
total = \thetotalitems, begin = \thebeginitems, end = \theenditems

\begin{enumerate}[resume]
    \item three
    \begin{enumerate}
        \item a nested enumerate
    \end{enumerate}
\end{enumerate}
total = \thetotalitems, begin = \thebeginitems, end = \theenditems

\begin{enumerate}
    \item one 
    \item two 
\end{enumerate}
total = \thetotalitems, begin = \thebeginitems, end = \theenditems

\end{document}

enter image description here

my new method

In this method, I renew the \item command, let the conter plus 1 when \item appears in the top level:

  1. first, we create a newif \ifenum to test if we are in the top level, we create a counter and save the macros:
\newif\ifenum
\newcounter{totalitems}

\usepackage{letltxmacro} \LetLtxMacro{@numerate}{\enumerate} \LetLtxMacro{\end@numerate}{\endenumerate} \LetLtxMacro{\it@m}{\item}

  1. use xparse to renew the \item command, when this \item is in the top level, step the counter totalitems. Also we can let it have some new features, like \item* for not count, \item[<paramater>] for a user-defined label as the previous definition but also count, \item*[paramater] for a user-defined label without count:
\DeclareDocumentCommand{\item}{ s o }{
    \IfNoValueTF{#2}{\it@m}{\it@m[#2]}  
    \ifenum
        \IfBooleanT{#1}{\addtocounter{totalitems}{-1}}%
        \stepcounter{totalitems}
    \fi
}
  1. Then we judge when should we true the \ifenum: after \begin{enumerate}, we test if we are in the top level, if true, set totalitems to 0, and set \enumtrue, if not, set \enumfalse; before \end{enumerate} act oppositely. It doesn't matter when nested list appears, because \ifenum always truns false when the second or deeper level begins.
\renewcommand{\enumerate}[1][]{%
    \@numerate[#1]
    \ifnum\enit@depth=\@ne
        \setcounter{totalitems}{0}
        \enumtrue
    \else
        \enumfalse
    \fi
}
\renewcommand{\endenumerate}{
    \ifnum\enit@depth=\@ne 
        \enumfalse
    \else
        \enumtrue
    \fi
    \end@numerate%
}

Now we can get the number of the top level items:

\documentclass{article}
\usepackage{enumitem}
\newif\ifenum
\makeatletter
\newcounter{totalitems}

\usepackage{letltxmacro} \LetLtxMacro{@numerate}{\enumerate} \LetLtxMacro{\end@numerate}{\endenumerate} \LetLtxMacro{\it@m}{\item}

\DeclareDocumentCommand{\item}{ s o }{ \IfNoValueTF{#2}{\it@m}{\it@m[#2]} \ifenum \IfBooleanT{#1}{\addtocounter{totalitems}{-1}}% \stepcounter{totalitems} \fi }

\renewcommand{\enumerate}[1][]{% @numerate[#1] \ifnum\enit@depth=@ne \setcounter{totalitems}{0} \enumtrue \else \enumfalse \fi } \renewcommand{\endenumerate}{ \ifnum\enit@depth=@ne \enumfalse \else \enumtrue \fi \end@numerate% }

\makeatother

\begin{document}

\begin{enumerate}
    \item one
    \item two
\end{enumerate}
total = \thetotalitems

\begin{enumerate}[resume]
    \item three
    \begin{enumerate}
        \item a nested enumerate
        \item* a star nest
    \end{enumerate}
    \item item after nested
\end{enumerate}
total = \thetotalitems

\begin{enumerate}
    \item* a star version 
    \item[paramater] the only item which is counted
    \item*[paramater] a star version with paramater
\end{enumerate}
total = \thetotalitems

\end{document}

enter image description here

Syvshc
  • 1,328