%D \module
%D   [       file=m-datastrc, % was: core-dat % was core-02a
%D        version=1999.08.10,               % 1997.03.31,
%D          title=\CONTEXT\ Modules,
%D       subtitle=Database Support,         % 2A
%D         author=Hans Hagen,
%D           date=\currentdate,
%D      copyright={PRAGMA / Hans Hagen \& Ton Otten}]
%C
%C This module is part of the \CONTEXT\ macro||package and is
%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
%C details.

\writestatus{loading}{ConTeXt Core Macros / Database Support}

\unprotect

%D This module is a (limited) rewrite of the original \type
%D {core-02a} module, the module that dealt with managing a
%D database of addresses. The principles and methods have not
%D changed; they are only generalized.
%D
%D A database file |<|in most cases such a base is generated
%D from another one|>| is structured as follows:
%D
%D \starttyping
%D \startrecord{tag}
%D   \memberofgroup{grouplist}
%D   \setrecordentry{name}{...}
%D   ....
%D \stoprecord
%D \stoptyping
%D
%D The interface to such a database is defined as follows:
%D
%D \starttyping
%D \definerecord[class][settings]
%D \setuprecord[class][settings]
%D \definerecordentry[class][name]
%D \stoptyping
%D
%D and processed by
%D
%D \starttyping
%D \processrecords[file list][tag and/or group list]
%D \stoptyping
%D
%D The actual processing is done by a macro assigned to \type
%D {command}:
%D
%D \starttyping
%D \setuprecord[class][command=\DoWithRecord]
%D \stoptyping
%D
%D Given that one can ask for a field with
%D
%D \starttyping
%D \getrecordentry{name}
%D \stoptyping
%D
%D such a command can look like:
%D
%D \starttyping
%D \def\DoWithRecord#1%
%D   {\startpacked
%D    \let\\=\quad
%D    name:  \getrecordentry{name}~\getrecordentry{family name}\par
%D    address: \getrecordentry{postal address}\par
%D    \stoppacked}
%D \stoptyping
%D
%D The argument passed is the tag. The database can look like:
%D
%D \starttyping
%D \startrecord{hagenj}
%D   \memberofgroup{a,b}
%D   \setrecordentry{naam}{Hans}
%D   \setrecordentry{family name}{Hagen}
%D   \setrecordentry{postal address}{J. Hagen\\Ridderstraat 29\\Hasselt NL}
%D \stoprecord
%D
%D \startrecord{ottenaf}
%D   \memberofgroup{a}
%D   \setrecordentry{name}{Ton}
%D   \setrecordentry{family name}{Otten}
%D   \setrecordentry{postal address}{A.F. Otten\\Prinsengracht 17\\Hasselt NL}
%D \stoprecord
%D \stoptyping
%D
%D The definition of this database looks like:
%D
%D \starttyping
%D \definerecord[address][command=\DoWithRecord]
%D
%D \definerecordentry[address][name]
%D \definerecordentry[address][family name]
%D \definerecordentry[address][postal address]
%D \stoptyping
%D
%D The actual processing is now done by (for instance):
%D
%D \starttyping
%D \processrecords[datafile][hagenj]
%D \processrecords[datafile][hagenj,offenaf]
%D \processrecords[datafile][all]
%D \processrecords[datafile][a]
%D \processrecords[datafile][b]
%D \stoptyping
%D
%D Of course one can reassign the command used to handle the
%D records in between.

% \??kt ->
% \??kw ->

\def\??db    {@@db}
\def\c!velden{velden}

%\newevery \everyrecord \EveryRecord

\def\definerecord
  {\dodoubleempty\dodefinerecord}

\def\dodefinerecord[#1][#2]%
  {\getparameters
     [\??db#1]
     [\c!velden=,
      \c!command=\gobbleoneargument,
      #2]}

\def\setuprecord
  {\dodoubleargument\dosetuprecord}

\def\dosetuprecord[#1][#2]%
  {\getparameters[\??db#1][#2]}%

\def\definerecordentry[#1][#2]%
  {\edef\recordentries{\getvalue{\??db#1\c!velden}}%
   \addtocommalist{#2}\recordentries
   \letvalue{\??db#1\c!velden}\recordentries}

%D Watch out: the entries are defined global! While
%D processing a record, no grouping is applied.

\def\getrecordentry   #1{\getvalue      {\??db:#1}}
\def\resetrecordentry #1{\letgvalueempty{\??db:#1}}
\def\assignrecordentry#1{\setgvalue     {\??db:#1}}

\long\def\skiprecord#1\stoprecord
  {\egroup}

\newif\ifrecordok

\newtoks\resetrecordlist

\def\processrecords
  {\dotripleargument\doprocessrecords}

\def\doprocessrecords[#1][#2][#3]%
  {\bgroup
   \ifx\\\undefined\let\\\relax\fi
   \def\docommand##1%
     {\resetrecordentry{##1}%
      \appendtoks\resetrecordentry{##1}\to\resetrecordlist}%
   \processcommacommand[\getvalue{\??db#1\c!velden}]\docommand
   \let\setrecordentry\skiprecord
   \the\resetrecordlist
   \doifelse{#2}\v!all   % 't Is nu eenmaal alles
     \recordoktrue
     {\doifelsenothing{#2} % of niets
        \recordoktrue
        \recordokfalse}%   % zullen we maar zeggen.
   \ifrecordok
     \let\askedrecords\v!all
   \else
     \makerawcommalist[#2]\askedrecords
   \fi
   \def\checkrecord##1%
     {\rawdoifinsetelse{##1}{\askedrecords}{\recordoktrue}{}}%
   \def\presetrecord##1%
     {\let\setrecordentry\assignrecordentry
      \let\memberofgroup\gobbleoneargument
      \the\resetrecordlist
      \def\stoprecord{\dostoprecord{##1}}}%
   \def\memberofgroup##1%
     {\doifsomething{##1}
        {\rawprocesscommalist[##1]\checkrecord}%
      \ifrecordok
        \presetrecord{##1}%
      \else
        \expandafter\skiprecord
      \fi}%
   \def\startrecord##1%
     {\bgroup
      \ifrecordok
        \presetrecord{##1}%
      \else
        \checkrecord{##1}%
        \ifrecordok
          \presetrecord{##1}%
        \fi
      \fi}%
   \def\dostoprecord##1%
     {\relax
      \egroup
     %\the\everyrecord
      \getvalue{\??db#1\c!command}{##1}}%
   \showmessage\m!databases1\askedrecords
   \def\doprocessrecords##1%
     {\readjobfile{##1}
        {\showmessage\m!databases2{(job)}}
        {\readsysfile{##1}
           {\showmessage\m!databases3{(sys)}}
           {\showmessage\m!databases4{}}}}%
   \processcommalist[#3]\doprocessrecords
   \egroup}

%D While writing the original implementation, I did some
%D experiments with \type {%} before each entry and changing
%D the category code of the comment char. Because \TEX\ scans
%D the line anyway |<|this is needed because the end of line
%D character can be non standard|>| this is not faster.
%D
%D Although this mechanism could have been combined with the
%D block moving mechanism, the current implementation is
%D prefered out of speed reasons.

\protect \endinput
