### Supporters and Partners

Sponsored by DANTE e.V.: The German speaking TeX Users Group

 Creating a glossary without using an external indexing application
(5 votes, average: 5.00 out of 5)
LaTeX - General
Written by Nicola Talbot
Thursday, 27 September 2012 22:29

There are a variety of packages (including my own glossaries package) that can generate a sorted list of terms or nomenclature, but they require the help of an external application, such as makeindex, to collate and sort entries. However some users don't like this, either because they find it difficult to invoke the indexing application or because they don't like the complexity of multiple LaTeX plus indexing runs. This article discusses how to by-pass the need for an external indexing application by using the datatool package to create sorted glossaries.

Note: although the datatool package provides a string comparison function that it uses for sorting databases, it's not as sophisticated as that used by Xindy. So if you're not writing in English, you may have to use Xindy for correct sorting. In which case, this article won't be of much use to you.

This article describes two approaches to the task of creating a glossary for your document. The first approach is fairly simple, requires only one LaTeX run, but the glossary has to occur at the end of the document and the entries don't have location lists. The second approach is more complicated, requires at least two LaTeX runs, but the glossary can be placed anywhere in the document and entries may have a location list.

The basic idea behind both methods is to create a database of entries and provide commands that can access the entries so they can be displayed in the document as well as a command that can be used to sort and list all the entries that have been used in the document.

Note: the methods describe here require at least version 2.11 of the datatool package. If you use an earlier version you will get "Undefined control sequence" errors.

## Simple Approach

In this section, I'm going to describe how to create a small package that provides commands to define new terms, use the terms in the document and then sort and list the terms at the end of the document. This package will define a database called glossary, which will have fields called Label, Name, Plural, Description and Sort. I'll later show you how to modify the code so you can have more fields, such as a field for an associated symbol or unit.

I'm going to call this package datagloss, so I need to create a file called datagloss.sty in my preferred text editor. The first line in this file needs to identify the required TeX format, which is LaTeX2e:

\NeedsTeXFormat{LaTeX2e}

The next line needs to identify the package (datagloss). It's also a good idea to add version information:
\ProvidesPackage{datagloss}[2012/09/24 v1.0]

Next I need to load all the dependent packages. The main one is datatool, but I'm also going to use etoolbox, xkeyval and mfirstuc:
\RequirePackage{datatool}
\RequirePackage{etoolbox}
\RequirePackage{xkeyval}
\RequirePackage{mfirstuc}

Next I'm going to create a new database called glossary.
\DTLnewdb{glossary}

Now I want to create a command called \newterm that defines a new term. It needs to have two mandatory arguments that specify the name and description, but it should also have an optional argument that can be used to override default values. For example, while most English plurals are formed by appending an "s" after the singular form, there are many exceptions. In most cases, I want to sort the entries according to the name, but there may be some exceptions where I want to specify the sort key. So the syntax should be:
\newterm[options]{name}{description}

This is where I need to use the xkeyval package. (There are other similar packages you might prefer to use, but I'm familiar with this one, so it's easiest for me to use it.) I want to have three keys: label, sort and plural that can be used in options. To define a new key, I need to define a command that will contain the required value, and I also need to define a key that sets this value. The keys used by \newterm all need to belong to the same family. I'm going to call this family newterm to make it easy to remember. So here's how to set up the three keys:
\newcommand*{\newterm@label}{}
\define@key{newterm}{label}{\renewcommand*{\newterm@label}{#1}}

\newcommand*{\newterm@plural}{}
\define@key{newterm}{plural}{\renewcommand*{\newterm@plural}{#1}}

\newcommand*{\newterm@sort}{}
\define@key{newterm}{sort}{\renewcommand*{\newterm@sort}{#1}}

Commands that contain an "@" symbol in their name are internal commands and should only be used in class or package files.

Now I can get on and define \newterm to have an optional argument and two mandatory arguments.

\newcommand{\newterm}[3][]{}

This first needs to set the default values for the label, the sort key and the plural form. The label and the sort key are just set to the name, and the plural is set to the name with "s" appended.
\newcommand{\newterm}[3][]{%
\renewcommand*{\newterm@label}{#2}%
\renewcommand*{\newterm@sort}{#2}%
\renewcommand*{\newterm@plural}{#2s}%
}

The percent symbol at the end of each line suppresses the unwanted space caused by the end-of-line character.

Once I've set the default values, I then need to use \setkeys to override these defaults with any user-supplied values in the option list.

\newcommand{\newterm}[3][]{%
\renewcommand*{\newterm@label}{#2}%
\renewcommand*{\newterm@sort}{#2}%
\renewcommand*{\newterm@plural}{#2s}%
\setkeys{newterm}{#1}%
}

Now I need to create a new row in the glossary database. The Name and Description field are supplied in the second and third arguments, but I also want to keep track of which entries I've used so that the glossary at the end of the document only contains those entries that have actually been referenced or used. To do this I'm going to define a field called Used that initially is set to 0 (not used), but when the entry is referenced this value will be changed to 1:
\newcommand{\newterm}[3][]{%
\renewcommand*{\newterm@label}{#2}%
\renewcommand*{\newterm@sort}{#2}%
\renewcommand*{\newterm@plural}{#2s}%
\setkeys{newterm}{#1}%
\DTLnewrow{glossary}%
\DTLnewdbentry{glossary}{Name}{#2}%
\DTLnewdbentry{glossary}{Description}{#3}%
\DTLnewdbentry{glossary}{Used}{0}%
}

Things now get a little tricky. The label, sort and plural are currently stored in the commands \newterm@label, \newterm@sort and \newterm@plural. However, by default, \DTLnewdbentry doesn't perform any expansion, so if I try to do
\DTLnewdbentry{glossary}{Label}{\newterm@label}%

This will store the value as \newterm@label rather than its value. To get around this, I can use \dtlexpandnewvalue, which will change \DTLnewdbentry to expand its value, but I need to localise the effect so that it doesn't subsequently try expanding the name and description of future terms (which would cause a problem if they contain fragile commands). So I need to use braces to scope the effect of \dtlexpandnewvalue and I'm also going to use etoolbox's \expandonce to ensure the command is expanded only once:
\newcommand{\newterm}[3][]{%
\renewcommand*{\newterm@label}{#2}%
\renewcommand*{\newterm@sort}{#2}%
\renewcommand*{\newterm@plural}{#2s}%
\setkeys{newterm}{#1}%
\DTLnewrow{glossary}%
\DTLnewdbentry{glossary}{Name}{#2}%
\DTLnewdbentry{glossary}{Description}{#3}%
\DTLnewdbentry{glossary}{Used}{0}%
{%
\dtlexpandnewvalue
\DTLnewdbentry{glossary}{Label}{\expandonce\newterm@label}%
\DTLnewdbentry{glossary}{Sort}{\expandonce\newterm@sort}%
\DTLnewdbentry{glossary}{Plural}{\expandonce\newterm@plural}%
}%
}

That looks okay, but I really ought to ensure each label is unique. First I'm going to define the command \iftermexists that has three arguments: the label, what to do if true and what to do if false. The datatool package has a command called \DTLgetrowforkey{cs}{database}{field}{value}. This gets the row specs for the first row to match the given criteria and stores it in the control sequence given in the first argument. I don't need to know anything about the row specs, all I want to know is whether \DTLgetrowforkey was successful. If it can't find a match, the control sequence will be empty. If it found a match the control sequence won't be empty. I'm just going to use a temporary name (\datagloss@tmp) for this control sequence, and I'm going to use etoolbox's \ifdefempty to check if it's empty:
\newcommand{\iftermexists}[3]{%
\DTLgetrowforkey{\datagloss@tmp}{glossary}{Label}{#1}%
\ifdefempty{\datagloss@tmp}{#3}{#2}%
}

I can now modify my definition of \newterm so that it checks for the existence of the supplied label:
\newcommand{\newterm}[3][]{%
\renewcommand*{\newterm@label}{#2}%
\renewcommand*{\newterm@sort}{#2}%
\renewcommand*{\newterm@plural}{#2s}%
\setkeys{newterm}{#1}%
\iftermexists{\newterm@label}%
{%
}%
{%
\DTLnewrow{glossary}%
\DTLnewdbentry{glossary}{Name}{#2}%
\DTLnewdbentry{glossary}{Description}{#3}%
\DTLnewdbentry{glossary}{Used}{0}%
{%
\dtlexpandnewvalue
\DTLnewdbentry{glossary}{Label}{\expandonce\newterm@label}%
\DTLnewdbentry{glossary}{Sort}{\expandonce\newterm@sort}%
\DTLnewdbentry{glossary}{Plural}{\expandonce\newterm@plural}%
}%
}%
}

So now I have the means to define new terms. Next I need to provide a way to access a particular term from its label. The datatool package provides several ways of accessing data. The most commonly used is DTLforeach, which iterates through a database. Now, I could use this to search for a particular label, but this isn't very efficient, particularly if the database is large. Instead, I'm going to use the more advanced commands provided. It's more complicated, but it's essentially a three step process:
1. Fetch the required row. A copy is stored in a register behind the scenes, but you don't really need to know the details right now.
2. Access and/or edit values in the copy of the current row.
3. Update the database so that it contains any modifications you performed in step 2.
To perform the first step, I can use
\dtlgetrowforvalue{database name}{column index}{value}

This selects the first row in the named database where entry for the column given by column index is equal to the given value. This sets the current row register. You can get the column index for a given field using
\dtlcolumnindex{database name}{field}

So to get the column index of the Label field, I need to do:
\dtlcolumnindex{glossary}{Label}

To get the value of a given column in the current row, I can use
\dtlgetentryfromcurrentrow{control sequence}{column index}

This stores the value in the given control sequence. This is just a temporary command, so let's just call it \datagloss@value. For example, to fetch the value of the Name field in the current row and store it in \datagloss@value, I can do:
\dtlgetentryfromcurrentrow{\datagloss@value}{\dtlcolumnindex{glossary}{Name}}%

I can change the value of a given column in the current row using:
\dtlreplaceentryincurrentrow{new value}{column index}

Note that this only changes the local copy of the current row. No change has been made to the actual database yet. For example, to change the value of the Used field to 1, I can do:
\dtlreplaceentryincurrentrow{1}{\dtlcolumnindex{glossary}{Used}}

Once I've finished editing the current row, I need to perform step 3 above and update the database:
\dtlrecombine

So let's define a command called \useentry{label}{field} that displays the value of the given field in the row identified by label and mark the entry as having been used:
\newcommand*{\useentry}[2]{%
\dtlgetrowforvalue{glossary}{\dtlcolumnindex{glossary}{Label}}{#1}%
\dtlgetentryfromcurrentrow{\datagloss@value}{\dtlcolumnindex{glossary}{#2}}%
\dtlreplaceentryincurrentrow{1}{\dtlcolumnindex{glossary}{Used}}%
\dtlrecombine
\datagloss@value
}

However, I might want to use a term at the start of a sentence, in which case the first letter needs to be converted to upper case. The mfirstuc package provides two commands to do this: \makefirstuc and \xmakefirstuc. The former doesn't perform any expansion on its argument, so \makefirstuc{\datagloss@value} would convert the whole of \datagloss@value to upper case. Instead, I need to use \xmakefirstuc, which will first expand \datagloss@value before sorting out the case conversion. So I can define \Useentry, which is analogous to \useentry, but converts the first letter to upper case:
\newcommand*{\Useentry}[2]{%
\dtlgetrowforvalue{glossary}{\dtlcolumnindex{glossary}{Label}}{#1}%
\dtlgetentryfromcurrentrow{\datagloss@value}{\dtlcolumnindex{glossary}{#2}}%
\dtlreplaceentryincurrentrow{1}{\dtlcolumnindex{glossary}{Used}}%
\dtlrecombine
\xmakefirstuc{\datagloss@value}%
}

There's some redundancy here, so let's slim down the code:
\newcommand*{\use@entry}[2]{%
\dtlgetrowforvalue{glossary}{\dtlcolumnindex{\newterm@database}{Label}}{#1}%
\dtlgetentryfromcurrentrow{\datagloss@value}{\dtlcolumnindex{glossary}{#2}}%
\dtlreplaceentryincurrentrow{1}{\dtlcolumnindex{glossary}{Used}}%
\dtlrecombine
}

\newcommand*{\useentry}[2]{%
\use@entry{#1}{#2}%
\datagloss@value
}

\newcommand*{\Useentry}[2]{%
\use@entry{#1}{#2}%
\xmakefirstuc{\datagloss@value}%
}

To make life a bit simpler, I'm also going to define some convenient shortcuts:
\newcommand*{\useterm}[1]{\useentry{#1}{Name}}

\newcommand*{\usetermpl}[1]{\useentry{#1}{Plural}}

\newcommand*{\Useterm}[1]{\Useentry{#1}{Name}}

\newcommand*{\Usetermpl}[1]{\Useentry{#1}{Plural}}

Finally, I need a command that will sort the database and list all the entries that have been used in the document:
\newcommand{\printterms}{%
\section*{Glossary}
\DTLsort{Sort}{glossary}%
\begin{description}
\DTLforeach*[\Used=1]{glossary}%
{\Name=Name,\Description=Description,\Used=Used}%
{%
\item[\xmakefirstuc{\Name}] \Description.
}%
\end{description}
}

This creates an unnumbered section with the title "Glossary", sorts the database according to the Sort field and iterates through the database inside a description environment, filtering the entries so that only those with the Used field set to 1 are displayed. I've used the starred version of \DTLforeach (the read-only version) as I'm not editing the database at this point and the read-only version is faster than the read/write version.

Unfortunately, there's going to be a problem if the glossary is empty as the description environment will complain about missing an \item. There's a slight modification to get around this:

\newcommand{\printterms}{%
\section*{Glossary}
\DTLsort{Sort}{glossary}%
\def\printgloss@tmp{\item[]}%
\begin{description}
\DTLforeach*[\Used=1]{glossary}%
{\Name=Name,\Description=Description,\Used=Used}%
{%
\item[\xmakefirstuc{\Name}] \Description.%
\def\printgloss@tmp{}%
}%
\printgloss@tmp
\end{description}
}

This defines a temporary command \newgloss@final that is initialised to \item[]. If there are any entries in the database that have been marked as used, this temporary command will be redefined to do nothing. At the end of the description environment, this temporary command is called. If there has been at least one entry this no does nothing, but if there haven't been any entries it does \item[], which keeps the description environment happy.

Here's the complete code for datagloss.sty:

\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{datagloss}[2012/09/24 v1.0]

\RequirePackage{datatool}
\RequirePackage{etoolbox}
\RequirePackage{xkeyval}
\RequirePackage{mfirstuc}

% Create a new database
\DTLnewdb{glossary}

% Define some keys for \newterm

\newcommand*{\newterm@label}{}
\define@key{newterm}{label}{\renewcommand*{\newterm@label}{#1}}

\newcommand*{\newterm@plural}{}
\define@key{newterm}{plural}{\renewcommand*{\newterm@plural}{#1}}

\newcommand*{\newterm@sort}{}
\define@key{newterm}{sort}{\renewcommand*{\newterm@sort}{#1}}

% Syntax: \newterm[options]{name}{description}

\newcommand{\newterm}[3][]{%
\renewcommand*{\newterm@label}{#2}%
\renewcommand*{\newterm@sort}{#2}%
\renewcommand*{\newterm@plural}{#2s}%
\setkeys{newterm}{#1}%
\iftermexists{\newterm@label}%
{%
\PackageError{datagloss}{Term \newterm@label' already exists}{}%
}%
{%
\DTLnewrow{glossary}%
\DTLnewdbentry{glossary}{Name}{#2}%
\DTLnewdbentry{glossary}{Description}{#3}%
\DTLnewdbentry{glossary}{Used}{0}%
{%
\dtlexpandnewvalue
\DTLnewdbentry{glossary}{Label}{\expandonce\newterm@label}%
\DTLnewdbentry{glossary}{Sort}{\expandonce\newterm@sort}%
\DTLnewdbentry{glossary}{Plural}{\expandonce\newterm@plural}%
}%
}%
}

% Syntax: \iftermexists{label}{true part}{false part}

\newcommand{\iftermexists}[3]{%
\DTLgetrowforkey{\datagloss@tmp}{glossary}{Label}{#1}%
\ifdefempty{\datagloss@tmp}{#3}{#2}%
}

\newcommand*{\use@entry}[2]{%
\dtlgetrowforvalue{glossary}{\dtlcolumnindex{glossary}{Label}}{#1}%
\dtlgetentryfromcurrentrow{\datagloss@value}{\dtlcolumnindex{glossary}{#2}}%
\dtlreplaceentryincurrentrow{1}{\dtlcolumnindex{glossary}{Used}}%
\dtlrecombine
}

\newcommand*{\useentry}[2]{%
\use@entry{#1}{#2}%
\datagloss@value
}

\newcommand*{\Useentry}[2]{%
\use@entry{#1}{#2}%
\xmakefirstuc{\datagloss@value}%
}

% Syntax: \useterm{label}

\newcommand*{\useterm}[1]{\useentry{#1}{Name}}

% Syntax: \usetermpl{label}

\newcommand*{\usetermpl}[1]{\useentry{#1}{Plural}}

% Syntax: \Useterm{label}

\newcommand*{\Useterm}[1]{\Useentry{#1}{Name}}

% Syntax: \Usetermpl{label}

\newcommand*{\Usetermpl}[1]{\Useentry{#1}{Plural}}

\newcommand{\printterms}{%
\section*{Glossary}
\DTLsort{Sort}{glossary}%
\def\printgloss@tmp{\item[]}%
\begin{description}
\DTLforeach*[\Used=1]{glossary}%
{\Name=Name,\Description=Description,\Used=Used}%
{%
\item[\xmakefirstuc{\Name}] \Description.%
\def\printgloss@tmp{}%
}%
\printgloss@tmp
\end{description}
}

\endinput

And here's a sample document:
\documentclass{article}

\usepackage{datagloss}

\newterm{eagle}{large bird}
\newterm{elk}{type of deer}
\newterm[plural=deer]{deer}{hoofed animal where the male usually has horns}
\newterm[label=elite]{{\'e}lite}{select group of people}

\begin{document}

\useterm{eagle}. \usetermpl{deer}.
\Useterm{eagle}. \Usetermpl{eagle}.
\useterm{elite}. \Useterm{elite}.
\useterm{elk}.

Checking for defined labels:
eagle \iftermexists{eagle}{exists}{doesn't exist};
dodo \iftermexists{dodo}{exists}{doesn't exist}.

\printterms

\end{document}


The image below shows the resulting text:

Perhaps you want some additional fields. Maybe your terms need an associated symbol or unit, or perhaps you want other parts of speech, such as a gerund, or there are multiple plural forms. The above code can be adapted according to your requirements. Let's suppose I also need a symbol field. First I need to add an extra key for the newterm family:
\newcommand*{\newterm@symbol}{}
\define@key{newterm}{symbol}{\renewcommand*{\newterm@symbol}{#1}}

And I need to add two extra lines to the definition of \newterm:
\newcommand{\newterm}[3][]{%
\renewcommand*{\newterm@label}{#2}%
\renewcommand*{\newterm@sort}{#2}%
\renewcommand*{\newterm@plural}{#2s}%
\renewcommand*{\newterm@symbol}{}% default empty symbol
\setkeys{newterm}{#1}%
\iftermexists{\newterm@label}%
{%
}%
{%
\DTLnewrow{glossary}%
\DTLnewdbentry{glossary}{Name}{#2}%
\DTLnewdbentry{glossary}{Description}{#3}%
\DTLnewdbentry{glossary}{Used}{0}%
{%
\dtlexpandnewvalue
\DTLnewdbentry{glossary}{Label}{\expandonce\newterm@label}%
\DTLnewdbentry{glossary}{Sort}{\expandonce\newterm@sort}%
\DTLnewdbentry{glossary}{Plural}{\expandonce\newterm@plural}%
}%
}%
}

I can also add a convenient shortcut to access the symbol:
\newcommand*{\usesymbol}[1]{\useentry{#1}{Symbol}}

The \printterms command also needs a minor modification. Again I'm using etoolbox's \ifdefempty command. This time to check if a symbol has been defined for a given entry:
\newcommand{\printterms}{%
\section*{Glossary}
\DTLsort{Sort}{glossary}%
\def\printgloss@tmp{\item[]}%
\begin{description}
\DTLforeach*[\Used=1]{glossary}%
{\Name=Name,\Description=Description,\Used=Used,\Symbol=Symbol}%
{%
\item[\xmakefirstuc{\Name}]
\ifdefempty{\Symbol}{}{(\Symbol) }\Description.%
\def\printgloss@tmp{}%
}%
\printgloss@tmp
\end{description}
}

You can add any other fields you like using this technique.

### Multiple Glossaries

So you're not content with just one glossary? Well that's okay. The code can be adapted for multiple glossaries, although they still need to be placed after all the terms have been used. Each glossary is stored in a separate database. The database called glossary (defined above) can be the main (default) glossary. Additional databases can be defined for the other glossaries, but if we use, say, \useentry, how do we know what database to query? This can be done by defining an internal command called, say, \dataglossentry@label that stores the database associated with label when the term is defined. To check if a term exists, we now need to check if \dataglossentry@label exists. This can be done with etoolbox's \ifcsdef command. Here's the new definition of \iftermexists:
\newcommand{\iftermexists}[3]{%
\ifcsdef{dataglossentry@#1}{#2}{#3}%
}

Next \newterm needs to be modified so that you can specify the database and define \dataglossentry@label. This means defining a new key for the newterm family that specifies the database name:
\newcommand*{\newterm@database}{}
\define@key{newterm}{database}{\renewcommand*{\newterm@database}{#1}}

And here's the modified \newterm:
\newcommand{\newterm}[3][]{%
\renewcommand*{\newterm@label}{#2}%
\renewcommand*{\newterm@sort}{#2}%
\renewcommand*{\newterm@plural}{#2s}%
\renewcommand*{\newterm@symbol}{}%
\renewcommand*{\newterm@database}{glossary}%
\setkeys{newterm}{#1}%
\iftermexists{\newterm@label}%
{%
\PackageError{datagloss}{Term \newterm@label' already
exists in database \newterm@database'}{}%
}%
{%
\global\cslet{dataglossentry@\newterm@label}{\newterm@database}%
\DTLnewrow{\newterm@database}%
\DTLnewdbentry{\newterm@database}{Name}{#2}%
\DTLnewdbentry{\newterm@database}{Description}{#3}%
\DTLnewdbentry{\newterm@database}{Used}{0}%
{%
\dtlexpandnewvalue
\DTLnewdbentry{\newterm@database}{Label}{\expandonce\newterm@label}%
\DTLnewdbentry{\newterm@database}{Sort}{\expandonce\newterm@sort}%
\DTLnewdbentry{\newterm@database}{Plural}{\expandonce\newterm@plural}%
\DTLnewdbentry{\newterm@database}{Symbol}{\expandonce\newterm@symbol}%
}%
}%
}

It's mostly straight-forward (set \newterm@database to default to glossary and then use \newterm@database where we previously had glossary) but one line needs a little explanation:
\global\cslet{dataglossentry@\newterm@label}{\newterm@database}%

This uses etoolbox's \cslet command. This is similar to TeX's \let primitive except that the first argument is a control sequence name rather than an actual control sequence. The name here is constructed from the label. This assigns \dataglossentry@label to the current value of \newterm@database. Since commands such as \DTLnewrow have a global effect, it makes sense to also make the \cslet global by prefixing it with \global.

The next thing to do is to modify \use@entry so that it fetches the database name:

\newcommand*{\use@entry}[2]{%
\letcs{\newterm@database}{dataglossentry@#1}%
\dtlgetrowforvalue{\newterm@database}{\dtlcolumnindex{\newterm@database}{Label}}{#1}%
\dtlgetentryfromcurrentrow{\datagloss@value}{\dtlcolumnindex{\newterm@database}{#2}}%
\dtlreplaceentryincurrentrow{1}{\dtlcolumnindex{\newterm@database}{Used}}%
\dtlrecombine
}

Here I've used \letcs, again from etoolbox. This is basically performing the opposite of the \cslet line earlier. It assigns the control sequence \newterm@database to the value of the command given by the control sequence name dataglossentry@. In other words, \newterm@database is set to the name of the database that contains the entry with the given label.

Finally, \printterms needs to be modified. There are several ways to go about this. I could just redefine \printterms to have an optional argument (the database, defaulting to glossary if absent) and a mandatory argument that specifies the glossary title:

\newcommand{\printterms}[2][glossary]{%
\section*{#2}
\DTLsort{Sort}{#1}%
\def\printgloss@tmp{\item[]}%
\begin{description}
\DTLforeach*[\Used=1]{#1}%
{\Name=Name,\Description=Description,\Used=Used,\Symbol=Symbol}%
{%
\item[\xmakefirstuc{\Name}]
\ifdefempty{\Symbol}{}{(\Symbol) }\Description.%
\def\printgloss@tmp{}%
}%
\printgloss@tmp
\end{description}
}

I could then do something like \printterms{Glossary} for the main glossary and \printterms[notation]{Notation} for a glossary contained in the database called notation.

Suppose I want an extra level of sophistication. Maybe I want to set the title when I first define the database. Maybe I might want to have an entry in the table of contents or one glossary should be in a section and another in a subsection. In this case, it might be better to have a database that keeps track of all the glossaries and their associated information. For example, I could define a database called datagloss that has a field called Glossary that stores the name of the glossary database, as well as other fields such as Title. Then create a command called, say, \newgloss that creates a new database for the new glossary and adds an entry into dataglos. For example:

\DTLnewdb{datagloss}

% Syntax: \newgloss{database name}{glossary title}

\newcommand*{\newgloss}[2]{%
\DTLnewdb{#1}%
\DTLnewrow{datagloss}%
\DTLnewdbentry{datagloss}{Glossary}{#1}%
\DTLnewdbentry{datagloss}{Title}{#2}%
}

% Initialise default glossary:
\newgloss{glossary}{Glossary}

The above code needs to replace the line
\DTLnewdb{glossary}

You can refine \newgloss so that it has an optional argument where you can set other fields. For example, you could have a heading key that specifies the sectional command or a sort key to indicate how to sort the database. It would also be useful to customise the glossary style, so let's have a start, item and end key that indicate how to format the start of the glossary, how to format each row of the glossary and how to end the glossary.

For example:

% Define some keys for \newgloss:

\newcommand*{\newgloss@sort}{}
\define@key{newgloss}{sort}{\renewcommand*{\newgloss@sort}{#1}}

\newcommand*{\newgloss@start}{}
\define@key{newgloss}{start}{\renewcommand*{\newgloss@start}{#1}}

\newcommand*{\newgloss@end}{}
\define@key{newgloss}{end}{\renewcommand*{\newgloss@end}{#1}}

\newcommand*{\newgloss@item}{}
\define@key{newgloss}{item}{\renewcommand*{\newgloss@item}{#1}}

% Syntax: \newgloss[options]{database name}{glossary title}

\newcommand*{\newgloss}[3][]{%
\renewcommand*{\newgloss@sort}{\DTLsort{Sort}{#2}}%
\renewcommand*{\newgloss@start}{\def\printgloss@tmp{\item[]}\begin{description}}%
\renewcommand*{\newgloss@end}{\printgloss@tmp\end{description}}%
\renewcommand*{\newgloss@item}{%
\item[\xmakefirstuc{\Name}]
\ifdefempty{\Symbol}{}{(\Symbol) }\Description.%
\def\printgloss@tmp{}%
}%
\setkeys{newgloss}{#1}%
\DTLnewdb{#2}%
\DTLnewrow{datagloss}%
\DTLnewdbentry{datagloss}{Glossary}{#2}%
\DTLnewdbentry{datagloss}{Title}{#3}%
{%
\dtlexpandnewvalue
\DTLnewdbentry{datagloss}{Sort}{\expandonce\newgloss@sort}%
\DTLnewdbentry{datagloss}{Start}{\expandonce\newgloss@start}%
\DTLnewdbentry{datagloss}{End}{\expandonce\newgloss@end}%
\DTLnewdbentry{datagloss}{Item}{\expandonce\newgloss@item}%
}%
}

Now, in my document, I can define a glossary called, say, notation that is put in a numbered section but the entries don't get sorted (so they're listed in the order of definition):
\newgloss[heading={\section},sort={}]{notation}{Notation}

You can add more keys according to your requirements. Next I need to modify \printterms so that it picks up the relevant information from the datagloss database:
\newcommand{\printterms}[1][glossary]{%
\renewcommand*{\newterm@database}{#1}%
% get the fields from datagloss:
\dtlgetrowforvalue{datagloss}{\dtlcolumnindex{datagloss}{Glossary}}{#1}%
\dtlgetentryfromcurrentrow{\datagloss@title}{\dtlcolumnindex{datagloss}{Title}}%
\dtlgetentryfromcurrentrow{\datagloss@sort}{\dtlcolumnindex{datagloss}{Sort}}%
\dtlgetentryfromcurrentrow{\datagloss@start}{\dtlcolumnindex{datagloss}{Start}}%
\dtlgetentryfromcurrentrow{\datagloss@end}{\dtlcolumnindex{datagloss}{End}}%
\dtlgetentryfromcurrentrow{\datagloss@item}{\dtlcolumnindex{datagloss}{Item}}%
% now display the glossary:
\datagloss@sort
\datagloss@start
\datagloss@foreachentry
{%
\datagloss@item
}%
\datagloss@end
}

\newcommand*{\datagloss@foreachentry}[1]{%
\DTLforeach*[\Used=1]{\newterm@database}%
{\Name=Name,\Description=Description,\Used=Used,\Symbol=Symbol}%
{%
#1%
}%
}

(I haven't needed to use \dtlrecombine as I haven't edited the database row fetched using \dtlgetrowforvalue. I've also made it more modular by moving the \DTLforeach part into a separate command.)

Here's the complete code for the updated package:

\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{datagloss}[2012/09/24 v1.0]

\RequirePackage{datatool}
\RequirePackage{etoolbox}
\RequirePackage{xkeyval}
\RequirePackage{mfirstuc}

\DTLnewdb{datagloss}

% Define some keys for \newgloss:

\newcommand*{\newgloss@sort}{}
\define@key{newgloss}{sort}{\renewcommand*{\newgloss@sort}{#1}}

\newcommand*{\newgloss@start}{}
\define@key{newgloss}{start}{\renewcommand*{\newgloss@start}{#1}}

\newcommand*{\newgloss@end}{}
\define@key{newgloss}{end}{\renewcommand*{\newgloss@end}{#1}}

\newcommand*{\newgloss@item}{}
\define@key{newgloss}{item}{\renewcommand*{\newgloss@item}{#1}}

% Syntax: \newgloss[options]{database name}{glossary title}

\newcommand*{\newgloss}[3][]{%
\renewcommand*{\newgloss@sort}{\DTLsort{Sort}{#2}}%
\renewcommand*{\newgloss@start}{\def\printgloss@tmp{\item[]}\begin{description}}%
\renewcommand*{\newgloss@end}{\printgloss@tmp\end{description}}%
\renewcommand*{\newgloss@item}{%
\item[\xmakefirstuc{\Name}]
\ifdefempty{\Symbol}{}{(\Symbol) }\Description.%
\def\printgloss@tmp{}%
}%
\setkeys{newgloss}{#1}%
\DTLnewdb{#2}%
\DTLnewrow{datagloss}%
\DTLnewdbentry{datagloss}{Glossary}{#2}%
\DTLnewdbentry{datagloss}{Title}{#3}%
{%
\dtlexpandnewvalue
\DTLnewdbentry{datagloss}{Sort}{\expandonce\newgloss@sort}%
\DTLnewdbentry{datagloss}{Start}{\expandonce\newgloss@start}%
\DTLnewdbentry{datagloss}{End}{\expandonce\newgloss@end}%
\DTLnewdbentry{datagloss}{Item}{\expandonce\newgloss@item}%
}%
}

% Initialise default glossary:
\newgloss{glossary}{Glossary}

% Define some keys for \newterm

\newcommand*{\newterm@label}{}
\define@key{newterm}{label}{\renewcommand*{\newterm@label}{#1}}

\newcommand*{\newterm@plural}{}
\define@key{newterm}{plural}{\renewcommand*{\newterm@plural}{#1}}

\newcommand*{\newterm@sort}{}
\define@key{newterm}{sort}{\renewcommand*{\newterm@sort}{#1}}

\newcommand*{\newterm@symbol}{}
\define@key{newterm}{symbol}{\renewcommand*{\newterm@symbol}{#1}}

\newcommand*{\newterm@database}{}
\define@key{newterm}{database}{\renewcommand*{\newterm@database}{#1}}

% Syntax: \newterm[options]{name}{description}

\newcommand{\newterm}[3][]{%
\renewcommand*{\newterm@label}{#2}%
\renewcommand*{\newterm@sort}{#2}%
\renewcommand*{\newterm@plural}{#2s}%
\renewcommand*{\newterm@symbol}{}%
\renewcommand*{\newterm@database}{glossary}%
\setkeys{newterm}{#1}%
\iftermexists{\newterm@label}%
{%
\PackageError{datagloss}{Term \newterm@label' already
exists in database \newterm@database'}{}%
}%
{%
\global\cslet{dataglossentry@\newterm@label}{\newterm@database}%
\DTLnewrow{\newterm@database}%
\DTLnewdbentry{\newterm@database}{Name}{#2}%
\DTLnewdbentry{\newterm@database}{Description}{#3}%
\DTLnewdbentry{\newterm@database}{Used}{0}%
{%
\dtlexpandnewvalue
\DTLnewdbentry{\newterm@database}{Label}{\expandonce\newterm@label}%
\DTLnewdbentry{\newterm@database}{Sort}{\expandonce\newterm@sort}%
\DTLnewdbentry{\newterm@database}{Plural}{\expandonce\newterm@plural}%
\DTLnewdbentry{\newterm@database}{Symbol}{\expandonce\newterm@symbol}%
}%
}%
}

% Syntax: \iftermexists{label}{true part}{false part}

\newcommand{\iftermexists}[3]{%
\ifcsdef{dataglossentry@#1}{#2}{#3}%
}

\newcommand*{\use@entry}[2]{%
\letcs{\newterm@database}{dataglossentry@#1}%
\dtlgetrowforvalue{\newterm@database}{\dtlcolumnindex{\newterm@database}{Label}}{#1}%
\dtlgetentryfromcurrentrow{\datagloss@value}{\dtlcolumnindex{\newterm@database}{#2}}%
\dtlreplaceentryincurrentrow{1}{\dtlcolumnindex{\newterm@database}{Used}}%
\dtlrecombine
}

\newcommand*{\useentry}[2]{%
\use@entry{#1}{#2}%
\datagloss@value
}

\newcommand*{\Useentry}[2]{%
\use@entry{#1}{#2}%
\xmakefirstuc{\datagloss@value}%
}

\newcommand*{\useterm}[1]{\useentry{#1}{Name}}

\newcommand*{\usetermpl}[1]{\useentry{#1}{Plural}}

\newcommand*{\Useterm}[1]{\Useentry{#1}{Name}}

\newcommand*{\Usetermpl}[1]{\Useentry{#1}{Plural}}

\newcommand*{\usesymbol}[1]{\useentry{#1}{Symbol}}

\newcommand{\printterms}[1][glossary]{%
\renewcommand*{\newterm@database}{#1}%
% get the fields from datagloss:
\dtlgetrowforvalue{datagloss}{\dtlcolumnindex{datagloss}{Glossary}}{#1}%
\dtlgetentryfromcurrentrow{\datagloss@title}{\dtlcolumnindex{datagloss}{Title}}%
\dtlgetentryfromcurrentrow{\datagloss@sort}{\dtlcolumnindex{datagloss}{Sort}}%
\dtlgetentryfromcurrentrow{\datagloss@start}{\dtlcolumnindex{datagloss}{Start}}%
\dtlgetentryfromcurrentrow{\datagloss@end}{\dtlcolumnindex{datagloss}{End}}%
\dtlgetentryfromcurrentrow{\datagloss@item}{\dtlcolumnindex{datagloss}{Item}}%
% now display the glossary:
\datagloss@sort
\datagloss@start
\datagloss@foreachentry
{%
\datagloss@item
}%
\datagloss@end
}

\newcommand*{\datagloss@foreachentry}[1]{%
\DTLforeach*[\Used=1]{\newterm@database}%
{\Name=Name,\Description=Description,\Used=Used,\Symbol=Symbol}%
{%
#1%
}%
}

\endinput

And here's a sample document:
\documentclass{article}

\usepackage{datagloss}
\usepackage{amsfonts}

\newterm{eagle}{large bird}
\newterm{elk}{type of deer}
\newterm[plural=deer]{deer}{hoofed animal where the male usually has horns}
\newterm[label=elite]{{\'e}lite}{select group of people}

\newterm[database=notation,label=real,symbol={\ensuremath{\mathbb{R}}}]{real
number}{a value representing a quantity along a continuous line}
\newterm[database=notation,label=natural,symbol={\ensuremath{\mathbb{N}}}]{natural
number}{a positive integer or countable number}
\newterm[database=notation,label=integer,symbol={\ensuremath{\mathbb{I}}}]{integer}{zero or a positive or negative whole number}

\begin{document}

\useterm{eagle}. \usetermpl{deer}.
\Useterm{eagle}. \Usetermpl{eagle}.
\useterm{elite}. \Useterm{elite}. \useterm{elk}.

Checking for defined labels:
eagle \iftermexists{eagle}{exists}{doesn't exist};
dodo \iftermexists{dodo}{exists}{doesn't exist}.

The set of \usetermpl{integer} is denoted
\usesymbol{integer}.

The set of \usetermpl{natural} is denoted
\usesymbol{natural}.

\printterms
\printterms[notation]

\end{document}


The image below shows the resulting text:

### Acronyms

It's also possible to incorporate acronyms. Firstly, I need to add a command that determines if an entry has been used. I'm going to call it \ifentryused and it will take three arguments: the first is the entry's label, the second what to do if it has been used and the third what to do if it hasn't been used:
\newcommand*{\ifentryused}[3]{%
\letcs{\newterm@database}{dataglossentry@#1}%
\dtlgetrowforvalue{\newterm@database}{\dtlcolumnindex{\newterm@database}{Label}}{#1}%
\dtlgetentryfromcurrentrow{\datagloss@value}{\dtlcolumnindex{\newterm@database}{Used}}%
\ifnum\datagloss@value=1\relax
#2%
\else
#3%
\fi
}

This fetches the value of the Used field for the given label, which should either be 0 or 1. If it's 1, then the entry has been used.

Next I need to add some more keys for \newterm:

\newcommand*{\newterm@database}{}
\define@key{newterm}{database}{\renewcommand*{\newterm@database}{#1}}

\newcommand*{\newterm@long}{}
\define@key{newterm}{long}{%
\renewcommand*{\newterm@long}{#1}%
\renewcommand*{\newterm@longpl}{#1s}%
}

\newcommand*{\newterm@short}{}
\define@key{newterm}{short}{%
\renewcommand*{\newterm@short}{#1}%
\renewcommand*{\newterm@shortpl}{#1s}%
}

\newcommand*{\newterm@longpl}{}
\define@key{newterm}{longplural}{\renewcommand*{\newterm@longpl}{#1}}

\newcommand*{\newterm@shortpl}{}
\define@key{newterm}{shortplural}{\renewcommand*{\newterm@shortpl}{#1}}

And modify \newterm:
\newcommand{\newterm}[3][]{%
\renewcommand*{\newterm@label}{#2}%
\renewcommand*{\newterm@sort}{#2}%
\renewcommand*{\newterm@plural}{#2s}%
\renewcommand*{\newterm@symbol}{}%
\renewcommand*{\newterm@database}{glossary}%
\renewcommand*{\newterm@short}{#2}%
\renewcommand*{\newterm@shortpl}{#2s}%
\renewcommand*{\newterm@long}{#3}%
\renewcommand*{\newterm@longpl}{#3s}%
\setkeys{newterm}{#1}%
\iftermexists{\newterm@label}%
{%
\PackageError{datagloss}{Term \newterm@label' already
exists in database \newterm@database'}{}%
}%
{%
\global\cslet{dataglossentry@\newterm@label}{\newterm@database}%
\DTLnewrow{\newterm@database}%
\DTLnewdbentry{\newterm@database}{Name}{#2}%
\DTLnewdbentry{\newterm@database}{Description}{#3}%
\DTLnewdbentry{\newterm@database}{Used}{0}%
{%
\dtlexpandnewvalue
\DTLnewdbentry{\newterm@database}{Label}{\expandonce\newterm@label}%
\DTLnewdbentry{\newterm@database}{Sort}{\expandonce\newterm@sort}%
\DTLnewdbentry{\newterm@database}{Plural}{\expandonce\newterm@plural}%
\DTLnewdbentry{\newterm@database}{Symbol}{\expandonce\newterm@symbol}%
\DTLnewdbentry{\newterm@database}{Short}{\expandonce\newterm@short}%
\DTLnewdbentry{\newterm@database}{Long}{\expandonce\newterm@long}%
\DTLnewdbentry{\newterm@database}{ShortPlural}{\expandonce\newterm@shortpl}%
\DTLnewdbentry{\newterm@database}{LongPlural}{\expandonce\newterm@longpl}%
}%
}%
}

So in my document, I can now define an acronym:
\newterm{SVM}{support vector machine}

Note: you may be wondering why I've reset \newterm@longpl and \newterm@shortpl in the short and long keys. Given that I've already set up default values at the start of \newterm, why change them again? I've done this because I might not want to store the short and long forms in the Name and Description fields. I might instead want to define an acronym like this:
\newterm[label={SVM},short={SVM},long={support vector machine}]%
{support vector machine (SVM)}%
{statistical pattern recognition technique}

Now the ShortPlural field is set to "SVMs" and the LongPlural field is set to "support vector machines". If you do need to use the shortplural and longplural keys, just make sure you use them after short and long.

If you want to define acronyms in this way, you might find it easier to define a shortcut:

\newcommand*{\newacr}[4][]{%
\newterm[label={#2},short={#2},long={#3},#1]{#3 (#2)}{#4}%
}

I can now do:
\newacr{SVM}{support vector machine}{statistical pattern technique}

You can, of course, customise \newacr to suit your requirements. Maybe you like to have the acronym displayed in smallcaps:
\newcommand*{\newacr}[4][]{%
\newterm[label={#2},short={\textsc{#2}},long={#3},#1]{#3
(\MakeUppercase{#2})}{#4}%
}

In which case, you need to use lower case letters when you define the acronym:
\newacr[label=SVM]{svm}{support vector machine}{statistical pattern technique}

Once I've defined an acronym, I can use it like this:
\ifentryused{SVM}{\useentry{SVM}{Short}}{\useentry{SVM}{Long} (\useentry{SVM}{Short})}

This is rather cumbersome to write, so let's define a shortcut:
\newcommand*{\acr}[1]{%
\ifentryused{#1}%
{\useentry{#1}{Short}}%
{\useentry{#1}{Long} (\useentry{#1}{Short})}%
}

Now I can just use \acr{SVM}. You can of course modify this to suit your preferred format. For example, maybe on first use you want the long form as a footnote:
\newcommand*{\acr}[1]{%
\ifentryused{#1}%
{\useentry{#1}{Short}}%
{\useentry{#1}{Short}\footnote{\useentry{#1}{Long}}}%
}

One more modification: when \datagloss@foreachentry uses \DTLforeach* it ought to assign values for the Long and Short fields in case they are required:
\newcommand*{\datagloss@foreachentry}[1]{%
\DTLforeach*[\Used=1]{\newterm@database}%
{\Name=Name,\Description=Description,\Used=Used,\Symbol=Symbol,%
\Long=Long,\Short=Short}%
{%
#1%
}%
}

Here's the package code in full:
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{datagloss}[2012/09/24 v1.0]

\RequirePackage{datatool}
\RequirePackage{etoolbox}
\RequirePackage{xkeyval}
\RequirePackage{mfirstuc}

\DTLnewdb{datagloss}

% Define some keys for \newgloss:

\newcommand*{\newgloss@sort}{}
\define@key{newgloss}{sort}{\renewcommand*{\newgloss@sort}{#1}}

\newcommand*{\newgloss@start}{}
\define@key{newgloss}{start}{\renewcommand*{\newgloss@start}{#1}}

\newcommand*{\newgloss@end}{}
\define@key{newgloss}{end}{\renewcommand*{\newgloss@end}{#1}}

\newcommand*{\newgloss@item}{}
\define@key{newgloss}{item}{\renewcommand*{\newgloss@item}{#1}}

% Syntax: \newgloss[options]{database name}{glossary title}

\newcommand*{\newgloss}[3][]{%
\renewcommand*{\newgloss@sort}{\DTLsort{Sort}{#2}}%
\renewcommand*{\newgloss@start}{\def\printgloss@tmp{\item[]}\begin{description}}%
\renewcommand*{\newgloss@end}{\printgloss@tmp\end{description}}%
\renewcommand*{\newgloss@item}{%
\item[\xmakefirstuc{\Name}]
\ifdefempty{\Symbol}{}{(\Symbol) }\Description.%
\def\printgloss@tmp{}%
}%
\setkeys{newgloss}{#1}%
\DTLnewdb{#2}%
\DTLnewrow{datagloss}%
\DTLnewdbentry{datagloss}{Glossary}{#2}%
\DTLnewdbentry{datagloss}{Title}{#3}%
{%
\dtlexpandnewvalue
\DTLnewdbentry{datagloss}{Sort}{\expandonce\newgloss@sort}%
\DTLnewdbentry{datagloss}{Start}{\expandonce\newgloss@start}%
\DTLnewdbentry{datagloss}{End}{\expandonce\newgloss@end}%
\DTLnewdbentry{datagloss}{Item}{\expandonce\newgloss@item}%
}%
}

% Initialise default glossary:
\newgloss{glossary}{Glossary}

% Define some keys for \newterm

\newcommand*{\newterm@label}{}
\define@key{newterm}{label}{\renewcommand*{\newterm@label}{#1}}

\newcommand*{\newterm@plural}{}
\define@key{newterm}{plural}{\renewcommand*{\newterm@plural}{#1}}

\newcommand*{\newterm@sort}{}
\define@key{newterm}{sort}{\renewcommand*{\newterm@sort}{#1}}

\newcommand*{\newterm@symbol}{}
\define@key{newterm}{symbol}{\renewcommand*{\newterm@symbol}{#1}}

\newcommand*{\newterm@database}{}
\define@key{newterm}{database}{\renewcommand*{\newterm@database}{#1}}

\newcommand*{\newterm@long}{}
\define@key{newterm}{long}{%
\renewcommand*{\newterm@long}{#1}%
\renewcommand*{\newterm@longpl}{#1s}%
}

\newcommand*{\newterm@short}{}
\define@key{newterm}{short}{%
\renewcommand*{\newterm@short}{#1}%
\renewcommand*{\newterm@shortpl}{#1s}%
}

\newcommand*{\newterm@longpl}{}
\define@key{newterm}{longplural}{\renewcommand*{\newterm@longpl}{#1}}

\newcommand*{\newterm@shortpl}{}
\define@key{newterm}{shortplural}{\renewcommand*{\newterm@shortpl}{#1}}

% Syntax: \newterm[options]{name}{description}

\newcommand{\newterm}[3][]{%
\renewcommand*{\newterm@label}{#2}%
\renewcommand*{\newterm@sort}{#2}%
\renewcommand*{\newterm@plural}{#2s}%
\renewcommand*{\newterm@symbol}{}%
\renewcommand*{\newterm@database}{glossary}%
\renewcommand*{\newterm@short}{#2}%
\renewcommand*{\newterm@shortpl}{#2s}%
\renewcommand*{\newterm@long}{#3}%
\renewcommand*{\newterm@longpl}{#3s}%
\setkeys{newterm}{#1}%
\iftermexists{\newterm@label}%
{%
\PackageError{datagloss}{Term \newterm@label' already
exists in database \newterm@database'}{}%
}%
{%
\global\cslet{dataglossentry@\newterm@label}{\newterm@database}%
\DTLnewrow{\newterm@database}%
\DTLnewdbentry{\newterm@database}{Name}{#2}%
\DTLnewdbentry{\newterm@database}{Description}{#3}%
\DTLnewdbentry{\newterm@database}{Used}{0}%
{%
\dtlexpandnewvalue
\DTLnewdbentry{\newterm@database}{Label}{\expandonce\newterm@label}%
\DTLnewdbentry{\newterm@database}{Sort}{\expandonce\newterm@sort}%
\DTLnewdbentry{\newterm@database}{Plural}{\expandonce\newterm@plural}%
\DTLnewdbentry{\newterm@database}{Symbol}{\expandonce\newterm@symbol}%
\DTLnewdbentry{\newterm@database}{Short}{\expandonce\newterm@short}%
\DTLnewdbentry{\newterm@database}{Long}{\expandonce\newterm@long}%
\DTLnewdbentry{\newterm@database}{ShortPlural}{\expandonce\newterm@shortpl}%
\DTLnewdbentry{\newterm@database}{LongPlural}{\expandonce\newterm@longpl}%
}%
}%
}

% Syntax: \newacr[options]{short}{long}{description}
\newcommand*{\newacr}[4][]{%
\newterm[label={#2},short={\textsc{#2}},long={#3},#1]%
{#3 (\MakeUppercase{#2})}%
{#4}%
}

% Syntax: \iftermexists{label}{true part}{false part}

\newcommand{\iftermexists}[3]{%
\ifcsdef{dataglossentry@#1}{#2}{#3}%
}

% Syntax: \ifentryused{label}{true part}{false part}

\newcommand*{\ifentryused}[3]{%
\letcs{\newterm@database}{dataglossentry@#1}%
\dtlgetrowforvalue{\newterm@database}{\dtlcolumnindex{\newterm@database}{Label}}{#1}%
\dtlgetentryfromcurrentrow{\datagloss@value}{\dtlcolumnindex{\newterm@database}{Used}}%
\ifnum\datagloss@value=1\relax
#2%
\else
#3%
\fi
}

\newcommand*{\use@entry}[2]{%
\letcs{\newterm@database}{dataglossentry@#1}%
\dtlgetrowforvalue{\newterm@database}{\dtlcolumnindex{\newterm@database}{Label}}{#1}%
\dtlgetentryfromcurrentrow{\datagloss@value}{\dtlcolumnindex{\newterm@database}{#2}}%
\dtlreplaceentryincurrentrow{1}{\dtlcolumnindex{\newterm@database}{Used}}%
\dtlrecombine
}

\newcommand*{\useentry}[2]{%
\use@entry{#1}{#2}%
\datagloss@value
}

\newcommand*{\Useentry}[2]{%
\use@entry{#1}{#2}%
\xmakefirstuc{\datagloss@value}%
}

\newcommand*{\useterm}[1]{\useentry{#1}{Name}}

\newcommand*{\usetermpl}[1]{\useentry{#1}{Plural}}

\newcommand*{\Useterm}[1]{\Useentry{#1}{Name}}

\newcommand*{\Usetermpl}[1]{\Useentry{#1}{Plural}}

\newcommand*{\usesymbol}[1]{\useentry{#1}{Symbol}}

\newcommand*{\acr}[1]{%
\ifentryused{#1}%
{\useentry{#1}{Short}}%
{\useentry{#1}{Long} (\useentry{#1}{Short})}%
}

\newcommand*{\acrpl}[1]{%
\ifentryused{#1}%
{\useentry{#1}{ShortPlural}}%
{\useentry{#1}{LongPlural} (\useentry{#1}{ShortPlural})}%
}

\newcommand*{\Acr}[1]{%
\ifentryused{#1}%
{\Useentry{#1}{Short}}%
{\Useentry{#1}{Long} (\useentry{#1}{Short})}%
}

\newcommand*{\Acrpl}[1]{%
\ifentryused{#1}%
{\Useentry{#1}{ShortPlural}}%
{\Useentry{#1}{LongPlural} (\useentry{#1}{ShortPlural})}%
}

\newcommand{\printterms}[1][glossary]{%
\renewcommand*{\newterm@database}{#1}%
% get the fields from datagloss:
\dtlgetrowforvalue{datagloss}{\dtlcolumnindex{datagloss}{Glossary}}{#1}%
\dtlgetentryfromcurrentrow{\datagloss@title}{\dtlcolumnindex{datagloss}{Title}}%
\dtlgetentryfromcurrentrow{\datagloss@sort}{\dtlcolumnindex{datagloss}{Sort}}%
\dtlgetentryfromcurrentrow{\datagloss@start}{\dtlcolumnindex{datagloss}{Start}}%
\dtlgetentryfromcurrentrow{\datagloss@end}{\dtlcolumnindex{datagloss}{End}}%
\dtlgetentryfromcurrentrow{\datagloss@item}{\dtlcolumnindex{datagloss}{Item}}%
% now display the glossary:
\datagloss@sort
\datagloss@start
\datagloss@foreachentry
{%
\datagloss@item
}%
\datagloss@end
}

\newcommand*{\datagloss@foreachentry}[1]{%
\DTLforeach*[\Used=1]{\newterm@database}%
{\Name=Name,\Description=Description,\Used=Used,\Symbol=Symbol,%
\Long=Long,\Short=Short}%
{%
#1%
}%
}

\endinput

And here's a sample document:
\documentclass{article}

\usepackage{datagloss}
\usepackage{amsfonts}

\newterm{eagle}{large bird}
\newterm{elk}{type of deer}
\newterm[plural=deer]{deer}{hoofed animal where the male usually has horns}
\newterm[label=elite]{{\'e}lite}{select group of people}

\newgloss
[
sort={},% don't sort the entries
% use tabular instead of description environment:
start={\begin{tabular}{lcl}},
item={\xmakefirstuc{\Name} & \Symbol & \Description\\},
end={\end{tabular}}
]
{notation}
{Notation}

\newterm[database=notation,label=real,symbol={\ensuremath{\mathbb{R}}}]{real
number}{a value representing a quantity along a continuous line}
\newterm[database=notation,label=natural,symbol={\ensuremath{\mathbb{N}}}]{natural
number}{a positive integer or countable number}
\newterm[database=notation,label=integer,symbol={\ensuremath{\mathbb{I}}}]{integer}{zero or a positive or negative whole number}

\newgloss{acronym}{Acronyms}

\newacr[database=acronym,label=SVM]{svm}{support vector machine}{statistical pattern technique}

\begin{document}

\useterm{eagle}. \usetermpl{deer}.
\Useterm{eagle}. \Usetermpl{eagle}.
\useterm{elite}. \Useterm{elite}. \useterm{elk}.

Checking for defined labels:
eagle \iftermexists{eagle}{exists}{doesn't exist};
dodo \iftermexists{dodo}{exists}{doesn't exist}.

The set of \usetermpl{integer} is denoted
\usesymbol{integer}.

The set of \usetermpl{natural} is denoted
\usesymbol{natural}.

First use: \acr{SVM}. Next use: \acr{SVM}.

\dtlforcolumn{\thisGlossary}{datagloss}{Glossary}
{\expandafter\printterms\expandafter[\thisGlossary]}

\end{document}

Note: in the above sample document, I've used \dtlforcolumn to iterate through all the defined glossaries. I've had to use \expandafter as \dtlgetrowforvalue requires the value to be expanded.

The image below shows the resulting text.

## Complicated Approach

The above approach is okay if you're happy for the glossaries to occur at the end of the document and you don't want a location list (that is, a list of page numbers where the entry was used). However, that might not fit your requirements. This section adapts the sample package, datagloss.sty, from the previous section.

### Glossaries at the Start of the Document

First, let's look at how to get the glossaries to appear at the start of the document. Here, we have a similar problem to things like the table of contents or list of figures. We don't know what entries have been used until we've reached the end of the document. Commands like \tableofcontents and \ref/\label work by writing information to an external file and then reading it in the next time LaTeX is run. We now have to have two LaTeX runs, but we can still get away with not using an external indexing application.

Whenever an entry is used, a line needs to be written to the auxiliary file. I could write the line as \datagloss@usedentry{label}{page number}. This can be done by modifying the definition of \use@entry:

\newcommand*{\use@entry}[2]{%
\protected@write{\@auxout}{}{\string\datagloss@usedentry{#1}{\thepage}}%
\letcs{\newterm@database}{dataglossentry@#1}%
\dtlgetrowforvalue{\newterm@database}{\dtlcolumnindex{\newterm@database}{Label}}{#1}%
\dtlgetentryfromcurrentrow{\datagloss@value}{\dtlcolumnindex{\newterm@database}{#2}}%
\dtlreplaceentryincurrentrow{1}{\dtlcolumnindex{\newterm@database}{Used}}%
\dtlrecombine
}

The modification here is the new line:
\protected@write{\@auxout}{}{\string\datagloss@usedentry{#1}}%

This writes a line to \@auxout, which is the write register associated with the auxiliary file. \string is used to write \datagloss@usedentry as is. (Basically \string converts the command name to a sequence of characters.) Next I need to define \datagloss@usedentry. This should add a Location field for the given entry. At the moment it's not going to do anything more than that:
\newcommand*{\datagloss@usedentry}[2]{%
\letcs{\newterm@database}{dataglossentry@#1}%
\dtlgetrowforvalue{\newterm@database}{\dtlcolumnindex{\newterm@database}{Label}}{#1}%
\dtlappendentrytocurrentrow{Location}{#2}%
\dtlrecombine
}

Now \datagloss@foreachentry needs the filter modified. Instead of checking the Used field, check if the Location field exists. This can be done using \DTLifnull:
\newcommand*{\datagloss@foreachentry}[1]{%
\DTLforeach*{\newterm@database}%
{\Name=Name,\Description=Description,\Used=Used,\Symbol=Symbol,%
\Long=Long,\Short=Short,\Location=Location}%
{%
\DTLifnull{\Location}{}{#1}%
}%
}

Here's the complete code:
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{datagloss}[2012/09/24 v1.0]

\RequirePackage{datatool}
\RequirePackage{etoolbox}
\RequirePackage{xkeyval}
\RequirePackage{mfirstuc}

\DTLnewdb{datagloss}

% Define some keys for \newgloss:

\newcommand*{\newgloss@sort}{}
\define@key{newgloss}{sort}{\renewcommand*{\newgloss@sort}{#1}}

\newcommand*{\newgloss@start}{}
\define@key{newgloss}{start}{\renewcommand*{\newgloss@start}{#1}}

\newcommand*{\newgloss@end}{}
\define@key{newgloss}{end}{\renewcommand*{\newgloss@end}{#1}}

\newcommand*{\newgloss@item}{}
\define@key{newgloss}{item}{\renewcommand*{\newgloss@item}{#1}}

% Syntax: \newgloss[options]{database name}{glossary title}

\newcommand*{\newgloss}[3][]{%
\renewcommand*{\newgloss@sort}{\DTLsort{Sort}{#2}}%
\renewcommand*{\newgloss@start}{\def\printgloss@tmp{\item[]}\begin{description}}%
\renewcommand*{\newgloss@end}{\printgloss@tmp\end{description}}%
\renewcommand*{\newgloss@item}{%
\item[\xmakefirstuc{\Name}]
\ifdefempty{\Symbol}{}{(\Symbol) }\Description.%
\def\printgloss@tmp{}%
}%
\setkeys{newgloss}{#1}%
\DTLnewdb{#2}%
\DTLnewrow{datagloss}%
\DTLnewdbentry{datagloss}{Glossary}{#2}%
\DTLnewdbentry{datagloss}{Title}{#3}%
{%
\dtlexpandnewvalue
\DTLnewdbentry{datagloss}{Sort}{\expandonce\newgloss@sort}%
\DTLnewdbentry{datagloss}{Start}{\expandonce\newgloss@start}%
\DTLnewdbentry{datagloss}{End}{\expandonce\newgloss@end}%
\DTLnewdbentry{datagloss}{Item}{\expandonce\newgloss@item}%
}%
}

% Initialise default glossary:
\newgloss{glossary}{Glossary}

% Define some keys for \newterm

\newcommand*{\newterm@label}{}
\define@key{newterm}{label}{\renewcommand*{\newterm@label}{#1}}

\newcommand*{\newterm@plural}{}
\define@key{newterm}{plural}{\renewcommand*{\newterm@plural}{#1}}

\newcommand*{\newterm@sort}{}
\define@key{newterm}{sort}{\renewcommand*{\newterm@sort}{#1}}

\newcommand*{\newterm@symbol}{}
\define@key{newterm}{symbol}{\renewcommand*{\newterm@symbol}{#1}}

\newcommand*{\newterm@database}{}
\define@key{newterm}{database}{\renewcommand*{\newterm@database}{#1}}

\newcommand*{\newterm@long}{}
\define@key{newterm}{long}{%
\renewcommand*{\newterm@long}{#1}%
\renewcommand*{\newterm@longpl}{#1s}%
}

\newcommand*{\newterm@short}{}
\define@key{newterm}{short}{%
\renewcommand*{\newterm@short}{#1}%
\renewcommand*{\newterm@shortpl}{#1s}%
}

\newcommand*{\newterm@longpl}{}
\define@key{newterm}{longplural}{\renewcommand*{\newterm@longpl}{#1}}

\newcommand*{\newterm@shortpl}{}
\define@key{newterm}{shortplural}{\renewcommand*{\newterm@shortpl}{#1}}

% Syntax: \newterm[options]{name}{description}

\newcommand{\newterm}[3][]{%
\renewcommand*{\newterm@label}{#2}%
\renewcommand*{\newterm@sort}{#2}%
\renewcommand*{\newterm@plural}{#2s}%
\renewcommand*{\newterm@symbol}{}%
\renewcommand*{\newterm@database}{glossary}%
\renewcommand*{\newterm@short}{#2}%
\renewcommand*{\newterm@shortpl}{#2s}%
\renewcommand*{\newterm@long}{#3}%
\renewcommand*{\newterm@longpl}{#3s}%
\setkeys{newterm}{#1}%
\iftermexists{\newterm@label}%
{%
\PackageError{datagloss}{Term \newterm@label' already
exists in database \newterm@database'}{}%
}%
{%
\global\cslet{dataglossentry@\newterm@label}{\newterm@database}%
\DTLnewrow{\newterm@database}%
\DTLnewdbentry{\newterm@database}{Name}{#2}%
\DTLnewdbentry{\newterm@database}{Description}{#3}%
\DTLnewdbentry{\newterm@database}{Used}{0}%
{%
\dtlexpandnewvalue
\DTLnewdbentry{\newterm@database}{Label}{\expandonce\newterm@label}%
\DTLnewdbentry{\newterm@database}{Sort}{\expandonce\newterm@sort}%
\DTLnewdbentry{\newterm@database}{Plural}{\expandonce\newterm@plural}%
\DTLnewdbentry{\newterm@database}{Symbol}{\expandonce\newterm@symbol}%
\DTLnewdbentry{\newterm@database}{Short}{\expandonce\newterm@short}%
\DTLnewdbentry{\newterm@database}{Long}{\expandonce\newterm@long}%
\DTLnewdbentry{\newterm@database}{ShortPlural}{\expandonce\newterm@shortpl}%
\DTLnewdbentry{\newterm@database}{LongPlural}{\expandonce\newterm@longpl}%
}%
}%
}

% Syntax: \newacr[options]{short}{long}{description}
\newcommand*{\newacr}[4][]{%
\newterm[label={#2},short={\textsc{#2}},long={#3},#1]%
{#3 (\MakeUppercase{#2})}%
{#4}%
}

% Syntax: \iftermexists{label}{true part}{false part}

\newcommand{\iftermexists}[3]{%
\ifcsdef{dataglossentry@#1}{#2}{#3}%
}

% Syntax: \ifentryused{label}{true part}{false part}

\newcommand*{\ifentryused}[3]{%
\letcs{\newterm@database}{dataglossentry@#1}%
\dtlgetrowforvalue{\newterm@database}{\dtlcolumnindex{\newterm@database}{Label}}{#1}%
\dtlgetentryfromcurrentrow{\datagloss@value}{\dtlcolumnindex{\newterm@database}{Used}}%
\ifnum\datagloss@value=1\relax
#2%
\else
#3%
\fi
}

\newcommand*{\use@entry}[2]{%
\protected@write{\@auxout}{}{\string\datagloss@usedentry{#1}{\thepage}}%
\letcs{\newterm@database}{dataglossentry@#1}%
\dtlgetrowforvalue{\newterm@database}{\dtlcolumnindex{\newterm@database}{Label}}{#1}%
\dtlgetentryfromcurrentrow{\datagloss@value}{\dtlcolumnindex{\newterm@database}{#2}}%
\dtlreplaceentryincurrentrow{1}{\dtlcolumnindex{\newterm@database}{Used}}%
\dtlrecombine
}

\newcommand*{\datagloss@usedentry}[2]{%
\letcs{\newterm@database}{dataglossentry@#1}%
\dtlgetrowforvalue{\newterm@database}{\dtlcolumnindex{\newterm@database}{Label}}{#1}%
\dtlupdateentryincurrentrow{Location}{#2}%
\dtlrecombine
}

\newcommand*{\useentry}[2]{%
\use@entry{#1}{#2}%
\datagloss@value
}

\newcommand*{\Useentry}[2]{%
\use@entry{#1}{#2}%
\xmakefirstuc{\datagloss@value}%
}

\newcommand*{\useterm}[1]{\useentry{#1}{Name}}

\newcommand*{\usetermpl}[1]{\useentry{#1}{Plural}}

\newcommand*{\Useterm}[1]{\Useentry{#1}{Name}}

\newcommand*{\Usetermpl}[1]{\Useentry{#1}{Plural}}

\newcommand*{\usesymbol}[1]{\useentry{#1}{Symbol}}

\newcommand*{\acr}[1]{%
\ifentryused{#1}%
{\useentry{#1}{Short}}%
{\useentry{#1}{Long} (\useentry{#1}{Short})}%
}

\newcommand*{\acrpl}[1]{%
\ifentryused{#1}%
{\useentry{#1}{ShortPlural}}%
{\useentry{#1}{LongPlural} (\useentry{#1}{ShortPlural})}%
}

\newcommand*{\Acr}[1]{%
\ifentryused{#1}%
{\Useentry{#1}{Short}}%
{\Useentry{#1}{Long} (\useentry{#1}{Short})}%
}

\newcommand*{\Acrpl}[1]{%
\ifentryused{#1}%
{\Useentry{#1}{ShortPlural}}%
{\Useentry{#1}{LongPlural} (\useentry{#1}{ShortPlural})}%
}

\newcommand{\printterms}[1][glossary]{%
\renewcommand*{\newterm@database}{#1}%
% get the fields from datagloss:
\dtlgetrowforvalue{datagloss}{\dtlcolumnindex{datagloss}{Glossary}}{#1}%
\dtlgetentryfromcurrentrow{\datagloss@title}{\dtlcolumnindex{datagloss}{Title}}%
\dtlgetentryfromcurrentrow{\datagloss@sort}{\dtlcolumnindex{datagloss}{Sort}}%
\dtlgetentryfromcurrentrow{\datagloss@start}{\dtlcolumnindex{datagloss}{Start}}%
\dtlgetentryfromcurrentrow{\datagloss@end}{\dtlcolumnindex{datagloss}{End}}%
\dtlgetentryfromcurrentrow{\datagloss@item}{\dtlcolumnindex{datagloss}{Item}}%
% now display the glossary:
\datagloss@sort
\datagloss@start
\datagloss@foreachentry
{%
\datagloss@item
}%
\datagloss@end
}

\newcommand*{\datagloss@foreachentry}[1]{%
\DTLforeach*{\newterm@database}%
{\Name=Name,\Description=Description,\Used=Used,\Symbol=Symbol,%
\Long=Long,\Short=Short,\Location=Location}%
{%
\DTLifnull{\Location}{}{#1}%
}%
}

\endinput

And here's a sample document (remember it now needs two LaTeX runs):
\documentclass{article}

\usepackage{datagloss}
\usepackage{amsfonts}

\newterm{eagle}{large bird}
\newterm{elk}{type of deer}
\newterm[plural=deer]{deer}{hoofed animal where the male usually has horns}
\newterm[label=elite]{{\'e}lite}{select group of people}

\newgloss
[
sort={},% don't sort the entries
% use tabular instead of description environment:
start={\begin{tabular}{lcl}},
item={\xmakefirstuc{\Name} & \Symbol & \Description\\},
end={\end{tabular}}
]
{notation}
{Notation}

\newterm[database=notation,label=real,symbol={\ensuremath{\mathbb{R}}}]{real
number}{a value representing a quantity along a continuous line}
\newterm[database=notation,label=natural,symbol={\ensuremath{\mathbb{N}}}]{natural
number}{a positive integer or countable number}
\newterm[database=notation,label=integer,symbol={\ensuremath{\mathbb{I}}}]{integer}{zero or a positive or negative whole number}

\newgloss{acronym}{Acronyms}

\newacr[database=acronym,label=SVM]{svm}{support vector machine}{statistical pattern technique}

\begin{document}

\dtlforcolumn{\thisGlossary}{datagloss}{Glossary}
{\expandafter\printterms\expandafter[\thisGlossary]}

\section{Sample Section}

\useterm{eagle}. \usetermpl{deer}.
\Useterm{eagle}. \Usetermpl{eagle}.
\useterm{elite}. \Useterm{elite}. \useterm{elk}.

Checking for defined labels:
eagle \iftermexists{eagle}{exists}{doesn't exist};
dodo \iftermexists{dodo}{exists}{doesn't exist}.

The set of \usetermpl{integer} is denoted
\usesymbol{integer}.

The set of \usetermpl{natural} is denoted
\usesymbol{natural}.

First use: \acr{SVM}. Next use: \acr{SVM}.

\end{document}


This is where things start to get really complicated and where you'd be better off using an indexing application, but if you're determined to persevere without one, this section will hopefully get you started.

In the previous section I defined \datagloss@usedentry to set the Location field for the given entry. I used \dtlupdateentryincurrentrow, which appends the value if it doesn't already exist otherwise it replaces the existing value for the given field. This needs to be modified so that each new location is added to the Location field:

\newcommand*{\datagloss@usedentry}[2]{%
\letcs{\newterm@database}{dataglossentry@#1}%
\dtlgetrowforvalue{\newterm@database}{\dtlcolumnindex{\newterm@database}{Label}}{#1}%
\dtlgetentryfromcurrentrow{\datagloss@loc}{\dtlcolumnindex{\newterm@database}{Location}}%
\ifx\datagloss@loc\dtlnovalue
\dtlappendentrytocurrentrow{Location}{#2}%
\else
\appto\datagloss@loc{,#2}%
\expandafter\dtlreplaceentryincurrentrow\expandafter
{\datagloss@loc}{\dtlcolumnindex{\newterm@database}{Location}}%
\fi
\dtlrecombine
}

This uses \dtlgetentryfromcurrentrow to set \datagloss@loc to the current value of the Location field. If no value is found, \datagloss@loc will be set to \dtlnovalue. This can be tested using \ifx. If it hasn't been set, then just use \dtlappendentrytocurrentrow to append the location. If it has been set, then etoolbox's \appto command can be used to append the new location to the list. Then this updated value stored in \datagloss@loc needs to be put in the Location column of the current row using \dtlreplaceentryincurrentrow. However, \dtlreplaceentryincurrentrow doesn't expand the value so a couple of \expandafter commands are needed to expand the value before inserting it into the current row.

Note: if you're only interested in the location of the first time the entry was used, you can simplify this code to:

\newcommand*{\datagloss@usedentry}[2]{%
\letcs{\newterm@database}{dataglossentry@#1}%
\dtlgetrowforvalue{\newterm@database}{\dtlcolumnindex{\newterm@database}{Label}}{#1}%
\dtlgetentryfromcurrentrow{\datagloss@loc}{\dtlcolumnindex{\newterm@database}{Location}}%
\ifx\datagloss@loc\dtlnovalue
\dtlappendentrytocurrentrow{Location}{#2}%
\fi
\dtlrecombine
}

Next I'm going to modify \newgloss so that the entry item also displays the location list:
\newcommand*{\newgloss}[3][]{%
\renewcommand*{\newgloss@sort}{\DTLsort{Sort}{#2}}%
\renewcommand*{\newgloss@start}{\def\printgloss@tmp{\item[]}\begin{description}}%
\renewcommand*{\newgloss@end}{\printgloss@tmp\end{description}}%
\renewcommand*{\newgloss@item}{%
\item[\xmakefirstuc{\Name}]
\ifdefempty{\Symbol}{}{(\Symbol) }\Description. \dolocationlist
\def\printgloss@tmp{}%
}%
\setkeys{newgloss}{#1}%
\DTLnewdb{#2}%
\DTLnewrow{datagloss}%
\DTLnewdbentry{datagloss}{Glossary}{#2}%
\DTLnewdbentry{datagloss}{Title}{#3}%
{%
\dtlexpandnewvalue
\DTLnewdbentry{datagloss}{Sort}{\expandonce\newgloss@sort}%
\DTLnewdbentry{datagloss}{Start}{\expandonce\newgloss@start}%
\DTLnewdbentry{datagloss}{End}{\expandonce\newgloss@end}%
\DTLnewdbentry{datagloss}{Item}{\expandonce\newgloss@item}%
}%
}

All I've done here is add \dolocationlist after the description. This makes it easier to switch the location list off if you decide you don't want it for a particular document as then you can just redefine \dolocationlist to do nothing.

If you decide only to store the first location, then the next part is easy:

\newcommand*{\dolocationlist}{\Location}

If you have stored all the locations, then \Location is set to a comma-separated list of locations. So if you used an entry on page 1, again on page 5 and finally on page 8, the \Location for that entry will expand to 1,5,8. This isn't so bad, but maybe you have used an entry three times on page 1, four times on page 2 and twice on page 3, then the \Location for that entry will expand to 1,1,1,2,2,2,2,3,3. This is something that an external indexing application, such as makeindex or xindy, would collate into a more compact list "1, 2, 3" or "1--3". So can we get (La)TeX to do it? The etoolbox provides a way of iterating through a comma-separated list with the \forcsvlist command, so here's my modified definition of \dolocationlist:
\newcommand*{\dolocationlist}{%
\def\prev@location{}%
\def\location@sep{}%
\expandafter\forcsvlist\expandafter\parse@location
\expandafter{\Location}%
}

\newcommand*{\parse@location}[1]{%
\ifthenelse{\equal{\prev@location}{#1}}%
{}% do nothing, same as previous location
{%
\location@sep
#1%
\def\prev@location{#1}%
\def\location@sep{, }%
}%
}

This just ignores the redundancies, so if \Location expands to 1,1,1,2,2,2,2,3,3, then this will display "1, 2, 3". It works as follows: first \dolocationlist initialises \prev@location and \location@sep to do nothing. Later, \prev@location will be set to the previous location in the list and \location@sep will be set to the list separator (", "). Next I want to use \forcsvlist to iterate through \Location using the handler function \parse@location. However, \forcsvlist needs the list to be expanded before use, so \expandafter is needed.

The handler function \parse@location works as follows: if the current location is the same as the previous location, do nothing, otherwise display the location separator, display the location, and then update \prev@location and \location@sep.

Why use a string comparison on the location instead of a numerical comparison? Because not all page numbers are arabic numerals. They may be roman numerals, alphabetical, or they may even be a composite, such as 2-5 indicating the fifth page of the second chapter. This makes it difficult to concatenate a run of locations, such as "1, 2, 3", using TeX. That kind of task is much better suited to a scripting language, such as Perl, hence the need for external indexing applications. However, if you're determined not to use one and you know that your locations are either roman or arabic and don't form composites, then it's possible to perform some concatenation. The following code is a fairly simplistic approach.

\newcommand*{\dolocationlist}{%
\def\prev@location{-1}%
\def\prev@locationvalue{}%
\def\location@sep{}%
\def\location@start{-1}%
\expandafter\forcsvlist\expandafter\parse@location
\expandafter{\Location}%
\do@prevlocation % tidy up loose ends
}

\newif\ifsequential

\newcommand*{\parse@location}[1]{%
\ifthenelse{\equal{\prev@location}{#1}}%
{}% do nothing, same as previous location
{%
% is the location a roman numeral?
\ifrmnum{#1}%
{% is roman
\edef\current@location{\rmtonum{#1}}%
}%
{%
\def\current@location{#1}%
}%
\ifnumequal{\prev@location+1}{\current@location}%
{% one more than previous value
% are both the same type?
\ifrmnum{#1}%
{% current location is roman, is previous?
\ifrmnum{\prev@locationvalue}%
{% yes, we have a sequence
\sequentialtrue
}%
{% no, we don't have a sequence
\sequentialfalse
}%
}%
{% current location isn't roman, is previous?
\ifrmnum{\prev@locationvalue}%
{% previous is roman, so we don't have a sequence
\sequentialfalse
}%
{% previous isn't roman, so we have a sequence
\sequentialtrue
}%
}%
}%
{%
\sequentialfalse
}%
\ifsequential
% has the start of the sequence been set?
\ifnumequal{\location@start}{-1}%
{% no it hasn't, so set it
\let\location@start\prev@location
\let\location@startval\prev@locationvalue
}%
{%
}%
\else
\do@prevlocation
\fi
\def\prev@location{#1}%
\def\prev@locationvalue{#1}%
}%
}

\newcommand*{\do@prevlocation}{%
% have we come to the end of a sequence?
\ifnumequal{\location@start}{-1}%
{% not the end of a sequence
\ifdefempty{\prev@locationvalue}%
{}%
{%
\location@sep
\prev@locationvalue
\def\location@sep{, }%
}%
}%
{% at the end of a sequence
\location@sep
\do@locrange
\def\location@sep{, }%
\def\location@start{-1}%
}%
}

\newcommand*{\do@locrange}{%
% are the start and end locations 2 or more apart?
\ifnumgreater{\current@location}{\location@start+1}%
{% yes, they are, so form a range:
\location@startval--\prev@locationvalue
}%
{% no, they aren't so don't form a range:
\location@startval, \prev@locationvalue
}%
}


You can customise \do@locrange. For example, suppose you want the sequence "3,4" changed to "2f" whereas a longer sequence, such as "3,4,5,6", needs to be "3ff", then you can modify \do@locrange as follows:
\newcommand*{\do@locrange}{%
% are the start and end locations 2 or more apart?
\ifnumgreater{\current@location}{\location@start+1}%
{% yes, they are
\location@startval ff%
}%
{% no, they aren't
\location@startval f%
}%
}

Here's the package code in full:
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{datagloss}[2012/09/24 v1.0]

\RequirePackage{datatool}
\RequirePackage{etoolbox}
\RequirePackage{xkeyval}
\RequirePackage{mfirstuc}

\DTLnewdb{datagloss}

% Define some keys for \newgloss:

\newcommand*{\newgloss@sort}{}
\define@key{newgloss}{sort}{\renewcommand*{\newgloss@sort}{#1}}

\newcommand*{\newgloss@start}{}
\define@key{newgloss}{start}{\renewcommand*{\newgloss@start}{#1}}

\newcommand*{\newgloss@end}{}
\define@key{newgloss}{end}{\renewcommand*{\newgloss@end}{#1}}

\newcommand*{\newgloss@item}{}
\define@key{newgloss}{item}{\renewcommand*{\newgloss@item}{#1}}

% Syntax: \newgloss[options]{database name}{glossary title}

\newcommand*{\newgloss}[3][]{%
\renewcommand*{\newgloss@sort}{\DTLsort{Sort}{#2}}%
\renewcommand*{\newgloss@start}{\def\printgloss@tmp{\item[]}\begin{description}}%
\renewcommand*{\newgloss@end}{\printgloss@tmp\end{description}}%
\renewcommand*{\newgloss@item}{%
\item[\xmakefirstuc{\Name}]
\ifdefempty{\Symbol}{}{(\Symbol) }\Description.  \dolocationlist
\def\printgloss@tmp{}%
}%
\setkeys{newgloss}{#1}%
\DTLnewdb{#2}%
\DTLnewrow{datagloss}%
\DTLnewdbentry{datagloss}{Glossary}{#2}%
\DTLnewdbentry{datagloss}{Title}{#3}%
{%
\dtlexpandnewvalue
\DTLnewdbentry{datagloss}{Sort}{\expandonce\newgloss@sort}%
\DTLnewdbentry{datagloss}{Start}{\expandonce\newgloss@start}%
\DTLnewdbentry{datagloss}{End}{\expandonce\newgloss@end}%
\DTLnewdbentry{datagloss}{Item}{\expandonce\newgloss@item}%
}%
}

\newcommand*{\dolocationlist}{%
\def\prev@location{-1}%
\def\prev@locationvalue{}%
\def\location@sep{}%
\def\location@start{-1}%
\expandafter\forcsvlist\expandafter\parse@location
\expandafter{\Location}%
\do@prevlocation % tidy up loose ends
}

\newif\ifsequential

\newcommand*{\parse@location}[1]{%
\ifthenelse{\equal{\prev@location}{#1}}%
{}% do nothing, same as previous location
{%
% is the location a roman numeral?
\ifrmnum{#1}%
{% is roman
\edef\current@location{\rmtonum{#1}}%
}%
{%
\def\current@location{#1}%
}%
\ifnumequal{\prev@location+1}{\current@location}%
{% one more than previous value
% are both the same type?
\ifrmnum{#1}%
{% current location is roman, is previous?
\ifrmnum{\prev@locationvalue}%
{% yes, we have a sequence
\sequentialtrue
}%
{% no, we don't have a sequence
\sequentialfalse
}%
}%
{% current location isn't roman, is previous?
\ifrmnum{\prev@locationvalue}%
{% previous is roman, so we don't have a sequence
\sequentialfalse
}%
{% previous isn't roman, so we have a sequence
\sequentialtrue
}%
}%
}%
{%
\sequentialfalse
}%
\ifsequential
% has the start of the sequence been set?
\ifnumequal{\location@start}{-1}%
{% no it hasn't, so set it
\let\location@start\prev@location
\let\location@startval\prev@locationvalue
}%
{%
}%
\else
\do@prevlocation
\fi
\def\prev@location{#1}%
\def\prev@locationvalue{#1}%
}%
}

\newcommand*{\do@prevlocation}{%
% have we come to the end of a sequence?
\ifnumequal{\location@start}{-1}%
{% not the end of a sequence
\ifdefempty{\prev@locationvalue}%
{}%
{%
\location@sep
\prev@locationvalue
\def\location@sep{, }%
}%
}%
{% at the end of a sequence
\location@sep
\do@locrange
\def\location@sep{, }%
\def\location@start{-1}%
}%
}

\newcommand*{\do@locrange}{%
% are the start and end locations 2 or more apart?
\ifnumgreater{\current@location}{\location@start+1}%
{% yes, they are, so form a range:
\location@startval--\prev@locationvalue
}%
{% no, they aren't so don't form a range:
\location@startval, \prev@locationvalue
}%
}

% Initialise default glossary:
\newgloss{glossary}{Glossary}

% Define some keys for \newterm

\newcommand*{\newterm@label}{}
\define@key{newterm}{label}{\renewcommand*{\newterm@label}{#1}}

\newcommand*{\newterm@plural}{}
\define@key{newterm}{plural}{\renewcommand*{\newterm@plural}{#1}}

\newcommand*{\newterm@sort}{}
\define@key{newterm}{sort}{\renewcommand*{\newterm@sort}{#1}}

\newcommand*{\newterm@symbol}{}
\define@key{newterm}{symbol}{\renewcommand*{\newterm@symbol}{#1}}

\newcommand*{\newterm@database}{}
\define@key{newterm}{database}{\renewcommand*{\newterm@database}{#1}}

\newcommand*{\newterm@long}{}
\define@key{newterm}{long}{%
\renewcommand*{\newterm@long}{#1}%
\renewcommand*{\newterm@longpl}{#1s}%
}

\newcommand*{\newterm@short}{}
\define@key{newterm}{short}{%
\renewcommand*{\newterm@short}{#1}%
\renewcommand*{\newterm@shortpl}{#1s}%
}

\newcommand*{\newterm@longpl}{}
\define@key{newterm}{longplural}{\renewcommand*{\newterm@longpl}{#1}}

\newcommand*{\newterm@shortpl}{}
\define@key{newterm}{shortplural}{\renewcommand*{\newterm@shortpl}{#1}}

% Syntax: \newterm[options]{name}{description}

\newcommand{\newterm}[3][]{%
\renewcommand*{\newterm@label}{#2}%
\renewcommand*{\newterm@sort}{#2}%
\renewcommand*{\newterm@plural}{#2s}%
\renewcommand*{\newterm@symbol}{}%
\renewcommand*{\newterm@database}{glossary}%
\renewcommand*{\newterm@short}{#2}%
\renewcommand*{\newterm@shortpl}{#2s}%
\renewcommand*{\newterm@long}{#3}%
\renewcommand*{\newterm@longpl}{#3s}%
\setkeys{newterm}{#1}%
\iftermexists{\newterm@label}%
{%
\PackageError{datagloss}{Term \newterm@label' already
exists in database \newterm@database'}{}%
}%
{%
\global\cslet{dataglossentry@\newterm@label}{\newterm@database}%
\DTLnewrow{\newterm@database}%
\DTLnewdbentry{\newterm@database}{Name}{#2}%
\DTLnewdbentry{\newterm@database}{Description}{#3}%
\DTLnewdbentry{\newterm@database}{Used}{0}%
{%
\dtlexpandnewvalue
\DTLnewdbentry{\newterm@database}{Label}{\expandonce\newterm@label}%
\DTLnewdbentry{\newterm@database}{Sort}{\expandonce\newterm@sort}%
\DTLnewdbentry{\newterm@database}{Plural}{\expandonce\newterm@plural}%
\DTLnewdbentry{\newterm@database}{Symbol}{\expandonce\newterm@symbol}%
\DTLnewdbentry{\newterm@database}{Short}{\expandonce\newterm@short}%
\DTLnewdbentry{\newterm@database}{Long}{\expandonce\newterm@long}%
\DTLnewdbentry{\newterm@database}{ShortPlural}{\expandonce\newterm@shortpl}%
\DTLnewdbentry{\newterm@database}{LongPlural}{\expandonce\newterm@longpl}%
}%
}%
}

% Syntax: \newacr[options]{short}{long}{description}
\newcommand*{\newacr}[4][]{%
\newterm[label={#2},short={\textsc{#2}},long={#3},#1]%
{#3 (\MakeUppercase{#2})}%
{#4}%
}

% Syntax: \iftermexists{label}{true part}{false part}

\newcommand{\iftermexists}[3]{%
\ifcsdef{dataglossentry@#1}{#2}{#3}%
}

% Syntax: \ifentryused{label}{true part}{false part}

\newcommand*{\ifentryused}[3]{%
\letcs{\newterm@database}{dataglossentry@#1}%
\dtlgetrowforvalue{\newterm@database}{\dtlcolumnindex{\newterm@database}{Label}}{#1}%
\dtlgetentryfromcurrentrow{\datagloss@value}{\dtlcolumnindex{\newterm@database}{Used}}%
\ifnum\datagloss@value=1\relax
#2%
\else
#3%
\fi
}

\newcommand*{\use@entry}[2]{%
\protected@write{\@auxout}{}{\string\datagloss@usedentry{#1}{\thepage}}%
\letcs{\newterm@database}{dataglossentry@#1}%
\dtlgetrowforvalue{\newterm@database}{\dtlcolumnindex{\newterm@database}{Label}}{#1}%
\dtlgetentryfromcurrentrow{\datagloss@value}{\dtlcolumnindex{\newterm@database}{#2}}%
\dtlreplaceentryincurrentrow{1}{\dtlcolumnindex{\newterm@database}{Used}}%
\dtlrecombine
}

\newcommand*{\datagloss@usedentry}[2]{%
\letcs{\newterm@database}{dataglossentry@#1}%
\dtlgetrowforvalue{\newterm@database}{\dtlcolumnindex{\newterm@database}{Label}}{#1}%
\dtlgetentryfromcurrentrow{\datagloss@loc}{\dtlcolumnindex{\newterm@database}{Location}}%
\ifx\datagloss@loc\dtlnovalue
\dtlappendentrytocurrentrow{Location}{#2}%
\else
\appto\datagloss@loc{,#2}%
\expandafter\dtlreplaceentryincurrentrow\expandafter
{\datagloss@loc}{\dtlcolumnindex{\newterm@database}{Location}}%
\fi
\dtlrecombine
}

\newcommand*{\useentry}[2]{%
\use@entry{#1}{#2}%
\datagloss@value
}

\newcommand*{\Useentry}[2]{%
\use@entry{#1}{#2}%
\xmakefirstuc{\datagloss@value}%
}

\newcommand*{\useterm}[1]{\useentry{#1}{Name}}

\newcommand*{\usetermpl}[1]{\useentry{#1}{Plural}}

\newcommand*{\Useterm}[1]{\Useentry{#1}{Name}}

\newcommand*{\Usetermpl}[1]{\Useentry{#1}{Plural}}

\newcommand*{\usesymbol}[1]{\useentry{#1}{Symbol}}

\newcommand*{\acr}[1]{%
\ifentryused{#1}%
{\useentry{#1}{Short}}%
{\useentry{#1}{Long} (\useentry{#1}{Short})}%
}

\newcommand*{\acrpl}[1]{%
\ifentryused{#1}%
{\useentry{#1}{ShortPlural}}%
{\useentry{#1}{LongPlural} (\useentry{#1}{ShortPlural})}%
}

\newcommand*{\Acr}[1]{%
\ifentryused{#1}%
{\Useentry{#1}{Short}}%
{\Useentry{#1}{Long} (\useentry{#1}{Short})}%
}

\newcommand*{\Acrpl}[1]{%
\ifentryused{#1}%
{\Useentry{#1}{ShortPlural}}%
{\Useentry{#1}{LongPlural} (\useentry{#1}{ShortPlural})}%
}

\newcommand{\printterms}[1][glossary]{%
\renewcommand*{\newterm@database}{#1}%
% get the fields from datagloss:
\dtlgetrowforvalue{datagloss}{\dtlcolumnindex{datagloss}{Glossary}}{#1}%
\dtlgetentryfromcurrentrow{\datagloss@title}{\dtlcolumnindex{datagloss}{Title}}%
\dtlgetentryfromcurrentrow{\datagloss@sort}{\dtlcolumnindex{datagloss}{Sort}}%
\dtlgetentryfromcurrentrow{\datagloss@start}{\dtlcolumnindex{datagloss}{Start}}%
\dtlgetentryfromcurrentrow{\datagloss@end}{\dtlcolumnindex{datagloss}{End}}%
\dtlgetentryfromcurrentrow{\datagloss@item}{\dtlcolumnindex{datagloss}{Item}}%
% now display the glossary:
\datagloss@sort
\datagloss@start
\datagloss@foreachentry
{%
\datagloss@item
}%
\datagloss@end
}

\newcommand*{\datagloss@foreachentry}[1]{%
\DTLforeach*{\newterm@database}%
{\Name=Name,\Description=Description,\Used=Used,\Symbol=Symbol,%
\Long=Long,\Short=Short,\Location=Location}%
{%
\DTLifnull{\Location}{}{#1}%
}%
}

\endinput

And here's the sample document (I've added the lipsum package to pad out the text):
\documentclass{article}

\usepackage{datagloss}
\usepackage{amsfonts}
\usepackage{lipsum}

\newterm{eagle}{large bird}
\newterm{elk}{type of deer}
\newterm[plural=deer]{deer}{hoofed animal where the male usually has horns}
\newterm[label=elite]{{\'e}lite}{select group of people}

\newgloss
[
sort={},% don't sort the entries
% use tabular instead of description environment:
start={\begin{tabular}{lcl}},
item={\xmakefirstuc{\Name} & \Symbol & \Description\\},
end={\end{tabular}}
]
{notation}
{Notation}

\newterm[database=notation,label=real,symbol={\ensuremath{\mathbb{R}}}]{real
number}{a value representing a quantity along a continuous line}
\newterm[database=notation,label=natural,symbol={\ensuremath{\mathbb{N}}}]{natural
number}{a positive integer or countable number}
\newterm[database=notation,label=integer,symbol={\ensuremath{\mathbb{I}}}]{integer}{zero or a positive or negative whole number}

\newgloss{acronym}{Acronyms}

\newacr[database=acronym,label=SVM]{svm}{support vector machine}{statistical pattern technique}

\begin{document}

\dtlforcolumn{\thisGlossary}{datagloss}{Glossary}
{\expandafter\printterms\expandafter[\thisGlossary]}

\section{Sample Section}

\useterm{eagle}. \usetermpl{deer}.
First use: \acr{SVM}.

\lipsum

\Useterm{eagle}. \Usetermpl{eagle}.

\lipsum[1-4]

\useterm{eagle}.  \useterm{elite}. \Useterm{elite}. \useterm{elk}.

The set of \usetermpl{integer} is denoted
\usesymbol{integer}.

The set of \usetermpl{natural} is denoted
\usesymbol{natural}.

Next use: \acr{SVM}.

\end{document} `

The image below shows a portion of the sample document including the glossaries and the start of a sample section.

If you have any difficulties implementing or adapting the code here, post a message in the forum.

dervomsee
0

That's nice! I will try it in my next bigger article. How is it with lualatex support?
Regards!
Nicola Talbot
0

I've never used lua, but it would be interesting to know.
Will Robertson
+1

This is great! LaTeX-based indexing is something I've wanted for ages. Are you planning on releasing this as a stand-alone package?
Nicola Talbot
0

I wasn't planning on it (unless there's a lot of demand).
Christophe Aeby
+1

Hi,

This is impressing! Thank you a lot! It would be realy grate to have it as a stand-alone package! ;-)

Thank you a lot for this improvement
Nicola Talbot
+1

Okay, I'll add a new package to the datatool bundle.
Nicola Talbot
+1

Okay, the next version of datatool will have a new package called datagidx which is an enhancement of the example code here (for both indexes and glossaries).

### Latest Forum Posts

﻿

How to add entity ,such as insetlayout flex, into Lyx outlin
01/09/2015 02:06, Smith chen

Re: How to create a flyer with this layout?
31/08/2015 21:12, Stefan_K

Re: How to create a flyer with this layout?
31/08/2015 15:54, Johannes_B

Re: Compilation:impossible
31/08/2015 15:51, Stefan_K

How to create a flyer with this layout?
31/08/2015 15:13, leoh

Re: Compilation:impossible
31/08/2015 15:01, Johannes_B

Re: "Undefined control sequence" error from ACS template ach
31/08/2015 14:55, PZJuly

Re: Compilation:impossible
31/08/2015 14:54, LucaCrippa

Re: "Undefined control sequence" error from ACS template ach
31/08/2015 14:51, Johannes_B

"Undefined control sequence" error from ACS template achemso
31/08/2015 14:19, PZJuly