2

I have a document where I describe how to renovate my house. Now someone wants an additional document (in the appendix and/or as extra CSV file) with parts lists of all windows and doors and so on.

How can I produce a table and/or a CSV file of macros that were called inside the document?

E.g. I would like to write a document like this:

\documentclass[final,a4paper,11pt,oneside,openany]{memoir}

\usepackage[naustrian]{babel}

\usepackage[thmmarks]{ntheorem}
\usepackage{thm-restate}

\newcounter{reqcounter}
\newtheorem{RQ}[reqcounter]{Neu}

\begin{document}
\chapter{Dachgeschoss}
\section{Wohnzimmer}
% I am searching for a macro to be used like:
%\fenster{130/150}{1 flg Drehkipp rechts}{Fixrahmen von innen abnehmbar}{}

\appendix
% After using \fenster several times, I can create a list somewhere else in the document:
%\listoffenster
\end{document}

having locally an effect like following LaTeX code, RQ being defined as theorem to get a number for each requirement:

\begin{RQ}
Fenster mit 3-Scheiben Isolierverglasung, mit Rolladen und Fensterbank. %fixed text
Ausmaße: 130/150. % from 1st argument
Ausführung: 1 flg Drehkipp rechts % from 2nd argument
Insektenschutz: Fixrahmen von innen abnehmbar % from 3rd argument
% from 4th argument
\textit{Farbe: Innen Weiß, Außen weiß; Beschlag: Silber.} %fixed text
\end{RQ}

and also creating a table in the appendix (using \listoffenster in the example above) and/or external CSV file with (first line is the static header):

Floor;Room;Size;Ausführung;Insektenschutz;Kommentar
Dachgeschoss;Wohnzimmer;130/150;1 flg Drehkipp rechts;Fixrahmen von innen abnehmbar;

Where "Dachgeschoss" is the chapter" and "Wohnzimmer" is the section where the macro was invoked.

9
  • 1
    Why would you use theorem? A table (float) environment wrapped around a tabular environment seems more straightforward. And then, \NewDocumentCommand{\Fenster}{mmmm}{<common code using #1, #2, #3, #4>}.
    – Erwann
    Commented Apr 4, 2021 at 7:11
  • I used theorems to get a number for each requirement I have. The table is supposed to be in the appendix, not at the place where I use the macro. I'll clarify this in the question.
    – markus23
    Commented Apr 4, 2021 at 9:39
  • 1
    The inverse is easier. Produce a CSV file of the parts and use the datatool package to create your instructions. Commented Apr 4, 2021 at 13:59
  • @JeffreyJWeimer Thank you for the idea. But naively used it also has drawbacks: 1. The floor&room cannot be automatically determined? 2. I need to specify within the CSV by number? (which will be a huge mess when the CSV is being edited). Can you give an example how to use it? (Maybe also mitigating the drawbacks.)
    – markus23
    Commented Apr 5, 2021 at 11:25
  • Please revise to provide a self-contained minimum working example (MWE). Your file does not compile (undefined control sequence \fenster). You can create the CSV file with any set of information that you want/need. Here is a line with six arguments: Fenster mit 3-Scheiben Isolierverglasung; mit Rolladen und Fensterbank, 130/150, 1 flg Drehkipp rechts, Fixrahmen von innen abnehmbar, , Farbe: Innen Weiß, Außen weiß; Beschlag: Silber. You can pull from this CSV file whatever you need/want in whatever order. An added advantage is to manage the CSV file in a database app. Commented Apr 5, 2021 at 13:32

2 Answers 2

3

Due to the 30000-character-limit I needed to split my answer in two parts.

This is part 2 of my answer.

Part 1 contains considerations, explanations and thoughts.

Part 2 contains a compilable example which is referred in part 1.

If you wish to upvote, please upvote only one of the two parts. This prevents unfair reputation-gain. If you wish to downvote, dovnvote whichever part(s) you like to downvote.


The promised example:

%\listfiles
%\errorcontextlines=10000
\documentclass[final,a4paper,11pt,oneside,openany]{memoir}
%\documentclass[final,a4paper,11pt,oneside,openany]{book}
\usepackage{datatool}
\usepackage{xparse}
\usepackage{hyperref}
\usepackage{atveryend}
\usepackage{rerunfilecheck}

\usepackage{verbatim}

\makeatletter
%========================================================================
%  \immediategroup{<balanced text>}%
%------------------------------------------------------------------------
%  A local scope wherein <balanced text> is carried out while \write, 
%  \closeout and \openout are patched to work in terms of \immediate.
%------------------------------------------------------------------------
\newcommand\immediategroup[1]{%
  \begingroup
  \@ifdefinable\MyStuffsavedwrite{\let\MyStuffsavedwrite\write}%
  \@ifdefinable\MyStuffsavedopenout{\let\MyStuffsavedopenout\openout}%
  \@ifdefinable\MyStuffsavedcloseout{\let\MyStuffsavedcloseout\closeout}%
  \def\write{\immediate\MyStuffsavedwrite}%
  \def\openout{\immediate\MyStuffsavedopenout}%
  \def\closeout{\immediate\MyStuffsavedcloseout}%
  #1%
  \endgroup
}%
%========================================================================
%  \DATABASEFILECreateIfNotExistent{<base-name of databases and of .csv-file>}%
%                                  {<separator>}%
%                                  {<delimiter>}%
% - Creates database of name: <base-name of databases and of .csv-file>DataFromPreviousLaTeXRun
% - Creates database of name: <base-name of databases and of .csv-file>DataToBeWrittenAtEndOfThisLaTeXRun
%   These databases exist in TeX's memory during the LaTeX-run.
% - Triggers the writing of file  <base-name of databases and of .csv-file>.csv 
%   at the end of the LaTeX-run from the content of the database 
%   <base-name of databases and of .csv-file>DataToBeWrittenAtEndOfThisLaTeXRun
% - Initializes Counter-macro \<base-name of databases and of .csv-file>DataToBeWrittenAtEndOfThisLaTeXRunCnt
%   to 0.   That counter-macro is to hold the value of the field "PrimaryKey" of the data-base 
%   <base-name of databases and of .csv-file>DataToBeWrittenAtEndOfThisLaTeXRun 
%   and is incremented always right before writing another line/row to the that database.
%
% The memory-database <base-name of databases and of .csv-file>DataFromPreviousLaTeXRun is
% used for retrieving data. In case the file  <base-name of databases and of .csv-file>.csv 
% exists that database is initialized by reading that file. Otherwise it is
% initialized empty.It is not modified during the LaTeX-run.
% The data stems from the previous LaTeX-run.
%
% The memory-database <base-name of databases and of .csv-file>DataToBeWrittenAtEndOfThisLaTeXRun is
% used for writing/storing entries during the LaTeX-run. It is initialized empty.
% It is modified during the LaTeX-run. At the end of the LaTeX-run it is saved as
% file <base-name of databases and of .csv-file>.csv .
%
% At the end of the LaTeX-run both databases are compared. If they differ, then
% something has changed and you are informed that you need to do another LaTeX-run.
%
% <separator> is the separator of values in the .csv-file. Usually this is comma (,)
% , but in your question you specified that you wish to use semicolon (;).
%
% <delimiter>: In case a value contains <separator> it needs to be nested
% between two instances of <delimiter>. <delimiter> usually is the quotes-character (").
%------------------------------------------------------------------------
\newcommand\DATABASEFILECreateIfNotExistent[3]{%
  \DTLifdbexists{#1DataFromPreviousLaTeXRun}{}{%
    \begingroup
    \DTLsetseparator{#2}%
    \DTLsetdelimiter{#3}%
    \IfFileExists{#1.csv}{%
      \DTLloaddb{#1DataFromPreviousLaTeXRun}{#1.csv}%
    }{%
      \DTLgnewdb{#1DataFromPreviousLaTeXRun}%
    }%
    \endgroup
  }%
  \DTLifdbexists{#1DataToBeWrittenAtEndOfThisLaTeXRun}{}{%
    \DTLgnewdb{#1DataToBeWrittenAtEndOfThisLaTeXRun}%
    % --- Counter-macro for primary-key of database ---
    \expandafter\@ifdefinable\csname#1DataToBeWrittenAtEndOfThisLaTeXRunCnt\endcsname{%
      \expandafter\long\expandafter\gdef\csname#1DataToBeWrittenAtEndOfThisLaTeXRunCnt\endcsname{0}%
    }%
    % -------------------------------------------------
    \AfterLastShipout{%
      % This will save the ..DataToBeWrittenAtEndOfThisLaTeXRun-
      % database to file after the last \shipout. The last \shipout 
      % is triggered by \end{document}/\enddocument.
      \immediategroup{%
        \DTLsetseparator{#2}%
        \DTLsetdelimiter{#3}%
        \RerunFileCheck{#1.csv}{}{Database `#1' may have changed. Rerun LaTeX to get listings of database-entries etc right}%
        \DTLsavedb{#1DataToBeWrittenAtEndOfThisLaTeXRun}{#1.csv}%
      }%
      %% Check if content of database-token-register stemming from
      %% database-file of previous LaTeX-run differs from content of
      %% database-token-register stemming from database-file of 
      %% the current LaTeX-run.
      %% If so, something changed and you need to re-run LaTeX.
      %% Probably this should be omitted with large databases.
      %% Therefore this is turned into comments and a tesr via
      %% \RerunFileCheck is used instead.
      %\begingroup
      %\DTLsetseparator{#2}%
      %\DTLsetdelimiter{#3}%
      %\DTLgcleardb{#1DataToBeWrittenAtEndOfThisLaTeXRun}%
      %\DTLnewdbonloadfalse
      %\DTLloaddb{#1DataToBeWrittenAtEndOfThisLaTeXRun}{#1.csv}%
      %\edef\tempa{%
      %  \the\csname dtldb@#1DataFromPreviousLaTeXRun\endcsname
      %  \the\csname dtlkeys@#1DataFromPreviousLaTeXRun\endcsname
      %  \the\csname dtlrows@#1DataFromPreviousLaTeXRun\endcsname
      %  \the\csname dtlcols@#1DataFromPreviousLaTeXRun\endcsname
      %}%
      %\edef\tempb{%
      %  \the\csname dtldb@#1DataToBeWrittenAtEndOfThisLaTeXRun\endcsname
      %  \the\csname dtlkeys@#1DataToBeWrittenAtEndOfThisLaTeXRun\endcsname
      %  \the\csname dtlrows@#1DataToBeWrittenAtEndOfThisLaTeXRun\endcsname
      %  \the\csname dtlcols@#1DataToBeWrittenAtEndOfThisLaTeXRun\endcsname
      %}%
      %\ifx\tempa\tempb
      %  \endgroup
      %\else
      %  \endgroup
      %  \@latex@warning@no@line{%
      %     Database `#1' may have changed.
      %     Rerun to get listings of entries etc right%
      %   }%
      %\fi
    }%
  }%
}%
%=========================================================================
% Infrastructure for triggering a warning in case the value of a document-
% wide property was requested at a moment in time when the property didn't
% exist (yet).
%-------------------------------------------------------------------------
\AfterLastShipout{\PropertyValuesNotFound}%
\newcommand*\PropertyValuesNotFound{}%
\newcommand*\PropertyValuesNotFoundTrue{%
  \gdef\PropertyValuesNotFound{\@latex@warning@no@line {There were undefined properties}}%
}%
\ExplSyntaxOn
%=========================================================================
% Property-List for properties that are to be maintained document-wide
% during the LaTeX-run. Values can change during the LaTeX-run.
% If you want to store the values which some properties have at a specific
% moment in time during the LaTeX-run, you can use the macro
% \DatabaseRowFromSomeOfThisDocumentsProperties
% for storing these properties' current values as another row of a
% database of name <base-name of databases and of .csv-file>DataToBeWrittenAtEndOfThisLaTeXRun
%-------------------------------------------------------------------------
\prop_new:N {\g__MyStuff_ThisDocumentsProperties_prop}
\prop_gput:Nnn \g__MyStuff_ThisDocumentsProperties_prop{Primary-Key}{0}
%=========================================================================
% expl3-scratch-variables:
%-------------------------------------------------------------------------
\tl_new:N {\l__MyStuff_ExtractedProperty_tl}
\clist_new:N{\l__MyStuff_PropertyNames_clist}
\bool_new:N \g__MyStuff_NewDatabaseRow_bool
\cs_new:Nn \__MyStuff_ExpandedDatabaseName: {}
%=========================================================================
% Set document-wide properties' values _globally_ by providing a
% <property1>=<value1>, <property2>=<value2>, ..., <propertyN>=<valueN>-list:
% \SetSomeOfThisDocumentsPropertiesFromKeyValList{%
%    <property1>=<value1>, <property2>=<value2>, ..., <propertyN>=<valueN>
% }%
%
% If a property doesn't exist it is created anew automatically.
% If a property does exist, its value is overridden.
%-------------------------------------------------------------------------
\NewDocumentCommand{\SetSomeOfThisDocumentsPropertiesFromKeyValList}{m}{
  \keyval_parse:NNn \__MyStuff_GSetSomeOfThisDocumentsPropertiesFromKeyValList:n
                    \__MyStuff_GSetSomeOfThisDocumentsPropertiesFromKeyValList:nn
                    {#1}
}
\cs_new:Nn \__MyStuff_GSetSomeOfThisDocumentsPropertiesFromKeyValList:n { 
  \__MyStuff_GSetSomeOfThisDocumentsPropertiesFromKeyValList:nn {#1}{\DTLstringnull}
}
\cs_new:Nn \__MyStuff_GSetSomeOfThisDocumentsPropertiesFromKeyValList:nn {
  \group_begin:
  \cs_set:Npn \protect { \noexpand\protect\noexpand }
  \exp_args:Nnx \use:n 
                { 
                  \exp_args:Nnx \use:n 
                                { \prop_gput:Nnn \g__MyStuff_ThisDocumentsProperties_prop }
                                {#1} 
                }
                {#2}
  % \prop_gput:Nxx \g__MyStuff_ThisDocumentsProperties_prop{#1}{#2}
  \group_end:
} 
%=========================================================================
% Create a row of a database 
% <base-name of databases and of .csv-file>DataToBeWrittenAtEndOfThisLaTeXRun
% from current values of document-wide properties by specifying a comma-
% separated list of property-names.
% Names of properties will be used as names of fields/columns of the database.
% If a field/column doesn't yet exist in the database, it will be created automatically.
% If the database itself doesn't exist, it will be created automatically
% by \DATABASEFILECreateIfNotExistent. (That's why you need to specify
% <separator> and <delimiter>.)
% If a property doesn't exist, an error-message is raised and datatool's
% null-value for strings is provided as value to the database.
%
%  \DatabaseRowFromSomeOfThisDocumentsProperties{<base-name of databases and of .csv-file>}%
%                                               {<separator>}%
%                                               {<delimiter>}%
%                                               {<Property1>, <Property2>,..., <PropertyN>}%
%
%-------------------------------------------------------------------------
\NewDocumentCommand{\DatabaseRowFromSomeOfThisDocumentsProperties}{mmmm}{
  \bool_gset_true:N \g__MyStuff_NewDatabaseRow_bool
  \cs_set:Nx \__MyStuff_ExpandedDatabaseName: {#1DataToBeWrittenAtEndOfThisLaTeXRun}
  \exp_args:Nx  \DATABASEFILECreateIfNotExistent{#1}{#2}{#3}
  \clist_set:Nn \l__MyStuff_PropertyNames_clist {#4}
  \cs_gset:cpx {\__MyStuff_ExpandedDatabaseName: Cnt} 
               {\number\numexpr\use:c{\__MyStuff_ExpandedDatabaseName: Cnt}+1\relax} 
  \exp_args:Nnx \use:n 
                { 
                  \prop_gput:Nnn \g__MyStuff_ThisDocumentsProperties_prop {PrimaryKey} 
                }
                {\use:c{\__MyStuff_ExpandedDatabaseName: Cnt}}
  % \prop_gput:Nxx \g__MyStuff_ThisDocumentsProperties_prop {PrimaryKey} {\use:c{\__MyStuff_ExpandedDatabaseName: Cnt}}
  \__MyStuff_DatabaseEntryFromThisDocumentsProperty:n {PrimaryKey}
  \clist_map_function:NN \l__MyStuff_PropertyNames_clist \__MyStuff_DatabaseEntryFromThisDocumentsProperty:x
}
\cs_new:Nn \__MyStuff_DatabaseEntryFromThisDocumentsProperty:n {
  \prop_get:NnN \g__MyStuff_ThisDocumentsProperties_prop {#1} \l__MyStuff_ExtractedProperty_tl
  \exp_args:NV \quark_if_no_value:nTF \l__MyStuff_ExtractedProperty_tl {
    \protect\PropertyValuesNotFoundTrue
    % I am too lazy to delve into expl3's l3msg
    \@latex@warning{Macro~ \token_to_str:N\DatabaseRowFromSomeOfThisDocumentsProperties:\space
    Property~`#1'~undefined~-~using~value~\DTLstringnull\space instead~-}
    \tl_set:Nn \l__MyStuff_ExtractedProperty_tl {\DTLstringnull}
  }{}
  \__MyStuff_DatabaseRowFromPropertyAndValue:onV \__MyStuff_ExpandedDatabaseName: {#1} \l__MyStuff_ExtractedProperty_tl
}
\cs_generate_variant:Nn \__MyStuff_DatabaseEntryFromThisDocumentsProperty:n {x}
\cs_new:Nn \__MyStuff_DatabaseRowFromPropertyAndValue:nnn {
  \DTLifhaskey{#1}{#2}{}{
    \DTLaddcolumn{#1}{#2}
  }
  \bool_if:NTF \g__MyStuff_NewDatabaseRow_bool{
     \DTLnewrow{#1}
  }{}
  \bool_gset_false:N \g__MyStuff_NewDatabaseRow_bool
  \group_begin:
  % \cs_set:Npn \protect { \noexpand\protect\noexpand }
  \cs_set:Npn \protect { \token_to_str:N  }
  \exp_args:Nnx \use:n {\DTLnewdbentry{#1}{#2}} {#3}
  \group_end:
}
\cs_generate_variant:Nn \__MyStuff_DatabaseRowFromPropertyAndValue:nnn{onV}
%=========================================================================
% Retrieve the current value of a document-wide property.
%
%  \GetDocumentsPropertyValue{<Property>}
%
% If a property doesn't exist, an error-message is raised and the
% tokens  \textsf{??}  are provided.
%
% Not used in the following code. But I wasn't aware of that at the time
% of composing all this. Probably it may be useful in other scenarios.
%-------------------------------------------------------------------------
\NewDocumentCommand{\GetDocumentsPropertyValue}{m}{
  \__GetDocumentsPropertyValue:x{#1}
}
\cs_new:Nn \__GetDocumentsPropertyValue:n {
  \prop_get:NnN \g__MyStuff_ThisDocumentsProperties_prop {#1} \l__MyStuff_ExtractedProperty_tl
  \exp_args:NV \quark_if_no_value:nTF \l__MyStuff_ExtractedProperty_tl {
    \protect\PropertyValuesNotFoundTrue
    % I am too lazy to delve into expl3's l3msg
    \@latex@warning{Macro~\token_to_str:N\GetDocumentsPropertyValue:\space
    Property~`#1'~undefined~-~using~value~\token_to_str:N\textsf{??}~instead~-}
    \tl_set:Nn \l__MyStuff_ExtractedProperty_tl {\textsf{??}}
  }{}
  \tl_use:N \l__MyStuff_ExtractedProperty_tl
}
\cs_generate_variant:Nn \__GetDocumentsPropertyValue:n {x}
%=========================================================================
%  Via datatool-package's \DTLforeach iterate on the rows
%  of the database  <base-name of databases and of .csv-file>DataFromPreviousLaTeXRun.
%
%  If the database itself doesn't exist, it will be created automatically
%  by \DATABASEFILECreateIfNotExistent. (That's why you need to specify
%  <separator> and <delimiter>.)
%
% \DoWithDatabaseProperties{<base-name of databases and of .csv-file>}%
%                          {<separator>}%
%                          {<delimiter>}%
%                          [<condition>]%
%                          {<assign list>}%
%                          {<text>}%
%
% <base-name of databases and of .csv-file>, <separator> and <delimiter> are
% the same as above. 
% <condition>, <assign list> and <text> are the same as with \DTLforeach
% of the datatool-package.
%-------------------------------------------------------------------------
\NewDocumentCommand\DoWithDatabaseProperties{mmmo}{
  \exp_args:Nx \DATABASEFILECreateIfNotExistent {#1}{#2}{#3}
  \IfNoValueTF{#4}{\exp_args:Nnx \use:n {\DTLforeach*}}
                  {\exp_args:Nnx \use:n {\DTLforeach*[{#4}]}}
                  {#1DataFromPreviousLaTeXRun}
}
\ExplSyntaxOff
\makeatother
%=========================================================================
%  Now the individual building blocks of the infrastructure for managing 
%  document-wide properties via databases and .csv files are in place. 
%  They can now be used for implementing the macros at user level, which
%  is straightforward:
%=========================================================================
% \ListOf...-commands can easily be implemented in terms of
% \DoWithDatabaseProperties.
%
% A scratch-switch which can be initialized by the \ListOf...-command
% and toggled by \DoWithDatabaseProperties's <text>-argument. This 
% switch is intended to indicate whether at least one row of the 
% database already ended up as an entry of the list.
% 
% E.g., only in this case a heading for the list in question is needed.
% Only in this case starting/ending some environment is needed, e.g., 
% description or tabular.
%
\newif\ifNoDatabaseEntryEndedUpInTheListYet
\global\NoDatabaseEntryEndedUpInTheListYettrue
%=========================================================================
% An example of how to implement \ListOfFenster by means of
% nested description-environments and \DoWithDatabaseProperties (whose
% underlying macro is \DTLforeach):
%-------------------------------------------------------------------------
\newcommand\ListOfFenster{%
  \global\NoDatabaseEntryEndedUpInTheListYettrue
  \DoWithDatabaseProperties{Parts}{;}{"}%
                           [{\equal{Fenster}{\ArtDesTeils}}]%
                           {%
                              \PrimaryKey=PrimaryKey,%
                              \NummerDesTeils=NummerDesTeils,%
                              \ArtDesTeils=ArtDesTeils,%
                              \Stockwerkname=Stockwerkname,%
                              \Zimmer=Zimmer,%
                              \Ausmasze=Ausmasze,%
                              \Ausfuehrung=Ausfuehrung,%
                              \Insektenschutz=Insektenschutz,%
                              \Kommentar=Kommentar,%
                              \Destination=Destination%
                           }%
                           {%
                             \ifNoDatabaseEntryEndedUpInTheListYet
                                \global\NoDatabaseEntryEndedUpInTheListYetfalse
                                \section*{Liste der Fenster}%
                                \begin{description}%
                             \fi
                             \item[\hyperlink
                                   {\Destination}%
                                   {%
                                     \PrintAssignListElement\NummerDesTeils 
                                     ~(\PrintAssignListElement\Stockwerkname, 
                                     \PrintAssignListElement\Zimmer)%
                                   }]\hfill\null
                             \begin{description}%
                             \AssignListElementDescriptionItem{\Ausmasze}{Maße}%
                             \AssignListElementDescriptionItem{\Ausfuehrung}{Ausführung}%
                             \AssignListElementDescriptionItem{\Insektenschutz}{Insektenschutz}%
                             \AssignListElementDescriptionItem{\Kommentar}{Kommentar}%
                             \end{description}%
                           }%
  \ifNoDatabaseEntryEndedUpInTheListYet\else\end{description}\fi
}%
\newcommand\PrintAssignListElement[1]{\DTLifnull{#1}{\textsf{??}}{#1}}%
\newcommand\AssignListElementDescriptionItem[2]{%
  \DTLifnullorempty{#1}{%
    \DTLifnull{#1}{\item[{#2:}]\PrnProp\textsf{??}}{}%
  }{%
    \item[{#2:}]#1%
  }%
}%
%=========================================================================
% Some elements of your database of parts shall come from the arguments
% of commands like \chapter or \section.
% One could patch these commands to also fulfill the purpose of
% setting the corresponding document-wide properties' values.
% Instead I define variants of these commands with slightly different
% names which set the corresponding document-wide properties' values
% and call the corresponding sectioning-commands:
%=========================================================================
\newcommand\FloorChapter[1]{%
  \SetSomeOfThisDocumentsPropertiesFromKeyValList{Stockwerkname=#1}%
  \chapter{#1}%
}
\newcommand\RoomSection[1]{%
  \SetSomeOfThisDocumentsPropertiesFromKeyValList{Zimmer=#1}%
  \section{#1}%
}%
%=========================================================================
% The \Fenster-Command sets some document-wide properties from its
% arguments, delivers an RQ-environment, sets some other document-wide 
% property from the current value of the counter underlying the RQ-environment
% and then creates a(nother) database-row from the current values of
% the document-wide properties:
%=========================================================================
\newcommand\Fenster[4]{%
  \SetSomeOfThisDocumentsPropertiesFromKeyValList{%
     ArtDesTeils=Fenster, 
     Ausmasze=#1, Ausfuehrung=#2, Insektenschutz=#3, Kommentar=#4%
  }%
  \begin{RQ}
  Fenster mit 3-Scheiben Isolierverglasung, mit Rolladen und Fensterbank.\\
  Ausmaße: #1.\\
  Ausführung: #2.\\
  Insektenschutz: #3.\\
  \textit{Farbe: Innen Weiß, Außen weiß; Beschlag: Silber.}%
  \ifcat$\detokenize{#4}$\else\\Kommentar: #4\fi
  \end{RQ}
  \SetSomeOfThisDocumentsPropertiesFromKeyValList{%
      NummerDesTeils=Neu\protect~\thereqcounter,
      Destination=\csname @currentHref\endcsname
  }%
  \DatabaseRowFromSomeOfThisDocumentsProperties{Parts}{;}{"}{%
     NummerDesTeils, ArtDesTeils, Stockwerkname, Zimmer, Ausmasze, 
     Ausfuehrung, Insektenschutz, Kommentar, Destination,
  }%
}%


\usepackage[naustrian]{babel}

\usepackage[thmmarks]{ntheorem}
\usepackage{thm-restate}

\newcounter{reqcounter}
\newtheorem{RQ}[reqcounter]{Neu}

\begin{document}

\ListOfFenster

\FloorChapter{Erdgeschoss}
\RoomSection{Toilette}
\Fenster{50/80}{1 flg Keinkipp mitte}{Fixrahmen von aussen einsehbar}{Kommentar zu diesem Fenster}

\FloorChapter{Dachgeschoss}
\RoomSection{Wohnzimmer}
\Fenster{130/150}{1 flg Drehkipp rechts}{Fixrahmen von innen abnehmbar}{}

This is the content of the automatically created .csv-file \verb|Parts.csv|\,:

\begingroup

\tiny

\verbatiminput{Parts.csv}

\endgroup

And once more:

\ListOfFenster

\end{document}

Images of the .pdf-output:

enter image description here enter image description here enter image description here

The file Parts.csv, which is written automatically during LaTeX-runs:

PrimaryKey;NummerDesTeils;ArtDesTeils;Stockwerkname;Zimmer;Ausmasze;Ausfuehrung;Insektenschutz;Kommentar;Destination
1;Neu~1;Fenster;Erdgeschoss;Toilette;50/80;1 flg Keinkipp mitte;Fixrahmen von aussen einsehbar;Kommentar zu diesem Fenster;RQ.1
2;Neu~2;Fenster;Dachgeschoss;Wohnzimmer;130/150;1 flg Drehkipp rechts;Fixrahmen von innen abnehmbar;;RQ.2

TeX-distribution/format/documentclass/packages/files in use - excerpt from the .log-file, created while uncommenting the \listfiles-command:

pdfTeX, Version 3.14159265-2.6-1.40.21 (TeX Live 2020) (preloaded format=pdflatex)
LaTeX2e <2020-10-01> patch level 4
L3 programming layer <2021-02-18>

memoir.cls -- 2020/10/04 v3.7n configurable book, report, article document class
(
 Package abstract [2008/07/23] emulated by memoir.
 Package appendix [2008/07/23] emulated by memoir.
 Package booktabs [2016/05/16] emulated by memoir.
 Package ccaption [2008/07/23] emulated by memoir.
 Package changepage [2008/07/23] emulated by memoir.
 Package chngcntr [2008/07/23] emulated by memoir.
 Package chngpage [2008/07/23] emulated by memoir.
 Package crop emulated by memoir.
 Package enumerate [2008/07/23] emulated by memoir.
 Package epigraph [2008/07/23] emulated by memoir.
 Package ifmtarg [2008/07/23] emulated by memoir.
 Package index [2008/07/23] emulated by memoir.
 Package makeidx [2008/07/23] emulated by memoir.
 Package moreverb [2008/07/23] emulated by memoir.
 Package mparhack [2008/07/23] emulated by memoir.
 Package needspace [2008/07/23] emulated by memoir.
 Package newfile [2008/07/23] emulated by memoir.
 Package nextpage [2008/07/23] emulated by memoir.
 Package pagenote [2008/07/23] emulated by memoir.
 Package parskip [2008/07/23] emulated by memoir.
 Package patchcmd [2008/07/23] emulated by memoir.
 Package setspace [2008/07/23] emulated by memoir.
 Package shortvrb [2008/07/23] emulated by memoir.
 Package showidx [2008/07/23] emulated by memoir.
 Package titleref [2008/07/23] emulated by memoir.
 Package titling [2008/07/23] emulated by memoir.
 Package tocbibind [2008/07/23] emulated by memoir.
 Package tocloft [2008/07/23] emulated by memoir.
 Package tocvsec2 [2008/07/23] emulated by memoir.
 Package verbatim [2008/07/23] emulated by memoir.
 Package verse [2008/07/23] emulated by memoir.
)
aliasctr.sty -- 2020/08/01 v0.72
amsbsy.sty -- 1999/11/29 v1.2d Bold Symbols
amsgen.sty -- 1999/11/30 v2.0 generic functions
amsmath.sty -- 2020/09/23 v2.17i AMS math features
amsopn.sty -- 2016/03/08 v2.02 operator names
amstext.sty -- 2000/06/29 v2.01 AMS text
array.sty -- 2020/10/01 v2.5c Tabular extension package (FMi)
atbegshi-ltx.sty -- 2020/08/17 v1.0a Emulation of the original atbegshi package with kernel methods
atveryend-ltx.sty -- 2020/08/19 v1.0a Emulation of the original atvery package with kernel methods
auxhook.sty -- 2019-12-17 v1.6 Hooks for auxiliary files (HO)
babel.sty -- 2021/02/22 3.54 The Babel package
bigintcalc.sty -- 2019/12/15 v1.5 Expandable calculations on big integers (HO)
bitset.sty -- 2019/12/09 v1.3 Handle bit-vector datatype (HO)
datatool-base.sty -- 2019/09/27 v2.32 (NLCT)
datatool-fp.sty -- 2019/09/27 v2.32 (NLCT)
datatool.sty -- 2019/09/27 v2.32 (NLCT)
dcolumn.sty -- 2014/10/28 v1.06 decimal alignment package (DPC)
defpattern.sty -- 1994/10/12
delarray.sty -- 2014/10/28 v1.01 array delimiter package (DPC)
etexcmds.sty -- 2019/12/15 v1.7 Avoid name clashes with e-TeX commands (HO)
etoolbox.sty -- 2020/10/05 v2.5k e-TeX tools for LaTeX (JAW)
expl3.sty -- 2021-02-18 L3 programming layer (loader) 
fp-addons.sty -- 1995/03/15
fp-basic.sty -- 1996/05/13
fp-eqn.sty -- 1995/04/03
fp-eval.sty -- 1995/04/03
fp-exp.sty -- 1995/04/03
fp-pas.sty -- 1994/08/29
fp-random.sty -- 1995/02/23
fp-snap.sty -- 1995/04/05
fp-trigo.sty -- 1995/04/14
fp-upn.sty -- 1996/10/21
gettitlestring.sty -- 2019/12/15 v1.6 Cleanup title references (HO)
hpdftex.def -- 2021-02-10 v7.00j Hyperref driver for pdfTeX
hycolor.sty -- 2020-01-27 v1.10 Color options for hyperref/bookmark (HO)
hyperref-langpatches.def -- 2021-02-10 v7.00j Hyperref: patches for babel languages
hyperref.sty -- 2021-02-10 v7.00j Hypertext links for LaTeX
iftex.sty -- 2020/03/06 v1.0d TeX engine tests
ifthen.sty -- 2014/09/29 v1.1c Standard LaTeX ifthen package (DPC)
infwarerr.sty -- 2019/12/03 v1.5 Providing info/warning/error messages (HO)
intcalc.sty -- 2019/12/15 v1.3 Expandable calculations with integers (HO)
kvdefinekeys.sty -- 2019-12-19 v1.6 Define keys (HO)
kvoptions.sty -- 2020-10-07 v3.14 Key value format for package options (HO)
kvsetkeys.sty -- 2019/12/15 v1.18 Key value parser (HO)
l3backend-pdftex.def -- 2021-02-18 L3 backend support: PDF output (pdfTeX)
letltxmacro.sty -- 2019/12/03 v1.6 Let assignment for LaTeX macros (HO)
ltxcmds.sty -- 2020-05-10 v1.25 LaTeX kernel commands for general use (HO)
mem11.clo -- 2008/01/30 v0.3 memoir class 11pt size option
memhfixc.sty -- 2019/10/24 v1.18 nameref/hyperref package fixes for memoir class
nameref.sty -- 2021-04-02 v2.47 Cross-referencing by name of section
naustrian.ldf -- 2020/07/21 v2.12 German support for babel (post-1996 orthography)
ntheorem.sty -- 2011/08/15 1.33
parseargs.sty -- 2020/08/01 v0.72
pd1enc.def -- 2021-02-10 v7.00j Hyperref: PDFDocEncoding definition (HO)
pdfescape.sty -- 2019/12/09 v1.15 Implements pdfTeX's escape features (HO)
pdftexcmds.sty -- 2020-06-27 v0.33 Utility functions of pdfTeX for LuaTeX (HO)
puenc.def -- 2021-02-10 v7.00j Hyperref: PDF Unicode definition (HO)
refcount.sty -- 2019/12/15 v3.6 Data extraction from label references (HO)
rerunfilecheck.sty -- 2019/12/05 v1.9 Rerun checks for auxiliary files (HO)
substr.sty -- 2009/10/20 v1.2 Handle substrings
tabularx.sty -- 2020/01/15 v2.11c `tabularx' package (DPC)
textcase.sty -- 2019/09/14 v1.00 Text only upper/lower case changing (DPC)
thm-autoref.sty -- 2020/08/01 v0.72
thm-kv.sty -- 2020/08/01 v0.72
thm-listof.sty -- 2020/08/01 v0.72
thm-ntheorem.sty -- 2020/08/01 v0.72
thm-patch.sty -- 2020/08/01 v0.72
thm-restate.sty -- 2020/08/01 v0.72
thmtools.sty -- 2020/08/01 v0.72
uniquecounter.sty -- 2019/12/15 v1.4 Provide unlimited unique counter (HO)
url.sty -- 2013/09/16 ver 3.4 Verb mode for urls, etc.
xfor.sty -- 2009/02/05 v1.05 (NLCT)
xkeyval.sty -- 2020/11/20 v2.8 package option processing (HA)
xkeyval.tex -- 2014/12/03 v2.7a key=value parser (HA)
xparse-2020-10-01.sty -- 2020-10-01
xparse.sty -- 2021-01-09 L3 Experimental document command parser
3
  • Thank you so much for this extensive work! This looks like a perfect solution (CSV generated etc.) Unfortunately, I get a compilation error: ./x.tex:149: Undefined control sequence. \prop_gput:Nxx \g__MyStuff_ThisDocumentsProperties_prop{Primary-Key}{0}
    – markus23
    Commented May 13, 2021 at 10:59
  • @markus23 I don't get errors. Probably you need to update your LaTeX-distribution in order to have a more recent expl3-release. Commented May 21, 2021 at 0:19
  • @markus23 I just edited so that it does without ` \prop_gput:Nxx`. Commented Feb 9, 2023 at 23:54
2

Due to the 30000-character-limit I needed to split my answer in two parts.

This is part 1 of my answer.

Part 1 contains considerations, explanations and thoughts.

Part 2 contains a compilable example which is referred in part 1.

If you wish to upvote, please upvote only one of the two parts. This prevents unfair reputation-gain. If you wish to downvote, dovnvote whichever part(s) you like to downvote.


Your request can be split into several aspects that need to be combined:

  1. You wish to maintain an external .csv-file.

    I suggest the following approach:

    Via Dr. Nicola Talbot's package datatool from the begin of the LaTeX-run maintain two databases in LaTeX's memory:

    One memory-database is used for storing data that shall end up in the .csv-file at the end of the LaTeX-run. The content of that database is written to .csv-file at the end of the LaTeX-run.

    The other memory-database is created by reading the .csv-file that came into being during the previous LaTeX-run. That database is used for retrieving data that shall be used for creating output that goes to the .pdf-file.

    This way simultaneous attempts at storing in the database and reading from the database won't collide and data that comes into being near the ends of LaTeX-runs is available at the beginnings of LaTeX-runs also, although stemming from the previous LaTeX-run, which implies that you need more than one LaTeX-run until everything matches out.

    (This basically is the same concept as with LaTeX's cross-referencing-mechanism consisting of the commands \refstepcounter/\label/\ref/\paggeref etc. The difference is that the database is not maintained via external .aux-file but via external .csv-file.)

    In the example in part 2 of this answer maintaining a database ⟨base name of databases and .csv‑file⟩⁠DataToBeWrittenAtEndOfThisLaTeXRun and a database ⟨base name of databases and .csv‑file⟩⁠DataFromPreviousLaTeXRun and a .csv-file ⟨base name of databases and .csv‑file⟩⁠.csv is initiated by the macro

    \DATABASEFILECreateIfNotExistent{⟨base name of databases and .csv-file⟩}%
                                    {⟨separator⟩}%
                                    {⟨delimiter⟩}%
    .

  2. You wish to maintain lists of parts. You can take a "part" for a collection of certain properties. So you need mechanisms for

    • introducing new properties by their names
    • setting current values of these properties
    • creating a database-entry from constellations of current values of certain properties.

    I suggest a concept similar to LaTeX's \refstepcounter. \refstepcounter internally is called by sectioning-commands (\section, \caption, ...) and sets values of properties realized via macros \@currentlabel, \@currentHref, etc, which in turn are used by the command \label for writing \newlabel-entries to .aux-files.

    In the example in part 2 of this answer introducing properties (if not already introduced) and globally setting their current values is done by the macro

    \SetSomeOfThisDocumentsPropertiesFromKeyValList{%
      ⟨propertyA⟩=⟨valueA⟩, ⟨propertyB⟩=⟨valueB⟩, ...%
    }%
    . If you don't specify a value, datatool's null-value for strings is assumed.
    Both the tokens denoting the name of the property and the tokens denoting the value of the property are expanded via expl3's x-expansion. With the latter I tried to take the LaTeX 2ε-\protect-mechanism into account.

    In the example in part 2 of this answer storing a collection of current values of properties as a new row of the database ⟨base name of databases and .csv‑file⟩⁠DataToBeWrittenAtEndOfThisLaTeXRun is done by the macro

    \DatabaseRowFromSomeOfThisDocumentsProperties{⟨base name of databases and .csv-file⟩}%
                                                 {⟨separator⟩}%
                                                 {⟨delimiter⟩}%
                                                 {⟨propertyA⟩, ⟨propertyB⟩, ...}%
    .

    If the databases ⟨base name of databases and .csv‑file⟩⁠DataToBeWrittenAtEndOfThisLaTeXRun and ⟨base name of databases and .csv‑file⟩⁠DataFromPreviousLaTeXRun do not yet exist, they will be created anew and writing the file ⟨base name of databases and .csv‑file⟩⁠.csv at the end of the LaTeX-run will be triggered.

    If the file ⟨base name of databases and .csv‑file⟩⁠.csv already exists from a previous LaTeX-run, its content will be read to the database ⟨base name of databases and .csv‑file⟩⁠DataFromPreviousLaTeXRun, otherwise the database ⟨base name of databases and .csv‑file⟩⁠DataFromPreviousLaTeXRun will be considered empty.

    At the end of the LaTeX-run both databases are compared. If they differ, then something has changed and you are informed that you need to do another LaTeX-run.

    ⟨separator⟩ is the separator of values in the .csv-file. Usually this is comma (,) , but in your question you specified that you wish to use semicolon (;).

    ⟨delimiter⟩: In case a value contains ⟨separator⟩ it needs to be nested between two instances of ⟨delimiter⟩. ⟨delimiter⟩ usually is the quotes-character (").

  3. Your example contains \chapter{Dachgeschoss} and \section{Wohnzimmer}. Seems some elements of your database/some properties' values are to come from arguments of sectioning-commands like \chapter (this is to set the value of the property "floor") and \section (this is to set the value of the property "room"). This means that besides their usual purposes you wish to assign an additional purpose to these sectioning-commands, namely the purpose of denoting values of certain properties which subsequently mentioned parts/windows/Fenster have. This means these sectioning-commands need to be patched to fulfill this purpose also. In my humble opinion this is the most difficult aspect because for adding the fulfillment of purposes commands need to be changed/patched while the sectioning-commands in question have starred variants and optional arguments also, and, e.g., you didn't specify what to do in case of an optional argument being specified also.

  4. You need some infrastructure for creating \listof...-commands which create whatsoever lists by iterating databases ⟨base name of databases and .csv‑file⟩⁠DataFromPreviousLaTeXRun.

    In the example in part 2 of this answer the macro

    \DoWithDatabaseProperties{⟨base name of databases and .csv‑file⟩}%
                             {⟨separator⟩}%
                             {⟨delimiter⟩}%
                             [⟨condition⟩]%
                             {⟨assign list⟩}%
                             {⟨text⟩}%
    is a wrapper for applying datatool's
    \DTLforeach[⟨condition⟩]%
                {⟨database name}%
                {⟨assign list⟩}%
                {⟨text⟩}%
    more conveniently to the database ⟨base name of databases and .csv‑file⟩⁠DataFromPreviousLaTeXRun while ensuring that the databases get created if not existing yet and having the possibility of specifying ⟨separator⟩ and ⟨delimiter⟩of values when things get written to .csv-file.

    In the ⟨assign list⟩‑argument assign to macros the names of such properties whose values were stored in the database so that the database has fields with the corresponding names. In the ⟨text⟩‑argument you can use these macros for denoting the corresponding values coming from the single fields of a row of the database.

    A scratch-switch \ifNoDatabaseEntryEndedUpInTheListYet is provided. It can be initialized by a \ListOf...‑command and toggled within \DoWithDatabaseProperties's ⟨text⟩‑argument. This switch is intended to indicate whether at least one row of the database already ended up as an entry of the list. E.g., only in this case a heading for the list is needed. E.g., only in this case some environment needs to be started/ended; e.g., description/tabular.

You must log in to answer this question.

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