1

Within my report I want do define several structural damages and I want to automatically generate a summary table at the beginning of the document. Here is an example of how far I have come so far and what I want to achieve.

\documentclass{article}

\usepackage{etoolbox}
\usepackage{pgffor}

\parindent=0pt

\newcounter{cntClassA}\newcounter{cntClassB}\newcounter{cntClassC}\newcounter{cntAddTmp}

\makeatletter

\newcommand\defineDamageKey[4] { \expandafter\edef\csname damage.#1.#2.#3\endcsname{#4} }

\newcommand{\addDamage}[3]{%
%
    \ifstrequal{#1}{A}{\stepcounter{cntClassA} \setcounter{cntAddTmp}{\thecntClassA} }{}%
    \ifstrequal{#1}{B}{\stepcounter{cntClassB} \setcounter{cntAddTmp}{\thecntClassB} }{}%
    \ifstrequal{#1}{C}{\stepcounter{cntClassC} \setcounter{cntAddTmp}{\thecntClassC} }{}%
%
    \defineDamageKey{#1}{\thecntAddTmp}{description}{#2}%
    \defineDamageKey{#1}{\thecntAddTmp}{solution}{#3}%
    \defineDamageKey{#1}{\thecntAddTmp}{ID}{#1-\thecntAddTmp}%
%
    \printDamage{#1}{\thecntAddTmp}%
}

\newcommand{\printDamage}[2]{%
    \textbf{ID:} \@nameuse{damage.#1.#2.ID}\\%
    \textbf{Damage class:} #1\\%
    \textbf{Description:} \@nameuse{damage.#1.#2.description}\\%
    \textbf{Solution:} \@nameuse{damage.#1.#2.solution}\\%
}

\newcommand{\damageSummary}[1]{%
    \newcount\damageCountForClass
%
    \ifstrequal{#1}{A}{ \damageCountForClass=\thecntClassA }{}%
    \ifstrequal{#1}{B}{ \damageCountForClass=\thecntClassB }{}%
    \ifstrequal{#1}{C}{ \damageCountForClass=\thecntClassC }{}%
%
    \foreach \n in {1,...,\damageCountForClass}%
    {%
        \textbf{\@nameuse{damage.#1.\n.ID}:} \@nameuse{damage.#1.\n.description}\\
    }%
}

\makeatother

\begin{document}

\section{Building 1}

\addDamage{A}{Broken window}{Fix window}

\addDamage{B}{Wrong wall paint}{Repaint}


\section{Building 2}

\addDamage{C}{Leaky windows}{Seal windows}

\addDamage{A}{Defective electrics}{Fix electrics}


\section{Summary}

\damageSummary{A}
\damageSummary{B}
\damageSummary{C}


\end{document}

enter image description here

My basic idea was to define my data as key/value pairs with structured named macros:

\@namedef{damage.A.1.description}{description damage A-1}
\@namedef{damage.A.1.solution}{solution damage A-1}
\@namedef{damage.A.2.description}{description damage A-2}

But now I want to have the summary table at the beginning of the document! Am I right that this only works with AUX files?

I've just started with low level TeX / macro writing and tried to figure out how to do this by examining the todonotes package (as the \todo and \listoftodos would possible a similar mechanism). But I dont get it.

So is my current solution actual a good starting point or do I need a different approach? Could anyone give me a hint how to achieve this?

3
  • welcome to TeX.SE!
    – Mensch
    Jul 28, 2021 at 15:01
  • I'm not able to directly answer your question, but I want to offer how I might approach something like this that might make your life easier. Coming from a Python background, I would likely manage this all in LuaLaTeX (similar syntax, easy to use data structures likes tables). You could manage a csv with your buildings/damages, use LuaLaTeX to parse it to a table, and write functions to print the summary, or individual damages. You could leverage some other software to more easily view and edit your csv file as well. Jul 28, 2021 at 15:03
  • I haven't read the code, but 1) archiving this can be done with, e.g., dar :) and 2) if you want to build this in one pass, you can gather the data before printing the summary (e.g., \begin{building}{Building 1} \addDamage{A}... \addDamage{B}... \end{building} \begin{building}{Building 2} \addDamage{C}... \addDamage{A}... \end{building} \printSummary \generateMainContents . As usual, having good data structures helps a lot.
    – frougon
    Jul 28, 2021 at 15:30

3 Answers 3

2

Edit: hyperref-support is added in case you wish hyperlinks from the summary to the place of the document where the damage is described.

If you don't mind having an auxiliary-file whose name is of pattern ⟨expansion of jobname⟩.Damageclass⟨name of damage-class⟩ for each damage-class, then you can probably do with LaTeX 2ε's \addtocontents-\@starttoc-meachanism. I used description-environments for listing the items. How to tweak the description-environment depends on the documentclass in use and may turn out tricky.

The example needs to be compiled three times without having auxiliary-files deleted between runs until everything matches out correctly. (First run creates auxiliary files. Second run creates description-item-labes of summaries and stores measurements in the aux-file. Third run uses measurements from aux-file.)

\documentclass{article}
\usepackage{hyperref}

\makeatletter
\newcommand\maxIDwidth{0pt}%
\AtBeginDocument{\providecommand\r@maxIDwidthpreviousrun{0pt}}%
\AtEndDocument{%
  \protected@write\@auxout{}{\string\newlabel{maxIDwidthpreviousrun}{\maxIDwidth}}%
}%
\DeclareRobustCommand\damageSummaryItem[3]{%
  \item[%
    \setbox\scratchbox\hbox{#1}%
    \ifdim\maxIDwidth<\wd\scratchbox\xdef\maxIDwidth{\the\wd\scratchbox}\fi
    \rlap{\hbox{\@ifundefined{hyperlink}{\@secondoftwo}{\hyperlink}{#3}{#1}}}%
    \hbox to\r@maxIDwidthpreviousrun{\hfill}%
  ]%
  #2%
}%
\newbox\scratchbox
\newcommand\addDamage[3]{%
   %#1 - Damage class
   %#2 - Damage description
   %#3 - Fix/Solution description
   \@ifundefined{c@Damageclass@#1}{%
     \newcounter{Damageclass@#1}%
     \expandafter\gdef\csname theDamageclass@#1\endcsname{#1\mbox{-}\arabic{Damageclass@#1}}%
   }{}%
   \begingroup
   \begin{description}%
   \itemsep=0pt
   \parskip=0pt
   \item[{\refstepcounter{Damageclass@#1}ID:%
     \addtocontents{Damageclass#1}{%
       \damageSummaryItem{\csname theDamageclass@#1\endcsname:}{#2}{\@ifundefined{@currentHref}{}{\@currentHref}}\protected@file@percent
     }%
   }]\csname theDamageclass@#1\endcsname
   \item[{Damage class:}]#1%
   \item[{Description:}]#2%
   \item[{Solution:}]#3%
   \end{description}%
   \endgroup
}%
\newcommand\damageSummary[1]{%
  \begingroup
  \IfFileExists{\jobname.Damageclass#1}{%
    \@ifundefined{hyperlink}{\@firstoftwo}{%
      {\expandafter}\expandafter\ifx\csname hyper@last\endcsname\relax
      \expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
    }%
  }{\@secondoftwo}%
  {%
    \begin{description}%
    \itemsep=0pt
    \parskip=0pt
    \@starttoc{Damageclass#1}%
    \par\vskip-\@topsepadd\vskip-\parskip
    \end{description}%
  }{\@starttoc{Damageclass#1}}%
  \endgroup
}%
\makeatother

\begin{document}

\section{Summary}
\damageSummary{A}
\damageSummary{B}
\damageSummary{C}

\section{Building 1}

\addDamage{A}{Broken window}{Fix window}

\addDamage{B}{Wrong wall paint}{Repaint}

\section{Building 2}

\addDamage{C}{Leaky windows}{Seal windows}

\addDamage{A}{Defective electrics}{Fix electrics}

\end{document}

enter image description here

The question "Use LaTeX to produce parts lists?" and the answers to it might be of interest to you— a similar problem is addressed there. In the answers you find a different approach which is based on the package datatool and a .csv-file.

1

You're right: you need an auxiliary file.

The following code will warn if the .dam file differs from the preceding run and tell you to rerun LaTeX.

\documentclass{article}

\ExplSyntaxOn

% the user interface
\NewDocumentCommand{\addDamage}{mmm}
 {% #1 = class, #2 = type, #3 = action
  \cryptkeeper_damage_add:nnn { #1 } { #2 } { #3 }
 }
\NewDocumentCommand{\printDamageSummary}{}
 {
  \section*{Summary}
  \cryptkeeper_damage_print:
 }
% the auxiliary command
\NewDocumentCommand{\damageSummary}{mmm}
 {
  \cryptkeeper_damage_add_summary:nnn { #1 } { #2 } { #3 }
 }

% the lower level code

% variables
\iow_new:N \g_cryptkeeper_damage_iow
\prop_new:N \g_cryptkeeper_damage_prop
\str_new:N \g_cryptkeeper_damage_hash_str
\int_new:N \g_cryptkeeper_damage_class_A_int
\int_new:N \g_cryptkeeper_damage_class_B_int
\int_new:N \g_cryptkeeper_damage_class_C_int
\seq_new:N \g_cryptkeeper_damage_id_seq

% actions for setup
\AtBeginDocument
 {
  \file_if_exist:nT { \c_sys_jobname_str.dam }
   {% the file exists, save its hash and input it
    \str_gset:Nx \g_cryptkeeper_damage_hash_str
     {
      \file_mdfive_hash:n { \c_sys_jobname_str.dam }
     }
    \file_input:n { \c_sys_jobname_str.dam }
   }
  \iow_open:Nn \g_cryptkeeper_damage_iow { \c_sys_jobname_str.dam }
 }
\AtEndDocument
 {
  \iow_close:N \g_cryptkeeper_damage_iow
  \str_if_eq:eeF
   { \g_cryptkeeper_damage_hash_str } % saved hash
   { \file_mdfive_hash:n { \c_sys_jobname_str.dam } } % new hash
   {% if differ, issue a message
    \msg_warning:nn { cryptkeeper/damage } { file differ }
   }
 }

% messages
\msg_new:nnn { cryptkeeper/damage } { file differ }
 {
  .dam~file~modified.~Rerun~LaTeX.
 }

\cs_new_protected:Nn \cryptkeeper_damage_add:nnn
 {
  % increment the counter
  \int_gincr:c { g_cryptkeeper_damage_class_#1_int }
  % write a the .dam file
  \iow_now:Nx \g_cryptkeeper_damage_iow
   {
    \exp_not:N \damageSummary % the command
    {#1} % the class
    { \int_use:c { g_cryptkeeper_damage_class_#1_int } } % the number
    { \exp_not:n { #2 } } % the damage
   }
  % print the data
  \par\addvspace{\topsep}
  \noindent
  \textbf{ID:}~#1-\int_use:c { g_cryptkeeper_damage_class_#1_int } \\*
  \textbf{Damage~class:}~#1 \\*
  \textbf{Description:}~#2 \\*
  \textbf{Solution:}~#3
  \par\addvspace{\topsep}
 }

\cs_new_protected:Nn \cryptkeeper_damage_add_summary:nnn
 {
  % save the ID in the form {1|2|3}000+damage number
  % class A is 1, and so on
  % the sequence will be sorted by \printSummary
  \seq_gput_right:Nx \g_cryptkeeper_damage_id_seq
   { \int_eval:n { \int_from_alph:n { #1 } * 1000 + #2 } }
  % save the data in a property list
  \prop_gput:Nen \g_cryptkeeper_damage_prop
   { \int_eval:n { \int_from_alph:n { #1 } * 1000 + #2 } }
   {
    \noindent\textbf{#1-#2:}~#3\par
   }
 }
\cs_generate_variant:Nn \prop_gput:Nnn { Ne }

\cs_new_protected:Nn \cryptkeeper_damage_print:
 {
  % sort the sequence
  \seq_sort:Nn \g_cryptkeeper_damage_id_seq
   {
    \int_compare:nTF { ##1 > ##2 } { \sort_return_swapped: } { \sort_return_same: }
   }
  % map the sequence and print the data stored in the property list
  \seq_map_inline:Nn \g_cryptkeeper_damage_id_seq
   {
    \prop_item:Nn \g_cryptkeeper_damage_prop { ##1 }
   }
 }

\ExplSyntaxOff

\begin{document}

\printDamageSummary

\section{Building 1}

\addDamage{A}{Broken window}{Fix window}

\addDamage{B}{Wrong wall paint}{Repaint}


\section{Building 2}

\addDamage{C}{Leaky windows}{Seal windows}

\addDamage{A}{Defective electrics}{Fix electrics}

\end{document}

Note that \printSummary can go anywhere.

enter image description here

0

Using memory package to store arbitrary free-form n-dimensional data, using tocloft package to collect the summary info on disk via \addtocontents command (although \addcontentsline also works and gives TOC page numbers), and creating the summary by using really simplistic expl3 syntax to do nested loops through the classes, class counters and todos (for the sorting):

summary

Currently hard-coded in places, but can be made dynamically extendable (e.g. creating fresh class counters, or calculating loop end-points).

And with memory package, adding extra dimensions, like a building field, becomes trivial.

MWE

\documentclass{article}
\usepackage[table]{xcolor}
\usepackage{tocloft}
\newlistof{todos}{ltd}{Summary}
\usepackage{memory}
\usepackage{etoolbox}
\usepackage{xparse}



\newcounter{todoitem}
\newcounter{classcounterA}
\newcounter{classcounterB}
\newcounter{classcounterC}
\newcounter{classcounterZ}
\newcounter{classcountertemp}


\ExplSyntaxOn


\int_new:N \l_myclassfirst_int
\int_new:N \l_myclasslast_int
\int_new:N \l_myclasscountmax_int
\int_new:N \l_mytempc_int
\int_new:N \l_mytempd_int


\int_new:N \l_myloopcount_int
\int_new:N \l_myloopcountcc_int
\int_new:N \l_mytodoloopcount_int
\int_new:N \l_mytodocountmax_int



\str_new:N \l_mytemp_str
\str_new:N \l_mytempb_str
\str_new:N \l_mytempc_str
\str_new:N \l_mytempd_str
\str_new:N \l_mytodototal_str

\NewDocumentCommand { \summarytodos } { } {

  %
    \int_set:Nn \l_myclassfirst_int { 
                                                                \int_from_alph:n { A }
                                                                                                 }

    \int_set:Nn \l_myclasslast_int { 
                                                                \int_from_alph:n { Z }
                                                                                                 }

  
   %
   \int_set:Nn \l_myloopcount_int { \c_zero_int }
   
   % loop through the classes (A-Z)
   \int_do_while:nNnn 
                    { \l_myloopcount_int } <  { \l_myclasslast_int } 
                    { 
                        \int_set:Nn 
                                    \l_myloopcount_int 
                                    { \l_myloopcount_int + 1 }

                 { 
                   % loop through the class counters
                   \int_set:Nn \l_myloopcountcc_int { \c_one_int }
                   \int_set:Nn \l_myclasscountmax_int { 15 } %
                   \int_do_while:nNnn 
                                    { \l_myloopcountcc_int } <  { \l_myclasscountmax_int } 
                                    { 
                                     { 

%--------------- loop through all the todo items
%--------------- if there is a match on class + classcounter
%---------------    then print               
%                   \alltodos[total]
                    \str_set:Nx \l_mytofototal_str { 
                                        \cs:w @alltodos@total@memory\cs_end:
                                                            }

                    \int_set:Nn \l_mytodoloopcount_int { \c_one_int }
                    \int_set:Nn \l_mytodocountmax_int { \l_mytofototal_str + 1} 
                   \int_do_while:nNnn 
                                    { \l_mytodoloopcount_int } 
                                    <  
                                    { \l_mytodocountmax_int } 
                                    { %true
                                    
                                    %match on class:
\str_set:Nx \l_mytemp_str { 
                    \cs:w @blist@\int_use:N\l_mytodoloopcount_int,\lbdimb @memory\cs_end:
                                        }
                                                
\str_set:Nx \l_mytempb_str { 
                                                    \int_to_Alph:n { \l_myloopcount_int }
                                                }
                                                
  %
\str_if_eq:NNTF
                                     \l_mytempb_str
                                     \l_mytemp_str
                {                                   
                                    %match on class counter:
                    \str_set:Nx \l_mytempc_str { 
                                        {\cs:w @blist@\int_use:N\l_mytodoloopcount_int,\lbdimc @memory\cs_end:}
                                                            }
                                                                    
                    \str_set:Nx \l_mytempd_str { 
                                                                        { \int_eval:n { \l_myloopcountcc_int } }
                                                                    }
                                                                    
                      %
                    \str_if_eq:NNTF
                                                         \l_mytempc_str
                                                         \l_mytempd_str
        {                                   

                        % add to summary
            \addtocontents{ltd}{%        
%                           List:
                    {\bfseries
%%              :\cs:w 
%%                      @blist@\int_use:N\l_mytodoloopcount_int,\lbdima @memory
%%                      \cs_end:
                \cs:w 
                        @blist@\int_use:N\l_mytodoloopcount_int,\lbdimb @memory
                        \cs_end:
                        -
                \cs:w 
                        @blist@\int_use:N\l_mytodoloopcount_int,\lbdimc @memory
                        \cs_end:
                        }%end bfseries
                        :~~ % space
                \cs:w 
                        @blist@\int_use:N\l_mytodoloopcount_int,\lbdimd @memory
                        \cs_end:
%%%%                :\cs:w 
%%%                     @blist@\int_use:N\l_mytodoloopcount_int,\lbdime @memory
%%%                     \cs_end:
%%%             :\cs:w 
%%%                     @blist@\int_use:N\l_mytodoloopcount_int,\lbdimf @memory
%%%                     \cs_end:
                    \par
                    }% end addtocontens
                        }{}% end match on class counter
                        }{}% end match on class
                        \int_set:Nn 
                                    \l_mytodoloopcount_int 
                                    { \l_mytodoloopcount_int + \c_one_int }


                                            }{}% todo loop

                                                }{}% class counter loop

                        \int_set:Nn 
                                    \l_myloopcountcc_int 
                                    { \l_myloopcountcc_int + \c_one_int }
                 
                                }% class counter loop
                 }
                 {  }

                        
                        
                    } % end class loop
                    
                        \addtocontents{ltd}{\protect\mbox{}\protect\hrulefill\par}
   
}

\ExplSyntaxOff



\newcommand\lbdimbuilding{building}
\newcommand\lbdimdclass{dclass}
\newcommand\lbdimdclasscounter{dclasscounter}
\newcommand\lbdimdescription{description}
\newcommand\lbdimsolution{solution}
\newcommand\lbdimstatus{status}

\newdata*{blist}
\newdata{alltodos}
\newdata{tempblist}





%-------------------------------
\newcommand\lbdima{\lbdimbuilding}
\newcommand\lbdimb{\lbdimdclass}
\newcommand\lbdimc{\lbdimdclasscounter}
\newcommand\lbdimd{\lbdimdescription}
\newcommand\lbdime{\lbdimsolution}
\newcommand\lbdimf{\lbdimstatus}

\newcommand\lbclasscountername{}



%-------------------------------
\newcommand\addtodo[4]{%
    \stepcounter{todoitem}
\blist[\thetodoitem,\lbdima]={#1}
\blist[\thetodoitem,\lbdimb]={#2}
\expandafter\renewcommand\expandafter\lbclasscountername{classcounter#2}
%\lbclasscountername
\stepcounter{\lbclasscountername}
\setcounter{classcountertemp}{\value{\lbclasscountername}}
\blist[\thetodoitem,\lbdimc] := {\theclasscountertemp}%{\lbclasscountername}
\blist[\thetodoitem,\lbdimd]={#3}
\blist[\thetodoitem,\lbdime]={#4}
\blist[\thetodoitem,\lbdimf]={To do}
\alltodos[total]=\thetodoitem
\printtodoitem{\thetodoitem}
}


%-------------------------------
\newcommand\printtodoitem[1]{%
\begin{tabular}{ll}
\rowcolor{blue!20}
{\Large {\phantom{L}}} Item & \\
\hline
%\blist[\thetodoitem,\lbdima]
\bfseries ID & \blist[\thetodoitem,\lbdimb]-\blist[\thetodoitem,\lbdimc] \\
\bfseries Class & \blist[\thetodoitem,\lbdimb]\\
\bfseries Description & \blist[\thetodoitem,\lbdimd]\\
\bfseries Solution & \blist[\thetodoitem,\lbdime]
%\blist[\thetodoitem,\lbdimf]
\end{tabular}
\hspace{1in}
%\par
%\bigskip
}


%-------------------------------
\newcommand\showalltodostotal{\alltodos[total]\ items in total.}


\begin{document}
\listoftodos


\section{b1}

\addtodo{(b1)}{A}{Broken window}{Fix window}
\addtodo{(b1)}{B}{Wrong wall paint}{Repaint}

\section{b2}

\addtodo{(b2)}{C}{Leaky windows}{Seal windows}
\addtodo{(b2)}{A}{Defective electrics}{Fix electrics}


\section{b3}

\addtodo{(b3)}{Z}{Too sunny}{Put sunshade up}
\addtodo{(b3)}{Z}{No solar panels}{Install solar panels}

\section{b4}

\showalltodostotal
\summarytodos

\end{document}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.