4

I want to create a database consisting of numbered blocks of TeX-code. In particular, I want to do sth like \mycommand{counterName}{Some text} and save a new entry somewhere, to use the Some Text later in another part of the document. The CounterName is a counter that I can reffer to get the text inserted in the document:

\newcounter{a}
\mycommand{a}{The first line}
\stepcounter{a}
\mycommand{a}{The second line}

.........

print{2}

And TeX will produce "The second line".

I am looking for something like glossaries package, but with ability to produce not all list at once, but a specific entry so it looks like the command \print{n} is simply substituted by the text in command \mycommand{n}{TEXT}.

Searching through TeX.SX I found some answers about datatool package and Lua codes, but haven't found a solution I can apply.

I would really appreciate a brief explanation on how to use datatool, glossaries or something similar for this stuff.

2 Answers 2

4

Since you mentioned datatool and glossaries, here are some alternatives.

With datatool, the simplest method requires defining all entries in incremental order (without using a counter). The row number supplies the indexing.

\documentclass{article}

\usepackage{datatool}

\DTLnewdb{data}

\newcommand{\addline}[1]{%
  \DTLnewrow{data}%
  \DTLnewdbentry{data}{Text}{#1}%
}

\newcommand{\print}[1]{%
  \DTLgetvalue{\thisval}{data}{#1}{1}%
  \thisval
}

\addline{The first line}
\addline{The second line}

\begin{document}

Line 2: \print{2}.

All lines:

\DTLforeach*{data}{\Text=Text}{\DTLcurrentindex. \Text.\par}

\end{document}

This produces:

image of document

Line 2: The second line.
All lines:
1. The first line.
2. The second line.

If you want to define your entries out of order using a counter, this can be done with an additional column:

\documentclass{article}

\usepackage{datatool}

\DTLnewdb{data}

\newcommand{\addline}[2]{%
  \DTLnewrow{data}%
  \dtlexpandnewvalue
  \DTLnewdbentry{data}{Index}{\the\value{#1}}%
  \dtlnoexpandnewvalue
  \DTLnewdbentry{data}{Text}{#2}%
}

\newcommand{\print}[1]{%
  \dtlgetrowindex{\thisrowidx}{data}{1}{#1}%
  \ifx\thisrowidx\dtlnovalue
    Not found!%
  \else
     \DTLgetvalue{\thisval}{data}{\thisrowidx}{2}%
     \thisval
  \fi
}

\newcounter{a}
\setcounter{a}{2}
\addline{a}{The second line}

\setcounter{a}{1}
\addline{a}{The first line}

\begin{document}

Line 2: \print{2}.

All lines:

\DTLforeach*{data}{\theIndex=Index,\Text=Text}{\theIndex. \Text.\par}

\end{document}

This produces:

image of document

Line 2: The second line.
All lines:
2. The second line.
1. The first line.

The list is now out of numerical order, but matches the order that the blocks were defined. You can sort them before displaying the list:

\DTLsort{Index}{data}
\DTLforeach*{data}{\theIndex=Index,\Text=Text}{\theIndex. \Text.\par}

image of document

Line 2: The second line.
All lines:
1. The first line.
2. The second line.

Here's a glossaries approach:

\documentclass{article}

\usepackage{glossaries-extra}

\glssetexpandfield{name}

\newcommand{\addline}[2]{%
  \edef\thisidx{\the\value{#1}}%
  \newglossaryentry{\thisidx}{name={\thisidx},description={#2}}%
}

\newcommand{\print}[1]{%
  \glsentrydesc{#1}%
}

\newcounter{a}
\setcounter{a}{2}
\addline{a}{The second line}

\setcounter{a}{1}
\addline{a}{The first line}

\begin{document}

Line 2: \print{2}.

All lines:

\renewcommand{\glstreenamefmt}[1]{#1}
\renewcommand{\glossarysection}[2][]{}
\printunsrtglossary[style=index]

\end{document}

This produces:

image of document

Again this lists in order of definition. If you want the list sorted, you can use the following instead:

\documentclass{article}

\usepackage[automake,nopostdot]{glossaries}

\makeglossaries

\glssetexpandfield{name}

\newcommand{\addline}[2]{%
  \edef\thisidx{\the\value{#1}}%
  \newglossaryentry{\thisidx}{name={\thisidx},description={#2}}%
}

\newcommand{\print}[1]{%
  \glsentrydesc{#1}\glsadd{#1}%
}

\newcounter{a}
\setcounter{a}{2}
\addline{a}{The second line}

\setcounter{a}{1}
\addline{a}{The first line}

\begin{document}

Line 2: \print{2}.

All lines:

\renewcommand{\glstreenamefmt}[1]{#1}
\renewcommand{\glossarysection}[2][]{}
\printglossary[style=index,nonumberlist]

\end{document}

This produces:

image of document

Line 2: The second line.
All lines:
2 The second line

This only lists the entry that has been indexed (with \glsadd). If you want all the entries to be listed, use \glsaddall (after all entries have been defined).

\documentclass{article}

\usepackage[automake,nopostdot]{glossaries}

\makeglossaries

\glssetexpandfield{name}

\newcommand{\addline}[2]{%
  \edef\thisidx{\the\value{#1}}%
  \newglossaryentry{\thisidx}{name={\thisidx},description={#2}}%
}

\newcommand{\print}[1]{%
  \glsentrydesc{#1}%
}

\newcounter{a}
\setcounter{a}{2}
\addline{a}{The second line}

\setcounter{a}{1}
\addline{a}{The first line}

\glsaddall

\begin{document}

Line 2: \print{2}.

All lines:

\renewcommand{\glstreenamefmt}[1]{#1}
\renewcommand{\glossarysection}[2][]{}
\printglossary[style=index,nonumberlist]

\end{document}

This produces:

image of document

Line 2: The second line.
All lines:
1 The first line
2 The second line

Expansion

It's important that the indexing value \the\value{#1} is fully expanded before being stored, otherwise it will keep changing as the counter changes value. Both datatool and glossaries have a way to switch expansion on or off when adding/defining a new entry.

In the case of datatool, expansion is switched on using \dtlexpandnewvalue. In the case of glossaries, expansion is switched on for a particular field using \glssetexpandfield{field-label}.

The code you want to add (in the final argument of \addline) may well contain fragile commands, in which case it's important not to expand the value. With datatool, the expansion is switched back off again using \dtlnoexpandnewvalue. With glossaries, the value is being stored in the description key and expansion is off by default for that field.

3
  • Thank you! I like the solution with datatool. It can serve many requests. And not too difficult, if one opens the datatool documentation. Apr 26, 2017 at 11:06
  • But I still not fully understant what \dtlexpandnewvalue and \dtlnoexpandnewvalue do. Apr 26, 2017 at 11:12
  • @MichaelFreimann \dtlexpandnewvalue switches on expansion so that the new entry is expanded before being added to the database. This is needed otherwise the entry will be \the\value{a} which will change as a changes. I've then switched off the expansion with \dtlnoexpandnewvalue in case the code you want to add contains fragile commands. The glossaries version likewise switches on expansion for the name field using \glssetexpandfield{name}. I'll add a note. Apr 26, 2017 at 12:00
2

The following is based on the assumption that \printlistitem occurs after \addlistitem:

enter image description here

\documentclass{article}

\usepackage{xparse}

\newcounter{listitem}
\NewDocumentCommand{\addlistitem}{o m}{%
  \IfValueTF{#1}
    {\expandafter\def\csname #1-list\endcsname{#2}}
    {\stepcounter{listitem}%
     \begingroup\edef\x{\endgroup\noexpand\expandafter
       \def\noexpand\csname \thelistitem-list\noexpand\endcsname}%
       \x{#2}}%
}

\newcommand{\printlistitem}[1]{%
  \ifcsname #1-list\endcsname
    \csname #1-list\endcsname
  \else
    Item~#1 does not exist.
  \fi
}

\begin{document}

\addlistitem{The first line}% 1
\addlistitem[B]{The second line}% C
\addlistitem{The third line}% 2

\printlistitem{2}

\printlistitem{1}

\printlistitem{3}

\printlistitem{B}

\end{document}

More modifications can be added, including error checking/handling and modifying it for handling reverse referencing (using a \label-\ref setup).


You may be interested in adding a \printallitems to list all the items you've added in the form of a ToC. The following example mimics that by setting each \addlistitem as a \section. You can do refinements to the presentation as needed:

enter image description here

\documentclass{article}

\usepackage{xparse,tocloft}

\newcounter{listitem}
\NewDocumentCommand{\addlistitem}{o m}{%
  \IfValueTF{#1}
    {\expandafter\def\csname #1-list\endcsname{#2}%
     \addcontentsline{los}{listitem}{\protect\numberline{#1} #2}}
    {\stepcounter{listitem}%
     \begingroup\edef\x{\endgroup\noexpand\expandafter
       \def\noexpand\csname \thelistitem-list\noexpand\endcsname}%
       \x{#2}%
     \addcontentsline{los}{listitem}{\protect\numberline{\thelistitem} #2}}%
}

\newcommand{\printlistitem}[1]{%
  \ifcsname #1-list\endcsname
    \csname #1-list\endcsname
  \else
    Item~#1 does not exist.
  \fi
}

\makeatletter
\let\l@listitem\l@section
\newcommand{\printallitems}{{%
  \renewcommand{\cftsecfont}{\mdseries}% Add more ToC-related tuning here
  \@starttoc{los}}}
\makeatother

\begin{document}

\printallitems

\bigskip

\addlistitem{The first line}% 1
\addlistitem[B]{The second line}% C
\addlistitem{The third line}% 2

\printlistitem{2}

\printlistitem{1}

\printlistitem{3}

\printlistitem{B}

\end{document}
11
  • If one would like to access these directly, one could use \renewcommand{\thelistitem}{\Alph{listitem}} or perhaps \Roman. Apr 23, 2017 at 19:30
  • Can you please give an explanation on what's going on inside the branch when I do not pass an optional argument? Apr 23, 2017 at 19:43
  • @MichaelFreimann: Within \addlistitem[<tag>]{<stuff>} we condition on whether <tag> is supplied or not. If it is not supplied, we simply create \<tag>-list to be <stuff>. If it is supplied, we increment the counter (listitem) and create \<\thelistitem>-list to be <stuff>.
    – Werner
    Apr 23, 2017 at 20:54
  • @Werner where exactly are the blocks of text stored? In a database, or as new commands? Apr 24, 2017 at 14:47
  • @MichaelFreimann: They're each stored as a new command. The interface makes it seem like there is some external database structure created.
    – Werner
    Apr 24, 2017 at 17:00

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.