/*
 * (c) 2005 Research Group Dr. Fuhrmann, University of Karlsruhe, Germany
 * All rights reserved.
 *
 * Author:
 *         Roman Krenicky
 *
 * $Id: kv_parsing.cpp,v 1.2 2005/11/21 09:39:01 kutzner Exp $
 *
 */

/*
  kv_parsing.cpp
  created: 13.5.2005
  last change: 14.5.2005
*/
/*! @file
    utility functions for simple parsing of key-value entries
*/

#include "globalheader.h"

#include "kv_parsing.h"
using namespace std;


/*! @brief parse 'key: value' entries in a buffer
    @param buf  the buffer to parse (see below)
    @param len  the size of @buf
    @param tail  if not NULL, will point into @a buf to the beginning of
                 pending data in the buffer or to NULL if no such data exists
                 (on success only)
    @return a dynamically allocated map of (key,value) pairs, possibly empty
            (don't forget to delete it!) or NULL on error

    the buffer has to consist of succeeding entry-lines, terminated with a
    newline character, optionally followed by an empty line (no spaces!) and
    arbitrary data (@a tail will point to that data) ["kvd" means Key-Value
    pairs and trailing Data]

    the trailing data has the length (@a len - (@c size_t)(@a tail - @a buf))

    the entry lines must not contain null characters, the trailing data may

    an entry line looks as follows: <key>,':',<value>,\n with optional spaces
    before and after the key and value, the first colon in each line is
    interpreted as the delimiter

    if there are multiple entries with the same key, only the last one will be
    returned
*/
map<string,string>* parse_kvd (const char *buf, size_t len, const char **tail)
{
   map<string,string> *retval = NULL;
   pair<string,string> next_entry;
   size_t pos = 0;  // read position in 'buf'
   size_t delimpos = 0;  // position of delimiter or end of line
   size_t endpos = 0;  // end of key or value (first position _after_ that)
   bool error_occurred = false;

   if (buf == NULL)
      return NULL;

   retval = new map<string,string>;
   if (tail != NULL)
      *tail = NULL;
   while (pos < len)
   {
      if (buf[pos] == '\n')  // empty line, key-value-part finished
      {
         if ( (tail != NULL) && (++pos < len) )
            *tail = &buf[pos];
         break;
      } // if (buf[pos] == '\n')  -> empty line, key-value-part finished
      // skip spaces:
      for (; (pos < len) && buf[pos] == ' '; pos++);
      // find delimiter:
      for ( delimpos=pos;
            (delimpos < len) && (buf[delimpos] != '\n') &&
            (buf[delimpos] != '\0') && (buf[delimpos] != ':');
            delimpos++ );
      if ( (delimpos >= len) || (delimpos == pos) || (buf[delimpos] != ':') )
         {  error_occurred = true;  break;  }
      // find end of key:
      endpos=delimpos-1;  //  delimpos > pos => delimpos-1 is no underflow
      for (; (endpos > pos) && (buf[endpos] == ' '); endpos--);
      endpos++;  // mind semantics of 'endpos'
      // extract key:
      if (pos == endpos)
         {  error_occurred = true;  break;  }
      next_entry.first.assign (&buf[pos], endpos-pos);
      // skip spaces:
      for (pos=delimpos+1; (pos < len) && buf[pos] == ' '; pos++);
      // find end of line:
      for ( delimpos=pos;
            (delimpos < len) && (buf[delimpos]!='\n') && (buf[delimpos]!='\0');
            delimpos++ );
      if ( (delimpos >= len) || (delimpos == pos) || (buf[delimpos] != '\n') )
         {  error_occurred = true;  break;  }
      // find end of value:
      endpos=delimpos-1;  //  delimpos > pos => delimpos-1 is no underflow
      for (; (endpos > pos) && (buf[endpos] == ' '); endpos--);
      endpos++;  // mind semantics of 'endpos'
      // extract value:
      if (pos == endpos)
         {  error_occurred = true;  break;  }
      next_entry.second.assign (&buf[pos], endpos-pos);
      // go to next line:      
      pos = delimpos+1;

      // enter the new entry:
      retval->erase (next_entry.first);  // if present (insert() would fail)
      retval->insert (next_entry);
   } // while (pos < len)
   if (error_occurred)
   {
      delete retval;
      retval = NULL;
   } // if (error_occurred)

   return retval;
} // parse_kvd (...)


/*! @brief finds the value belonging to a key in a key-value map
    @param entries  a map of key-value pairs where both keys and values must be
                    non-empty
    @param key  the key of the entry to search (non-empty)
    @return the value belonging to @a key or an empty string if the entry was
            not found
*/
string findvalue_kv (const map<string,string> &entries, const string &key)
{
   string retval;
   map<string,string>::const_iterator iter;

   if (key.empty())
      return retval;

   iter = entries.find (key);
   if (iter != entries.end())
      retval = iter->second;
   return retval;
} // findvalue_kv (...)


/*! @brief assembles key-value entries into a string
    @param entries  a map of key-value pairs where both keys and values must be
                    non-empty, must not begin or end with spaces, must not
                    contain newline characters and the keys must not contain a
                    colon (:) as it is used as delimiter
    @return the key-value pairs in a string

    the string will solely constist of entry lines of the following form:
    <keyN>,': ',<valueN>,\n

    @note if you want to create a kvd-buffer as expected by parse_kvd, don't
          forget to place an empty line between the returned data of this
          function and the trailing data
*/
string assemble_kv (const map<string,string> &entries)
{
   string retval;
   map<string,string>::const_iterator iter;

   for (iter=entries.begin(); iter != entries.end(); iter++)
      {  (((retval += iter->first) += ": ") += iter->second) += '\n';  }
   return retval;
} // assemble_kv (...)
