stevemarple / IniFile

Arduino library to parse ini files.
GNU Lesser General Public License v2.1
87 stars 45 forks source link

ESP8266 compatiblity. #7

Closed miky2k closed 3 years ago

miky2k commented 8 years ago

I added minor changes to make library compatible with ESP8266. strcasecmp(cp, section) not exist in ESP8266 core.

see lines with #ifdef ESP8266 or //TODO:

#include <IniFile.h>
#include <string.h>
#ifdef ESP8266
#include <stdint.h>

#endif

const uint8_t IniFile::maxFilenameLen = INI_FILE_MAX_FILENAME_LEN;

IniFile::IniFile(const char* filename, uint8_t mode,bool caseSensitive)
{
  if (strlen(filename) <= maxFilenameLen)
    strcpy(_filename, filename);
  else
    _filename[0] = '\0';
  _mode = mode;
  _caseSensitive = caseSensitive;
}

IniFile::~IniFile()
{
  //if (_file)
  //  _file.close();
}

bool IniFile::validate(char* buffer, size_t len) const
{
  uint32_t pos = 0;
  error_t err;
  while ((err = readLine(_file, buffer, len, pos)) == errorNoError)
    ;
  if (err == errorEndOfFile) {
    _error = errorNoError;
    return true;
  }
  else {
    _error = err;
    return false;
  }
}

bool IniFile::getValue(const char* section, const char* key,
              char* buffer, size_t len, IniFileState &state) const
{
  bool done = false;
  if (!_file) {
    _error = errorFileNotOpen;
    return true;
  }

  switch (state.getValueState) {
  case IniFileState::funcUnset:
    state.getValueState = (section == NULL ? IniFileState::funcFindKey
               : IniFileState::funcFindSection);
    state.readLinePosition = 0;
    break;

  case IniFileState::funcFindSection:
    if (findSection(section, buffer, len, state)) {
      if (_error != errorNoError)
    return true;
      state.getValueState = IniFileState::funcFindKey;
    }
    break;

  case IniFileState::funcFindKey:
    char *cp;
    if (findKey(section, key, buffer, len, &cp, state)) {
      if (_error != errorNoError)
    return true;
      // Found key line in correct section
      cp = skipWhiteSpace(cp);
      removeTrailingWhiteSpace(cp);

      // Copy from cp to buffer, but the strings overlap so strcpy is out
      while (*cp != '\0')
    *buffer++ = *cp++;
      *buffer = '\0';
      return true;
    }
    break;

  default:
    // How did this happen?
    _error = errorUnknownError;
    done = true;
    break;
  }

  return done;
}

bool IniFile::getValue(const char* section, const char* key,
              char* buffer, size_t len) const
{
  IniFileState state;
  while (!getValue(section, key, buffer, len, state))
    ;
  return _error == errorNoError;
}

bool IniFile::getValue(const char* section, const char* key,
             char* buffer, size_t len, char *value, size_t vlen) const
{
  if (getValue(section, key, buffer, len) < 0)
    return false; // error
  if (strlen(buffer) >= vlen)
    return false;
  strcpy(value, buffer);
  return true;
}

// For true accept: true, yes, 1
 // For false accept: false, no, 0
bool IniFile::getValue(const char* section, const char* key, 
              char* buffer, size_t len, bool& val) const
{
  if (getValue(section, key, buffer, len) < 0)
    return false; // error

  if (strcmp(buffer, "true") == 0 || //TODO:strcasecom ?
      strcmp(buffer, "yes") == 0 ||
      strcmp(buffer, "1") == 0) {
    val = true;
    return true;
  }
  if (strcmp(buffer, "false") == 0 || //TODO:strcasecom ?
      strcmp(buffer, "no") == 0 ||
      strcmp(buffer, "0") == 0) {
    val = false;
    return true;
  }
  return false; // does not match any known strings      
}

bool IniFile::getValue(const char* section, const char* key,
              char* buffer, size_t len, int& val) const
{
  if (getValue(section, key, buffer, len) < 0)
    return false; // error

  val = atoi(buffer);
  return true;
}

bool IniFile::getValue(const char* section, const char* key,    \
              char* buffer, size_t len, uint16_t& val) const
{
  long longval;
  bool r = getValue(section, key, buffer, len, longval);
  if (r)
    val = uint16_t(longval);
  return r;
}

bool IniFile::getValue(const char* section, const char* key,
              char* buffer, size_t len, long& val) const
{
  if (getValue(section, key, buffer, len) < 0)
    return false; // error

  val = atol(buffer);
  return true;
}

bool IniFile::getValue(const char* section, const char* key,
              char* buffer, size_t len, unsigned long& val) const
{
  if (getValue(section, key, buffer, len) < 0)
    return false; // error

  char *endptr;
  unsigned long tmp = strtoul(buffer, &endptr, 10);
  if (endptr == buffer)
    return false; // no conversion
  if (*endptr == '\0') {
    val = tmp;
    return true; // valid conversion
  }
  // buffer has trailing non-numeric characters, and since the buffer
  // already had whitespace removed discard the entire results
  return false; 
}

bool IniFile::getValue(const char* section, const char* key,
              char* buffer, size_t len, float & val) const
{
  if (getValue(section, key, buffer, len) < 0)
    return false; // error

  char *endptr;
  float tmp = strtod(buffer, &endptr);
  if (endptr == buffer)
    return false; // no conversion
  if (*endptr == '\0') {
    val = tmp;
    return true; // valid conversion
  }
  // buffer has trailing non-numeric characters, and since the buffer
  // already had whitespace removed discard the entire results
  return false; 
}

bool IniFile::getIPAddress(const char* section, const char* key,
                  char* buffer, size_t len, uint8_t* ip) const
{
  // Need 16 chars minimum: 4 * 3 digits, 3 dots and a null character
  if (len < 16)
    return false;

  if (getValue(section, key, buffer, len) < 0) 
    return false; // error

  int i = 0;
  char* cp = buffer;
  ip[0] = ip[1] = ip[2] = ip[3] = 0;
  while (*cp != '\0' && i < 4) {
    if (*cp == '.') {
      ++i;
      ++cp;
      continue;
    }
    if (isdigit(*cp)) {
      ip[i] *= 10;
      ip[i] += (*cp - '0');
    }
    else {
      ip[0] = ip[1] = ip[2] = ip[3] = 0;
      return false;
    }
    ++cp;
  }
  return true;
}

#if defined(ARDUINO) && ARDUINO >= 100
bool IniFile::getIPAddress(const char* section, const char* key,
                  char* buffer, size_t len, IPAddress& ip) const
{
  // Need 16 chars minimum: 4 * 3 digits, 3 dots and a null character
  if (len < 16)
    return false;

  if (getValue(section, key, buffer, len) < 0) 
    return false; // error

  int i = 0;
  char* cp = buffer;
  ip = IPAddress(0, 0, 0, 0);
  while (*cp != '\0' && i < 4) {
    if (*cp == '.') {
      ++i;
      ++cp;
      continue;
    }
    if (isdigit(*cp)) {
      ip[i] *= 10;
      ip[i] += (*cp - '0');
    }
    else {
      ip = IPAddress(0, 0, 0, 0);
      return false;
    }
    ++cp;
  }
  return true;
}
#endif

bool IniFile::getMACAddress(const char* section, const char* key,
                   char* buffer, size_t len, uint8_t mac[6]) const
{
  // Need 18 chars: 6 * 2 hex digits, 5 : or - and a null char
  if (len < 18)
    return false;

  if (getValue(section, key, buffer, len) < 0)
    return false; // error

  int i = 0;
  char* cp = buffer;
  memset(mac, 0, 6);

  while (*cp != '\0' && i < 6) {
    if (*cp == ':' || *cp == '-') {
      ++i;
      ++cp;
      continue;
    }
    if (isdigit(*cp)) {
      mac[i] *= 16; // working in hex!
      mac[i] += (*cp - '0');
    }
    else {
      if (isxdigit(*cp)) {
    mac[i] *= 16; // working in hex!
    mac[i] += (toupper(*cp) - 55); // convert A to 0xA, F to 0xF
      }
      else {
    memset(mac, 0, sizeof(mac));
    return false;
      }
    }
    ++cp;
  }
  return true;
}

//int8_t IniFile::readLine(File &file, char *buffer, size_t len, uint32_t &pos)
IniFile::error_t IniFile::readLine(File &file, char *buffer, size_t len, uint32_t &pos)
{
  if (!file)
    return errorFileNotOpen;

  if (len < 3) 
    return errorBufferTooSmall;

#ifndef ESP8266
  if (!file.seek(pos))
     return errorSeekError;
  size_t bytesRead = file.read(buffer, len);
#endif
#ifdef ESP8266
  if (!file.seek(pos,SeekSet))
       return errorSeekError;
  size_t bytesRead = file.read((uint8_t *)buffer, len);
#endif

  if (!bytesRead) {
    buffer[0] = '\0';
    //return 1; // done
    return errorEndOfFile;
  }

  for (size_t i = 0; i < bytesRead && i < len-1; ++i) {
    // Test for '\n' with optional '\r' too
    // if (endOfLineTest(buffer, len, i, '\n', '\r')

    if (buffer[i] == '\n' || buffer[i] == '\r') {
      char match = buffer[i];
      char otherNewline = (match == '\n' ? '\r' : '\n'); 
      // end of line, discard any trailing character of the other sort
      // of newline
      buffer[i] = '\0';

      if (buffer[i+1] == otherNewline)
    ++i;
      pos += (i + 1); // skip past newline(s)
      //return (i+1 == bytesRead && !file.available());
      return errorNoError;
    }
  }
  if (!file.available()) {
    // end of file without a newline
    buffer[bytesRead] = '\0';
    // return 1; //done
    return errorEndOfFile;
  }

  buffer[len-1] = '\0'; // terminate the string
  return errorBufferTooSmall;
}

bool IniFile::isCommentChar(char c)
{
  return (c == ';' || c == '#');
}

char* IniFile::skipWhiteSpace(char* str)
{
  char *cp = str;
  while (isspace(*cp))
    ++cp;
  return cp;
}

void IniFile::removeTrailingWhiteSpace(char* str)
{
  char *cp = str + strlen(str) - 1;
  while (cp >= str && isspace(*cp))
    *cp-- = '\0';
}

bool IniFile::findSection(const char* section, char* buffer, size_t len, 
                 IniFileState &state) const
{
  if (section == NULL) {
    _error = errorSectionNotFound;
    return true;
  }

  error_t err = IniFile::readLine(_file, buffer, len, state.readLinePosition);

  if (err != errorNoError && err != errorEndOfFile) {
    // Signal to caller to stop looking and any error value
    _error = err;
    return true;
  }

  char *cp = skipWhiteSpace(buffer);
  //if (isCommentChar(*cp))
  //return (done ? errorSectionNotFound : 0);
  if (isCommentChar(*cp)) {
    // return (err == errorEndOfFile ? errorSectionNotFound : errorNoError);
    if (err == errorSectionNotFound) {
      _error = err;
      return true;
    }
    else
      return false; // Continue searching
  }

  if (*cp == '[') {
    // Start of section
    ++cp;
    cp = skipWhiteSpace(cp);
    char *ep = strchr(cp, ']');
    if (ep != NULL) {
      *ep = '\0'; // make ] be end of string
      removeTrailingWhiteSpace(cp);
      if (_caseSensitive) {
    if (strcmp(cp, section) == 0) {
      _error = errorNoError;
      return true;
    }
      }
      else {
    if (strcmp(cp, section) == 0) { //TODO:strcmp
      _error = errorNoError;
      return true;
    }
      }
    }
  }

  // Not a valid section line
  //return (done ? errorSectionNotFound : 0);
  if (err == errorEndOfFile) {
    _error = errorSectionNotFound;
    return true;
  }

  return false;
}

// From the current file location look for the matching key. If
// section is non-NULL don't look in the next section
bool IniFile::findKey(const char* section, const char* key,
             char* buffer, size_t len, char** keyptr,
             IniFileState &state) const
{
  if (key == NULL || *key == '\0') {
    _error = errorKeyNotFound;
    return true;
  }

  error_t err = IniFile::readLine(_file, buffer, len, state.readLinePosition);
  if (err != errorNoError && err != errorEndOfFile) {
    _error = err;
    return true;
  }

  char *cp = skipWhiteSpace(buffer);
  // if (isCommentChar(*cp))
  //   return (done ? errorKeyNotFound : 0);
  if (isCommentChar(*cp)) {
    if (err == errorEndOfFile) {
      _error = errorKeyNotFound;
      return true;
    }
    else
      return false; // Continue searching
  }

  if (section && *cp == '[') {
    // Start of a new section
    _error = errorKeyNotFound;
    return true;
  }

  // Find '='
  char *ep = strchr(cp, '=');
  if (ep != NULL) {
    *ep = '\0'; // make = be the end of string
    removeTrailingWhiteSpace(cp);
    if (_caseSensitive) {
      if (strcmp(cp, key) == 0) {
    *keyptr = ep + 1;
    _error = errorNoError;
    return true;
      }
    }
    else {
      if (strcmp(cp, key) == 0) { //TODO:strcasecmp
    *keyptr = ep + 1;
    _error = errorNoError;
    return true;
      }
    }
  }

  // Not the valid key line
  if (err == errorEndOfFile) {
    _error = errorKeyNotFound;
    return true;
  }
  return false;
}

bool IniFile::getCaseSensitive(void) const
{
  return _caseSensitive;
}

void IniFile::setCaseSensitive(bool cs)
{
  _caseSensitive = cs;
}

IniFileState::IniFileState()
{
  readLinePosition = 0;
  getValueState = funcUnset;
}
miky2k commented 8 years ago
#ifndef _INIFILE_H
#define _INIFILE_H
#ifdef ESP8266
#include <stdint.h>
#include <stdlib.h>
#include <IPAddress.h>
#include <FS.h>
#define FILE_READ 0
#endif
// Maximum length for filename, excluding NULL char 26 chars allows an
// 8.3 filename instead and 8.3 directory with a leading slash
#define INI_FILE_MAX_FILENAME_LEN 26

#ifndef ESP8266
#include "SD.h"
#include "Ethernet.h"
#endif

class IniFileState;

class IniFile {
public:
  enum error_t {
    errorNoError = 0,
    errorFileNotFound,
    errorFileNotOpen,
    errorBufferTooSmall,
    errorSeekError,
    errorSectionNotFound,
    errorKeyNotFound,
    errorEndOfFile,
    errorUnknownError,
  };

  static const uint8_t maxFilenameLen;

  // Create an IniFile object. It isn't opened until open() is called on it.
  IniFile(const char* filename, uint8_t mode = FILE_READ,bool caseSensitive = false);
  ~IniFile();

  inline bool open(void); // Returns true if open succeeded
  inline void close(void);

  inline bool isOpen(void) const;

  inline error_t getError(void) const;
  inline void clearError(void) const;
  // Get the file mode (FILE_READ/FILE_WRITE)
  inline uint8_t getMode(void) const;

  // Get the filename asscoiated with the ini file object
  inline const char* getFilename(void) const;

  bool validate(char* buffer, size_t len) const;

  // Get value from the file, but split into many short tasks. Return
  // value: false means continue, true means stop. Call getError() to
  // find out if any error
  bool getValue(const char* section, const char* key,char* buffer, size_t len, IniFileState &state) const;

  // Get value, as one big task. Return = true means value is present
  // in buffer
  bool getValue(const char* section, const char* key,
          char* buffer, size_t len) const; 

  // Get the value as a string, storing the result in a new buffer
  // (not the working buffer)
  bool getValue(const char* section, const char* key,
           char* buffer, size_t len, char *value, size_t vlen) const;

  // Get a boolean value
  bool getValue(const char* section, const char* key,
           char* buffer, size_t len, bool& b) const;

  // Get an integer value
  bool getValue(const char* section, const char* key,
           char* buffer, size_t len, int& val) const;

  // Get a uint16_t value
  bool getValue(const char* section, const char* key,
           char* buffer, size_t len, uint16_t& val) const;

  // Get a long value
  bool getValue(const char* section, const char* key,
           char* buffer, size_t len, long& val) const;

  bool getValue(const char* section, const char* key,
           char* buffer, size_t len, unsigned long& val) const;

  // Get a float value
  bool getValue(const char* section, const char* key,
           char* buffer, size_t len, float& val) const;

  bool getIPAddress(const char* section, const char* key,
               char* buffer, size_t len, uint8_t* ip) const;

#if defined(ARDUINO) && ARDUINO >= 100
  bool getIPAddress(const char* section, const char* key,char* buffer, size_t len, IPAddress& ip) const;
#endif

  bool getMACAddress(const char* section, const char* key,
            char* buffer, size_t len, uint8_t mac[6]) const;

  // Utility function to read a line from a file, make available to all
  //static int8_t readLine(File &file, char *buffer, size_t len, uint32_t &pos);
  static error_t readLine(File &file, char *buffer, size_t len, uint32_t &pos);
  static bool isCommentChar(char c);
  static char* skipWhiteSpace(char* str);
  static void removeTrailingWhiteSpace(char* str);

  bool getCaseSensitive(void) const;
  void setCaseSensitive(bool cs);

  protected:
  // True means stop looking, false means not yet found
  bool findSection(const char* section, char* buffer, size_t len,   
              IniFileState &state) const;
  bool findKey(const char* section, const char* key, char* buffer,
         size_t len, char** keyptr, IniFileState &state) const;

private:
  char _filename[INI_FILE_MAX_FILENAME_LEN];
  uint8_t _mode;
  mutable error_t _error;
  mutable File _file;
  bool _caseSensitive;
};

bool IniFile::open(void)
{
  if (_file)
    _file.close();
#ifndef ESP8266
  _file = SD.open(_filename, _mode);
#endif
#ifdef ESP8266
  _file = SPIFFS.open(_filename,"r");  //TODO:Only read?
#endif
  if (isOpen()) {
    _error = errorNoError;
    return true;
  }
  else {
    _error = errorFileNotFound;
    return false;
  }
}

void IniFile::close(void)
{
  if (_file)
    _file.close();
}

bool IniFile::isOpen(void) const
{
  return (_file == true);
}

IniFile::error_t IniFile::getError(void) const
{
  return _error;
}

void IniFile::clearError(void) const
{
  _error = errorNoError;
}

uint8_t IniFile::getMode(void) const
{
  return _mode;
}

const char* IniFile::getFilename(void) const
{
  return _filename;
}

class IniFileState {
public:
  IniFileState();

private:
  enum {funcUnset = 0,
    funcFindSection,
    funcFindKey,
  };

  uint32_t readLinePosition;
  uint8_t getValueState;

  friend class IniFile;
};

#endif
tniercke commented 5 years ago

That works liek a charm. Thanks for sharing.

Only thing missing are "setIniValue(...)" functions ;-D