Associate counters -- stepping (many) counters simultaneously
Just for completeness, here's an implementation using expl3
:
\documentclass{book}
\usepackage{xparse}
\usepackage{blindtext}
\usepackage{xcolor}
\usepackage{totcount}
\ExplSyntaxOn
\NewDocumentCommand{\DeclareAssociatedCounters}{mm}
{
\hupfer_declareassociatedcounters:nn { #1 } { #2 }
}
\NewDocumentCommand{\AddAssociatedCounters}{mm}
{
\hupfer_addassociatedcounters:nn { #1 } { #2 }
}
\NewDocumentCommand{\RemoveAssociatedCounters}{mm}
{
\hupfer_removeassociatedcounters:nn { #1 } { #2 }
}
\NewDocumentCommand{\ClearAssociatedCountersList}{m}
{
\hupfer_declareassociatedcounters:nn { #1 } { }
}
\cs_new_protected:Nn \hupfer_declareassociatedcounters:nn
{
\clist_clear_new:c { g_hupfer_bound_counters_#1_clist }
\hupfer_addassociatedcounters:nn { #1 } { #2 }
}
\cs_new_protected:Nn \hupfer_addassociatedcounters:nn
{
\clist_gput_right:cn { g_hupfer_bound_counters_#1_clist } { #2 }
% or a slower routine if we want to check the items are counter names
%\clist_map_inline:nn { #2 }
% {
% \cs_if_exist:cTF { c@##1 }
% {
% \clist_gput_right:cn { g_hupfer_bound_counters_#1_clist } { ##1 }
% }
% {
% \ERROR{NOT A COUNTER}
% }
% }
%%%%
\clist_gremove_duplicates:c { g_hupfer_bound_counters_#1_clist }
% remove accidental #1 from the list
\clist_gremove_all:cn { g_hupfer_bound_counters_#1_clist } { #1 }
}
\cs_new_protected:Nn \hupfer_removeassociatedcounters:nn
{
\clist_map_inline:nn { #2 }
{
\clist_gremove_all:cn { g_hupfer_bound_counters_#1_clist } { ##1 }
}
}
\cs_new_protected:Npn \hupfer_stepcounter:n #1
{
\hupfer_orig_stepcounter:n { #1 }
\clist_if_exist:cT { g_hupfer_bound_counters_#1_clist }
{
\clist_map_inline:cn { g_hupfer_bound_counters_#1_clist }
{
\hupfer_orig_stepcounter:n { ##1 }
}
}
}
\AtBeginDocument
{
% save a copy of \stepcounter
\cs_set_eq:NN \hupfer_orig_stepcounter:n \stepcounter
% use the new one
\cs_set_eq:NN \stepcounter \hupfer_stepcounter:n
}
\ExplSyntaxOff
\newcommand{\ShowNiceCounterOutput}[5]{%
\begin{center}
\begin{tabular}{llll}
& & & \\
& \multicolumn{3}{c}{totcount page values} \\
Page & \textcolor{red}{#1} & \textcolor{blue}{#2} & \textcolor{gray}{#3} \\
\thepage & \textcolor{red}{\number\totvalue{#1}} &
\textcolor{blue}{\number\totvalue{#2}} & \textcolor{gray}{\number\totvalue{#3}} \\
& & & \\
& \multicolumn{3}{c}{totcount section values} \\
Section & \textcolor{red}{#4} & \textcolor{blue}{#5} \\
\thesection & \textcolor{red}{\number\totvalue{#4}} &
\textcolor{blue}{\number\totvalue{#5}} & \\
& & & \\
\end{tabular}
\end{center}%
}
\ExplSyntaxOn
\NewDocumentCommand{\QuickOutput}{m}
{
\prg_replicate:nn { #1 }
{
\pagenumbering{arabic}% -> pagenumber reset to zero , on purpose inside the loop
\ShowNiceCounterOutput{page}{totalpages}{anotherpagescounter}{section}{totalsections}
\blindtext
\newpage% some pages
}
}
\ExplSyntaxOff
\begin{document}
\regtotcounter{page} % Register a total value counter --> this will be the driver counter
\newtotcounter{totalpages}% 1st driven counter
\newtotcounter{anotherpagescounter}% 2nd driven counter
\newtotcounter{yetanotherpagescounter}% 3rd driven counter
\regtotcounter{section}
\newtotcounter{totalsections}
\DeclareAssociatedCounters{page}{totalpages,anotherpagescounter}% Register the driver and the driven counters
\DeclareAssociatedCounters{section}{section,totalsections}% Register the driver and the driven counters for sections%
\AddAssociatedCounters{page}{yetanotherpagescounter}% Register the driver and the driven counters
\pagenumbering{Roman}
\chapter{The first chapter}
\section{My first section}
% Generate dummy output
\QuickOutput{10}
\section{My second section}
\QuickOutput{10}
\pagenumbering{arabic}% -> pagenumber reset to zero
\appendix
\chapter{First Appendix chapter}
\section{My (only) appendix section}
% Generate dummy output, again ;-)
\QuickOutput{10}
% Clear the list of counters associated to page
\ClearAssociatedCountersList{page}
% Not necessary, only for removing some counters from list
\RemoveAssociatedCounters{page}{totalpages,anotherpagescounter}
% Readd the counter totalpages to the list --> in this context,
% It acts, as if it has not been removed at all
\DeclareAssociatedCounters{page}{totalpages}
\QuickOutput{20}
\end{document}
Update 2017/03/05 The assoccnt
package has been superseded by xassoccnt
and is available as version 1.3
on CTAN -- this package is much more powerful than assoccnt
-- please don't use assoccnt
any longer and switch to xassoccnt
.
As egreg stated: the LaTeX kernel does not provide such a feature and perhaps there is other package doing this feature, so I have decided to provide a small package, being uploaded to CTAN and perhaps available soon.
The assoccnt.sty
file
\NeedsTeXFormat{LaTeX2e}%
\ProvidesPackage{assoccnt}[2014/08/13 v0.2 -- Associate counters stepping]%
%%%
%% License: LaTeX Project Public License
%% Copyright (2014) Dr. Christian Hupfer
%% Author: Christian Hupfer [email protected]
%%
%%
%%%%
%%% Some options later on
%%%%
\RequirePackage{etoolbox}[2011/01/03 2.2]%
\let\@@assoccnt@standardstepcounter\stepcounter%
\let\@@assoccnt@standardrefstepcounter\refstepcounter%
% Just for a quick suffix
\newcommand{\@@associatedcounterslistsuffix}{%
AssociatedCountersList%
}%
%%% Internal macro to generate the name of the list of associated counters
\newcommand{\@@assoccnt@@generatelistname}[1]{%
% #1 Name of the driver counter
@#1\@@associatedcounterslistsuffix
}%
\newcommand{\@@assoccnt@@addassociatedcounter}[2]{%
\ifcsdef{#1}{%
\ifinlistcs{#2}{#1}{%
% Do nothing, since counter is already in the list
}{%
\listcsadd{#1}{#2}%
}%
}{%
% Nothing in here
}%
}%
% This command defines a list of counters, that should be stepped also if the driver counter
% is `\stepcounter`ed.
% A self - association is not possible, as this would lead to inconsistent counting
\newcommand{\DeclareAssociatedCounters}[2]{%
% #1 --> driver counter
% #2 --> CSV list of other counters, that should be stepped, if the driver counter is stepped
\ifcsdef{\@@assoccnt@@generatelistname{#1}}{%
% % Nothing to be done --> List already exists
\GenericWarning{}{Warning: List of associated counters for counter #1 already exists}%
}{%
\csgdef{\@@assoccnt@@generatelistname{#1}}{}% Define some global list
}%
% Now add the counter names from #2 to the list
% Note: Currently, it is not checked whether a counter is already added!
\forcsvlist{\@@assoccnt@@addassociatedcounter{\@@assoccnt@@generatelistname{#1}}}{#2}%
% Now remove an accidental self-association
\RemoveAssociatedCounter{#1}{#1}%
}%
%%% A generic macro, that removes a list entry from the list by
%%% defining a temporary list and omitting the
\newcommand{\@@assoccnt@removefromlist}[2]{%
% #1 list name
% #2 element to be removed
\ifcsdef{#1}{%
\gdef\@@mytemplist{}%
\renewcommand*{\do}[1]{%
\ifstrequal{##1}{#2}{
% Later one some success routine etc. ???
}{%
\listgadd{\@@mytemplist}{##1}%
}%
}%
\dolistcsloop{#1}%
\csxdef{#1}{\@@mytemplist}%
}{%
% The list is not defined at all, can't remove something from something not existing...
}%
}%
%% Remove a particular counter from the list
\newcommand{\RemoveAssociatedCounter}[2]{%
% #1 arg: driver counter
% #2 arg: counter name to be removed
\@@assoccnt@removefromlist{\@@assoccnt@@generatelistname{#1}}{#2}%
}%
%% Remove a CSV list of counters from the list of associated counters
\newcommand{\RemoveAssociatedCounters}[2]{%
% #1 arg: driver counter
% #2 arg: CSV list of counters to be removed
\forcsvlist{\RemoveAssociatedCounter{#1}}{#2}%
}%
%% Remove all associated counters from the list
\newcommand{\ClearAssociatedCountersList}[1]{%
% #1 arg: driver counter
\csundef{\@@assoccnt@@generatelistname{#1}}%
}%
%% Test if a counter is an associated counter of driver counter
\newcommand{\IfIsAssociatedCounter}[4]{%
% #1 arg: driver counter
% #2 arg: (possibly) associated counter
% #3 arg: Code for execution on true branch
% #4 arg: Code for execution on false branch
\ifcsdef{\@@assoccnt@@generatelistname{#1}}{%
\ifinlistcs{#2}{\@@assoccnt@@generatelistname{#1}}{%
#3%
}{%
#4%
}%
}{% List does not exist, so it's not associated
#4%
}%
}%
%%% The stepcounter wrapper for the standard stepcounter command
%%% This is just for convenience, if the command as to be improved/extended later on in future
\newcommand{\@@assoccnt@stepcounter}[1]{%
\@@assoccnt@standardstepcounter{#1}%
}%
%%% Not needed so far
\newcommand{\@@assoccnt@refstepcounter}[1]{%
\@@assoccnt@standardrefstepcounter{#1}%
}%
%%%% Redefinition of the \stepcounter command
\renewcommand{\stepcounter}[1]{%
\@@assoccnt@standardstepcounter{#1}% Traditional behaviour, since this is expected!
\ifcsdef{\@@assoccnt@@generatelistname{#1}}{% Check first, whether the list exists at all,
\forlistcsloop{\@@assoccnt@stepcounter}{\@@assoccnt@@generatelistname{#1}}% March through the list
}{%
%No list -> do nothing at all
}%
}%
%%%% Redefinition of the \stepcounter command
\renewcommand{\refstepcounter}[1]{%
\@@assoccnt@standardstepcounter{#1}% Traditional behaviour, since this is expected!
\protected@edef\@currentlabel%
{\csname p@#1\endcsname\csname the#1\endcsname}%
\ifcsdef{\@@assoccnt@@generatelistname{#1}}{% Check first, whether the list exists at all,
\forlistcsloop{\@@assoccnt@stepcounter}{\@@assoccnt@@generatelistname{#1}}% March through the list
}{%
%No list -> do nothing at all
}%
}%
\endinput%
And some test driver file
\documentclass{book}%
\usepackage{totcount}%
\usepackage{assoccnt}%
% Some packages only for output and dummy pages
\usepackage{blindtext}%
\usepackage{forloop}%
\usepackage{xcolor}%
\newcounter{loopcounter}%
\newcommand{\ShowNiceCounterOutput}[5]{%
\begin{center}%
\begin{tabular}{llll}%
& & & \tabularnewline
& \multicolumn{3}{c}{totcount page values} \tabularnewline
Page & \textcolor{red}{#1} & \textcolor{blue}{#2} & \textcolor{gray}{#3} \tabularnewline
\thepage & \textcolor{red}{\number\totvalue{#1}} & \textcolor{blue}{\number\totvalue{#2}} & \textcolor{gray}{\number\totvalue{#3}} \tabularnewline
& & & \tabularnewline
& \multicolumn{3}{c}{totcount section values} \tabularnewline
Section & \textcolor{red}{#4} & \textcolor{blue}{#5} \tabularnewline
\thesection & \textcolor{red}{\number\totvalue{#4}} & \textcolor{blue}{\number\totvalue{#5}} & \tabularnewline
& & & \tabularnewline
\end{tabular}
\end{center}%
}%
\newcommand{\QuickOutput}[1]{%
%
\forloop{loopcounter}{1}{\value{loopcounter} < \numexpr #1+1}{%
\pagenumbering{arabic}% -> pagenumber reset to zero , on purpose inside the loop
\ShowNiceCounterOutput{page}{totalpages}{anotherpagescounter}{section}{totalsections}%
\blindtext%
\newpage% some pages
}%
}%
\begin{document}
\regtotcounter{page} % Register a total value counter --> this will be the driver counter
\newtotcounter{totalpages}% 1st driven counter
\newtotcounter{anotherpagescounter}% 2nd driven counter
\newtotcounter{yetanotherpagescounter}% 3rd driven counter
\regtotcounter{section}%
\newtotcounter{totalsections}
\DeclareAssociatedCounters{page}{totalpages,anotherpagescounter}% Register the driver and the driven counters
\DeclareAssociatedCounters{section}{section,totalsections}% Register the driver and the driven counters for sections%
\DeclareAssociatedCounters{page}{yetanotherpagescounter}% Register the driver and the driven counters
\pagenumbering{Roman}%
\chapter{The first chapter}%
\section{My first section}%
% Generate dummy output
\QuickOutput{10}%
\section{My second section}%
\QuickOutput{10}%
\pagenumbering{arabic}% -> pagenumber reset to zero
\appendix
\chapter{First Appendix chapter}%
\section{My (only) appendix section}%
% Generate dummy output, again ;-)
\QuickOutput{10}%
% Clear the list of counters associated to page
\ClearAssociatedCountersList{page}%
% Not necessary, only for removing some counters from list
\RemoveAssociatedCounters{page}{totalpages,anotherpagescounter}%
% Readd the counter totalpages to the list --> in this context,
% It acts, as if it has not been removed at all
\DeclareAssociatedCounters{page}{totalpages}
\QuickOutput{20}%
\end{document}
The output of the driver is basically the same as in the snapshot of the question.
Notes
- There is no test so far whether the arguments of the several commands are counter (names) at all
- There is no test that two (or more) associated counters are not mutally associated counters.
I will provide updates and improvements continously.