Greene's Programming Pages
C++

"cfgstl" — A Configuration File Parser Using C++ and STL

Hi, fellow programmers!

Yeah, yeah, I know, I gave you a nifty little class to read a Windows INI-style configuration text file, but I didn't give you any code examples of actually using it. That's what this page is for. So let's get started!


I just recently finished writing a program in which I use my CfgReader class, and I'll use this program to demonstrate to you how I use the class. And, oh, by the way, you might get a program that's also useful to you. This program also gives you a practical C++ example of creating an index on a file of records, and since I use the STL "map" structure in which I build and store the index in memory this program also gives you a practical C++ example of using an STL map.(The program code is shown in its entirety below.)

My purpose for writing this program was to read a file of fixed-field, LF-delimited records, then, based on "key string" constructed from the values of user-specified fields, generate an output file that consisted of only unique "keys." In other words, I wanted this program to perform a "de-duping" function.

You can think about the logic for doing this in the following manner: First, we have to have a way to look at the records with the key values being in an ascending order. Second, as we step through the records in this ordered manner, we simply compare the each record's key value to the key value of the previous record. If it is a different (new) key value, then we write the record to the output file. If the key value is the same, as the previous record, we do not write the record to the output file. Logically, it's that simple. Additionally, and what I have done in this program, we might "mark" the output record in such a way so that subsequent processes, if they need to, can tell whether or not any particular record that survived from the original input file was a unique key value or was a member of a group of two or more records that shared the same key value.

The file must first be sorted into ascending order based on the key (and the key will actually be made up of multiple fields if the user wishes to define more than one field as making up the key, which she or he does in the configuration file). Alternatively, we could simply construct an index on the file that consists of the key and the file location of the record containing that key, sort the index entries, then use the index to "fseek()" into the file by ascending order of the key values. This alternative method takes a little bit more programming, but is actually much more efficient, both in terms of user interaction (the user only needs to run one program) and in terms of processing (an index is smaller than the orginal data file, and if it is not too large it can all be done in memory). So for efficiency, we do the alternative method.

Now, to be even more efficient, since we only need to reference unique key values, we can examine the key values as we are constructing the index. If the key value is already in the index, then the current record which we were examining to put into the index can simply be skipped, because that is the whole reason for this program: Skip records with duplicate key values, only outputting the first occurrences of any key value so that the output file contains no records with duplicate key values. In other words, we can implement this "de-duping" logic right into the construction of the index, then when we use the index to read the input records in ascending order by key value we will at that time automatically only be reading records with unique values and we will have reduced the second read loop by the number of duplicate key value records.

And — wa la! — that is exactly what the STL map structure buys us. It's purpose is to store unique values in the first member element. So we can construct the map in memory, with the key value as the first member element and the file position of the record with that key value as the second member element.

But since we might want to "mark" any particular to indicate that it was a member of a group of two or more records with the same key value, we need to also store this indicator. It could be a bool, but I chose to use char. Additionally, I wanted the program to also keep track of how many records actually occurred that had any particular key value stored in the map. Therefore, while the first member element of the map is simply the key value, stored as an STL string, I made the second member element a structure I called "RecInfo" which contains a long "position", a char "dupeflag", and an unsigned long "groupcount".

So now we're ready. As we read through the input file the first time during which we simply construct an index and do not write any output, we construct the "key" string from the user-specified field(s). As the program proceeds through the file, the first occurrence of a particular key value for a record is then considered the first member of a "key group," which means "a group of records that all possess the same key value." Since at this point we are simply creating an index with an STL map, for each record we simply check to see if the key value already exists in the map (that's the "it_index = index.find(key);" using an iterator to reference the map). If the key value is not in the map, we insert it along with the appropriate record information: file position, initialize duplicate "mark" to blank, and initialize count to 1. If the key value is already in the map, we "mark" the already existing entry with the letter "M" and increment the count for this key value entry by 1.

Once we have created the index, we reset the file pointer to the beginning of the file, then simply loop through the index, performing a "random access" read (that's the "fseek(fs_in, (*it_index).second.position, SEEK_SET)") on the file and outputting only these records. Very simple, very fast. Just the way I like it!

Oh, yeah, I almost forgot about the use of the CfgReader class!

Note that the number of pieces of information you may be pulling into the program from the configuration file that the program user has used to tell the program what to do (and, maybe, where to go!) can grow to a lot of items. Additionally, since we are talking about user entry, you'll definitely be wanting to add a bunch of validation logic to make sure all of this user entered information looks okay and follows the prescribed format. Therefore, my routine that performs all of that funcationality is something I always code as a function separate from the main(). I call it "GetParms". Now, we can wrap all of the user-configured parameter information in a structure, to keep it all together and make it easier to pass around from one function to another. I happen to have gotten in to the habit of keeping my user-configured filenames separate from the rest of my parameter values, so in this program you actually see two structures: FSNAMESTRUCT, and PARMSTRUCT. ("FSNAME" means "File Stream Name." I think you can figure out "PARMS"!) Of course, the other thing we need to pass to GetParms() is the name of the configuration file. So that gives us the declaration:

bool GetParms(const char* fs_name_cfg,
              FSNAMESTRUCT& fs_name,
              PARMSTRUCT& parms);

The very first thing we do in GetParms() is to create a CfgReader instance. The CfgReader constructor is given the name of the Windows INI-style configuration file.

Now, it is true that you could just write

CfgReader CR(fs_name_cfg);
but in the case where the filename that the constructor is given is invalid, I wrote the constructor so that it will throw an exception. Thus, to catch this exception, you need to make a CfgReader pointer, then make a new instance in a try block and catch any potential exceptions in a catch block, and that is what I do here in the dedupe program.

The way the CfgReader works is that it reads the entire configuration file into memory (stripping out all irrelevant whitespace, including comments that might be in the configuration file), then subsequent functions simply look through this "in-memory" data to pull out the information that you ask it to get.

You'll see in the GetParms() function where I first get the filenames I'm interested in from the information in the configuration file, and then I proceed to get other parameter information such as "reclen_in" (length of records in input file), "countkey" (the user-specified fields that make up the key, which consists of a pattern of numbers delimited by commas which correspond to record position and length values). Several validation checks are performed to make sure that the user-enter information is okay. Everything from making sure that a parameter argument that is supposed to be a number is indeed numeric to verifying that none of the fields specified by the user extend beyond the record length that the user has specified.

Some final comments: The "dedupe" program includes some an overloaded "ValidDigit" routine. Here are the declarations for the routine:

bool ValidDigits(const char* buf, const unsigned int len_buf);
bool ValidDigits(const char* cstr);
bool ValidDigits(const string& str);
I simply verifies that all of the characters passed to it are one of the digits 0 to 9. Anything else gives a return value of false. (This routine if for positive integers, no decimal or sign is allowed.) You'll see that in my code I use the "fgets()" function, which assumes LF-delimited records, and I have gotten into the habit of always opening my input data files in binary mode, regardless of whether they are "text files" or "binary files". Why? Well, that's another discussion. Just note here that I account for doing it this way by taking care of the possible existence of a CR [hex 0D] at the end of the input buffer just before the LF [hex 0A].) For Windows 9x, NT, or 2000, this program is a console application run from the command window. For you Unix guys, well, you'll do fine.

A C++ programmer,
Todd S. Greene
<tgreene@usxchange.net>
(Oct. 17, 2000)


//==============================================================================
// program: dedupe
// datetime: 10/17/00
// Copyright 2000 Todd S. Greene
// https://members.tripod.com/toddsgreene/
//
// purpose: Dedupe records.
//------------------------------------------------------------------------------
// This code is hereby released to the public domain.
//
// Your actual use of this code is your responsiblity, not mine. (In other
// words, if you choose to use this code, then you are liable for results,
// not me. No one has the right to sue me for anything, based merely on using
// some information that I have provided on a purely non-commercial basis.)
//==============================================================================

#pragma comment(exestr,"Copyright ©2000 Todd S. Greene. Released to Public Domain. (Author: Todd S. Greene)")

#include "cfgstl.h"
#include "ValidDigits.cpp"

#include <cstdlib>
#include <iostream>
#include <fstream>
#include <ctime>
#include <string>
#include <deque>
#include <map>
using namespace std;

#include <sys\stat.h>  // for file info stat() function (get file stats)


//==============================================================================
// STRUCTURES:
//------------------------------------------------------------------------------
struct FSNAMESTRUCT
   {
   char* log;
   char* in;
   char* out;
   };

struct PARMSTRUCT
   {
   unsigned int reclen_in;
   deque<unsigned short> fld_pos;
   deque<unsigned short> fld_len;
   };

struct RecInfo
   {
   long position;
   char dupeflag;
   unsigned long groupcount;
   };

typedef map<string, RecInfo, less<string> > Index;
//==============================================================================


//==============================================================================
// FUNCTIONS:
//------------------------------------------------------------------------------
void ShowCommandline();
bool GetParms(const char*, FSNAMESTRUCT&, PARMSTRUCT&);
//==============================================================================


//##############################################################################
//## START OF MAIN #############################################################
//##############################################################################

int main(int argc, char** argv)
   {
   if (argc != 2)
      {
      ShowCommandline();
      return EXIT_FAILURE;
      }

   FSNAMESTRUCT fs_name;
   int sz_fs_names = 260;
   fs_name.log = new char[sz_fs_names];  *(fs_name.log) = '\0';
   fs_name.in = new char[sz_fs_names];   *(fs_name.in) = '\0';
   fs_name.out = new char[sz_fs_names];  *(fs_name.out) = '\0';

   PARMSTRUCT parms;

   // get parameters from configuration file:
   if (!GetParms(argv[1], fs_name, parms))
      { return EXIT_FAILURE; }

   //===========================================================================
   // open log file:
   ofstream fs_log;
   fs_log.open(fs_name.log);
   if (!fs_log)
      {
      cerr << "\nfatal error: could not open log file \"" << fs_name.log << "\"!" << endl;
      return EXIT_FAILURE;
      }

   // open input file:
   FILE* fs_in;
   fs_in = fopen(fs_name.in, "rb");  // read only, binary
   if (!fs_in)
      {
      cerr << "\nfatal error: could not open input file \"" << fs_name.in << "\"!" << endl;
      return EXIT_FAILURE;
      }

   // open output file:
   FILE* fs_out;
   fs_out = fopen(fs_name.out, "wb");  // write only, binary
   if (!fs_out)
      {
      cerr << "\nfatal error: could not open output file \"" << fs_name.out << "\"!" << endl;
      return EXIT_FAILURE;
      }
   //===========================================================================

   //===========================================================================
   // setup variables for file reading loop:
   //---------------------------------------------------------------------------
   long filepos = 0;
   long last_filepos = 0;

   const unsigned int sz_buf = 4096;
   char* buf;
   try  // test for exceptions when allocating memory
      { buf = new char[sz_buf+8]; }
   catch (...)
      {
      cerr << "\nfatal error: memory allocation failure for file input buffer!" << endl;
      return EXIT_FAILURE;
      }
   unsigned int len_buf;

   unsigned long count_recs_in = 0;
   unsigned long count_recs_out = 0;

   const unsigned long reclen_fixed = parms.reclen_in;

   string key;
   RecInfo recinfo;
   Index index;
   Index::iterator it_index;

   // get file information:
   struct stat statbuf;
   stat(fs_name.in, &statbuf);
   const unsigned long filesize = statbuf.st_size;
   //===========================================================================

   cout << "\n   Creating index...   ";
   cout << " 0%" << flush;

   //===========================================================================
   // primary input loop starts here:
   //--------------------------------------------------------------------------
   while (fgets(buf, sz_buf, fs_in))
      {
      static unsigned short count_for_status = 0;
      if (count_for_status < 4000) { count_for_status++; }
      else
         {
         count_for_status = 0;
         float progress = ftell(fs_in);
         progress *= 100;
         progress /= filesize;
         unsigned short progress_int = progress;
         if (progress_int > 9)
            { cout << "\x08\x08\x08" << progress_int << "%" << flush; }
         else
            { cout << "\x08\x08" << progress_int << "%" << flush; }
         }

      count_recs_in++;
      len_buf = ftell(fs_in);
      len_buf -= filepos;
      filepos += len_buf;
      //filepos = ftell(fs_in); // commented out, since using "filepos += len_buf"
      if (!feof(fs_in))
         { len_buf--; }  // since line delimiter is dropped
      if (len_buf > 0)
         {
         if (*(buf+len_buf-1) == '\x0D')  // since using binary read
            { len_buf--; }
         }

      if (len_buf > reclen_fixed)
         {
         //fs_out.close();
         fclose(fs_out);
         remove(fs_name.out);
         cerr << "\nfatal error: record length must be " << reclen_fixed << "!" << endl;
         cerr << "      (rec #" << count_recs_in << ")" << endl;
         return EXIT_FAILURE;
         }

      // determine key value, and store position:
      key.assign(buf+parms.fld_pos.at(0), parms.fld_len.at(0));
      unsigned int i = 1;
      while (i < parms.fld_pos.size())
         {
         key.append(buf+parms.fld_pos.at(i), parms.fld_len.at(i));
         i++;
         }

      it_index = index.find(key);
      if (it_index == index.end())
         {
         recinfo.position = last_filepos;
         recinfo.dupeflag = ' ';
         recinfo.groupcount = 1;
         index.insert(Index::value_type(key, recinfo));
         //index[key] = last_filepos;
         }
      else
         {
         (*it_index).second.dupeflag = 'M';
         (*it_index).second.groupcount++;
         }

      // now store current file position;
      last_filepos = ftell(fs_in);
      }  // *** END OF PRIMARY INPUT LOOP ***
   //===========================================================================

   cout << "\x08\x08\x08\x08" << "100%\n";
   cout << "   ...finished creating index" << endl;

   //------------------------
   // reset input file:
   fseek(fs_in, 0, SEEK_SET);
   filepos = 0;
   //------------------------


   //--------------------------------------------------
   // set processing start time:
   time_t timv = time(0);
   char time_start[49];
   strcpy(time_start, ctime(&timv));
   if (strlen(time_start) > 0)
      {
      if (*(time_start+strlen(time_start)-1) == '\n')
         { *(time_start+strlen(time_start)-1) = '\0'; }
      }
   //--------------------------------------------------

   unsigned long count_uniques = 0;

   cout << "\n   Processing data...   ";
   cout << " 0%" << flush;

   //===========================================================================
   // primary output loop starts here:
   //--------------------------------------------------------------------------
   for (it_index = index.begin(); it_index != index.end(); it_index++)
      {
      static unsigned short count_for_status = 0;
      if (count_for_status < 4000) { count_for_status++; }
      else
         {
         count_for_status = 0;
         float progress = ftell(fs_in);
         progress *= 100;
         progress /= filesize;
         unsigned short progress_int = progress;
         if (progress_int > 9)
            { cout << "\x08\x08\x08" << progress_int << "%" << flush; }
         else
            { cout << "\x08\x08" << progress_int << "%" << flush; }
         }

      if (fseek(fs_in, (*it_index).second.position, SEEK_SET))
         {
         fclose(fs_out);
         remove(fs_name.out);
         cerr << "\nfatal error: fseek() failure using index on input file!" << endl;
         cerr << "      (file position: " << (*it_index).second.position << ")" << endl;
         return EXIT_FAILURE;
         }
      if (!fgets(buf, sz_buf, fs_in))
         {
         fclose(fs_out);
         remove(fs_name.out);
         cerr << "\nfatal error: read failure using index on input file!" << endl;
         cerr << "      (file position: " << (*it_index).second.position << ")" << endl;
         return EXIT_FAILURE;
         }

      len_buf = ftell(fs_in);
      len_buf -= (*it_index).second.position;
      if (!feof(fs_in))
         { len_buf--; }  // since line delimiter is dropped
      if (len_buf > 0)
         {
         if (*(buf+len_buf-1) == '\x0D')  // since using binary read
            { len_buf--; }
         }

      if (len_buf > reclen_fixed)
         {
         fclose(fs_out);
         remove(fs_name.out);
         cerr << "\nfatal error: record length must be " << reclen_fixed << "!" << endl;
         cerr << "      (file position: " << (*it_index).second.position << ")" << endl;
         return EXIT_FAILURE;
         }

      if ((*it_index).second.groupcount == 1)
         { count_uniques++; }

      *(buf+len_buf) = (*it_index).second.dupeflag;
      len_buf++;
      memmove(buf+len_buf, "\x0D\x0A", 2);
      len_buf += 2;
      fwrite(buf, 1, len_buf, fs_out);
      count_recs_out++;
      }  // *** END OF PRIMARY INPUT LOOP ***
   //===========================================================================

   //fwrite("\x0D\x0A", 1, 2, fs_out);
   delete[] buf;
   fclose(fs_in);
   fclose(fs_out);

   cout << "\x08\x08\x08\x08" << "100%\n";
   cout << "   ...finished processing data" << endl;

   //--------------------------------------------------
   // set processing stop time:
   timv = time(0);
   char time_stop[49];
   strcpy(time_stop, ctime(&timv));
   if (strlen(time_stop) > 0)
      {
      if (*(time_stop+strlen(time_stop)-1) == '\n')
         { *(time_stop+strlen(time_stop)-1) = '\0'; }
      }
   //--------------------------------------------------

   cout << "\n   (Check results in \"" << fs_name.log << "\")" << endl;

   cout << "\n     input file: " << fs_name.in << endl;
   cout << "        recs in: " << count_recs_in << endl;
   cout << "    output file: " << fs_name.out << endl;
   cout << "       recs out: " << count_recs_out << endl;
   cout << "\n        uniques: " << count_uniques << endl;
   cout << "    non-uniques: " << (count_recs_out - count_uniques) << endl;

   cout << "\n        process start time: " << time_start << endl;
   cout << "       process finish time: " << time_stop << endl;

   fs_log << "\n     input file: " << fs_name.in << endl;
   fs_log << "        recs in: " << count_recs_in << endl;
   fs_log << "    output file: " << fs_name.out << endl;
   fs_log << "       recs out: " << count_recs_out << endl;
   fs_log << "\n        uniques: " << count_uniques << endl;
   fs_log << "    non-uniques: " << (count_recs_out - count_uniques) << endl;

   fs_log << "\n        process start time: " << time_start << endl;
   fs_log << "       process finish time: " << time_stop << endl;

   return EXIT_SUCCESS;
   }  // *** END OF MAIN ***

//##############################################################################
//## END OF MAIN ###############################################################
//##############################################################################


//==============================================================================
bool GetParms(const char* fs_name_cfg,
              FSNAMESTRUCT& fs_name,
              PARMSTRUCT& parms)
   {
   // get parameters from configuration file:
   CfgReader* CR;
   try
      { CR = new CfgReader(fs_name_cfg); }
   catch(int err)
      {
      if (err == -1)
         {
         cerr << "file problem" << endl;
         return false;
         }
      else
         {
         cerr << "undefined problem" << endl;
         return false;
         }
      }
   catch(...)
      {
      cerr << "undefined problem" << endl;
      return false;
      }

   bool parm_not_found = false;
   bool parm_not_valid = false;
   cout << "\n";

   if (!CR->GetParmValue(fs_name.log, "FILES", "file_log"))
      {
      parm_not_found = true;
      cerr << "*** fatal error: \"file_log\" parameter not found in configuration file!" << endl;
      }

   if (!CR->GetParmValue(fs_name.in, "FILES", "file_in"))
      {
      parm_not_found = true;
      cerr << "*** fatal error: \"file_in\" parameter not found in configuration file!" << endl;
      }

   if (!CR->GetParmValue(fs_name.out, "FILES", "file_out"))
      {
      parm_not_found = true;
      cerr << "*** fatal error: \"file_out\" parameter not found in configuration file!" << endl;
      }

   // verify no duplicate file names:
   if ((!stricmp(fs_name_cfg, fs_name.log))
      || (!stricmp(fs_name_cfg, fs_name.in))
      || (!stricmp(fs_name_cfg, fs_name.out))
      || (!stricmp(fs_name.log, fs_name.in))
      || (!stricmp(fs_name.log, fs_name.out))
      || (!stricmp(fs_name.in, fs_name.out)))
      {
      parm_not_valid = true;
      cerr << "*** fatal error: duplicate file names in configuration file!" << endl;
      }

   //------------------------------------------------

   string str_temp;
   char str_parm[64];
//   unsigned int ui_val;
   bool this_parm_is_valid;

   //------------------------------------------------

   // establish "reclen_in":
   str_temp.assign("");
   strcpy(str_parm, "reclen_in");
   this_parm_is_valid = true;

   if (CR->GetParmValue(str_temp, "PROCESS", str_parm))
      {
      // validate numeric:
      if (ValidDigits(str_temp))
         {
         if (str_temp.size() > 4)
            {
            parm_not_valid = true;
            this_parm_is_valid = false;
            cerr << "*** fatal error: \"" << str_parm << "\" parameter is too large (> 4 chars)!" << endl;
            }
         }
      else
         {
         parm_not_valid = true;
         this_parm_is_valid = false;
         cerr << "*** fatal error: \"" << str_parm << "\" parameter is non-numeric!" << endl;
         }

      if (this_parm_is_valid)
         {
         parms.reclen_in = atoi(str_temp.c_str());
         if ((parms.reclen_in < 1) || (parms.reclen_in > 4096))
            {
            parm_not_valid = true;
            cerr << "*** fatal error: \"" << str_parm << "\" value is not valid (range: 1 to 4096)!" << endl;
            }
         }
      }
   else
      {
      parm_not_found = true;
      cerr << "*** fatal error: \"" << str_parm << "\" parameter not found in configuration file!" << endl;
      }

   //------------------------------------------------

   // establish "countkey":
   str_temp.assign("");
   strcpy(str_parm, "countkey");
   this_parm_is_valid = true;

   if (CR->GetParmValue(str_temp, "PROCESS", str_parm))
      {
      //GetParmArgs(vParmItems, str_countkey, ',');
      const unsigned short max_flds = 10;
      const unsigned short max_len_fldtotal = 50;
      unsigned short len_fldtotal = 0;

      vector<string> vArgs;
      CR->GetParmArgs(vArgs, str_temp, ',');

      // get parameter values from string, then delete string
      while (true)
         {
         unsigned short i;
         // validate number of parameter values:
         if ((vArgs.size() == 0) || ((vArgs.size() % 2) != 0))
            {
            parm_not_valid = true;
            cerr << "\nfatal error: \"" << str_parm << "\" parameter is missing elements!" << endl;
            break;
            }

         if (vArgs.size() > (2 * max_flds))
            {
            parm_not_valid = true;
            cerr << "\nfatal error: \"" << str_parm << "\" parameter has too elements!" << endl;
            break;
            }

         // store the different pieces of the "countkey" parameter:
         for (i = 0; i < vArgs.size(); i++)
            {
            // validate parameter value (field position):
            if (vArgs.at(i).size() == 0)
               {
               parm_not_valid = true;
               cerr << "\nfatal error: \"" << str_parm << "\" field position parameter is blank!" << endl;
               break;
               }
            if (vArgs.at(i).size() > 4)
               {
               parm_not_valid = true;
               cerr << "\nfatal error: \"" << str_parm << "\" field position parameter is too large (> 4 chars)!" << endl;
               break;
               }
            if (!ValidDigits(vArgs.at(i).c_str(), vArgs.at(i).size()))
               {
               parm_not_valid = true;
               cerr << "\nfatal error: \"" << str_parm << "\" field position parameter is not numeric!" << endl;
               break;
               }

            i++;

            // validate parameter value (field length):
            if (vArgs.at(i).size() == 0)
               {
               parm_not_valid = true;
               cerr << "\nfatal error: \"" << str_parm << "\" field length parameter is blank!" << endl;
               break;
               }
            if (vArgs.at(i).size() > 2)
               {
               parm_not_valid = true;
               cerr << "\nfatal error: \"" << str_parm << "\" field length parameter is too large (> 2 chars)!" << endl;
               break;
               }
            if (!ValidDigits(vArgs.at(i).c_str(), vArgs.at(i).size()))
               {
               parm_not_valid = true;
               cerr << "\nfatal error: \"" << str_parm << "\" field length parameter is not numeric!" << endl;
               break;
               }

            // store this parameter value set::
            parms.fld_pos.push_back(atoi(vArgs.at(i-1).c_str()) - 1);
            parms.fld_len.push_back(atoi(vArgs.at(i).c_str()));

            // validate defined field with maximum record length:
            if ((parms.fld_pos.back() + parms.fld_len.back()) > parms.reclen_in)
               {
               parm_not_valid = true;
               cerr << "\nfatal error: \"" << str_parm << "\" field exceeds specified maximum record length!" << endl;
               break;
               }

            // add to total length of "generated field":
            len_fldtotal += parms.fld_len.back();

            // validate total length of "generated field":
            if (len_fldtotal > max_len_fldtotal)
               {
               parm_not_valid = true;
               cerr << "\nfatal error: total length of defined \"" << str_parm << "\" field exceeds " << max_len_fldtotal << " characters!" << endl;
               break;
               }
            }

         break;
         }
//for (unsigned short i = 0; i < parms.fld_pos.size(); i++)
//cout << "fld_pos.at(" << i << ") = " << parms.fld_pos.at(i) << "   fld_len.at(" << i << ") = " << parms.fld_len.at(i) << endl;
      }
   else
      {
      parm_not_found = true;
      cerr << "*** fatal error: \"" << str_parm << "\" parameter not found in configuration file!" << endl;
      }

   //------------------------------------------------

   if (parm_not_found || parm_not_valid)
      { return false; }

   return true;
   }  // end of GetParms() routine
//==============================================================================


//==============================================================================
void ShowCommandline()
   {
   cout <<
      "\n"
      "DEDUPE   Version 1.0.0   09-25-2000   (Author: Todd S. Greene)\n"
      "Copyright ©2000 Presort Services, Inc. All Rights Reserved.\n\n"
      "   Dedupe records.\n\n"
      "   Usage: dedupe {configuration path\\filename}\n\n"
      "      Configuration file requires the following:\n"
      "[FILE NAMES]\n"
      "file_log={log file}\n"
      "file_in={input file}\n"
      "file_out={output file}\n\n"
      "[PROCESS]\n"
      "min_lines={minimum allowed fields per record}  (default: 3; range: 3-6)\n"
      "max_lines={maximum allowed fields per record}  (default: 8; range: 3-12)\n\n"
      "   \"max_lines\" value must be >= to \"min_lines\" value"
      << endl;
   }  // end of ShowCommandline() routine
//==============================================================================



//##############################################################################
// a separate file, to be "included" in the "dedupe" code
//##############################################################################

//==============================================================================
// function: ValidDigits
// datetime: 9/20/00 5:10:08 pm EST
// Copyright 2000 Todd S. Greene
// https://members.tripod.com/toddsgreene/
//
// This code is hereby released to the public domain.
//
// Your actual use of this code is your responsiblity, not mine. (In other
// words, if you choose to use this code, then you are liable for results,
// not me. No one has the right to sue me for anything, based merely on using
// some information that I have provided on a purely non-commercial basis.)
//==============================================================================

#ifndef _ValidDigits_cpp_
#define _ValidDigits_cpp_

#include <string>
using namespace std;

bool ValidDigits(const char* buf, const unsigned int len_buf)
   {
   // verify digits only:
   if (len_buf < 1)
      { return false; }
   const char* ptr_buf = buf;
   for (unsigned int i = 0; i < len_buf; i++)
      {
      if ((*(ptr_buf) < '0') || (*(ptr_buf) > '9'))
         { return false; }
      ptr_buf++;
      }
   return true;
   }

bool ValidDigits(const char* cstr)
   {
   // verify digits only:
   if (!*(cstr))
      { return false; }
   for (const char* ptr_cstr = cstr; *(ptr_cstr); ptr_cstr++)
      {
      if ((*(ptr_cstr) < '0') || (*(ptr_cstr) > '9'))
         { return false; }
      }
   return true;
   }

bool ValidDigits(const string& str)
   {
   // verify digits only:
   if (str.size() < 1)
      { return false; }
   for (unsigned int i = 0; i < str.size(); i++)
      {
      if ((str.at(i) < '0') || (str.at(i) > '9'))
         { return false; }
      }
   return true;
   }

#endif  // _ValidDigits_cpp_
//==============================================================================

 PREVIOUS PAGE   C++ PAGE 
 HOME   CONTACT ME 
This page: Created 10/17/00. Last updated 10/17/00.
Copyright © 2000  Todd S. Greene.