ini 파일 읽어들이기

|

ini 파일은 각종 설정값 등을 정의한 설정 파일로, de facto 표준입니다. 단순 텍스트 파일로 되어 있어서 수정이 편리합니다.


ini 파일 예제

ini 파일은 다음과 같은 형태로 되어 있습니다.

; Test ini file

[version]                   ; INI file version
version = 1.02
version_name = Hello, snowdeer

[device_config]             ; Device configuration
mic_enabled = false
speaker_enabled = false
camera_enabled = true
motion_enabled = true


다음 헤더 파일만 프로젝트에 첨부하면 됩니다.

INIReader.h


#ifndef __INI_H__
#define __INI_H__

#ifdef __cplusplus
extern "C" {
#endif

#include <stdio.h>

typedef int (*ini_handler)(void *user, const char *section,
                           const char *name, const char *value);

typedef char *(*ini_reader)(char *str, int num, void *stream);

int ini_parse(const char *filename, ini_handler handler, void *user);

int ini_parse_file(FILE *file, ini_handler handler, void *user);

int ini_parse_stream(ini_reader reader, void *stream, ini_handler handler,
                     void *user);

#ifndef INI_ALLOW_MULTILINE
#define INI_ALLOW_MULTILINE 1
#endif

/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of
   the file. See http://code.google.com/p/inih/issues/detail?id=21 */
#ifndef INI_ALLOW_BOM
#define INI_ALLOW_BOM 1
#endif

/* Nonzero to allow inline comments (with valid inline comment characters
   specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match
   Python 3.2+ configparser behaviour. */
#ifndef INI_ALLOW_INLINE_COMMENTS
#define INI_ALLOW_INLINE_COMMENTS 1
#endif
#ifndef INI_INLINE_COMMENT_PREFIXES
#define INI_INLINE_COMMENT_PREFIXES ";"
#endif

/* Nonzero to use stack, zero to use heap (malloc/free). */
#ifndef INI_USE_STACK
#define INI_USE_STACK 1
#endif

/* Stop parsing on first error (default is to keep parsing). */
#ifndef INI_STOP_ON_FIRST_ERROR
#define INI_STOP_ON_FIRST_ERROR 0
#endif

/* Maximum line length for any line in INI file. */
#ifndef INI_MAX_LINE
#define INI_MAX_LINE 200
#endif

#ifdef __cplusplus
}
#endif

/* inih -- simple .INI file parser

inih is released under the New BSD license (see LICENSE.txt). Go to the project
home page for more info:

https://github.com/benhoyt/inih

*/

#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS
#endif

#include <stdio.h>
#include <ctype.h>
#include <string.h>

#if !INI_USE_STACK
#include <stdlib.h>
#endif

#define MAX_SECTION 50
#define MAX_NAME 50

/* Strip whitespace chars off end of given string, in place. Return s. */
inline static char *rstrip(char *s) {
  char *p = s + strlen(s);
  while (p > s && isspace((unsigned char) (*--p)))
    *p = '\0';
  return s;
}

/* Return pointer to first non-whitespace char in given string. */
inline static char *lskip(const char *s) {
  while (*s && isspace((unsigned char) (*s)))
    s++;
  return (char *) s;
}

/* Return pointer to first char (of chars) or inline comment in given string,
   or pointer to null at end of string if neither found. Inline comment must
   be prefixed by a whitespace character to register as a comment. */
inline static char *find_chars_or_comment(const char *s, const char *chars) {
#if INI_ALLOW_INLINE_COMMENTS
  int was_space = 0;
  while (*s && (!chars || !strchr(chars, *s)) &&
      !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
    was_space = isspace((unsigned char) (*s));
    s++;
  }
#else
  while (*s && (!chars || !strchr(chars, *s))) {
        s++;
    }
#endif
  return (char *) s;
}

/* Version of strncpy that ensures dest (size bytes) is null-terminated. */
inline static char *strncpy0(char *dest, const char *src, size_t size) {
  strncpy(dest, src, size);
  dest[size - 1] = '\0';
  return dest;
}

/* See documentation in header file. */
inline int ini_parse_stream(ini_reader reader, void *stream, ini_handler handler,
                            void *user) {
  /* Uses a fair bit of stack (use heap instead if you need to) */
#if INI_USE_STACK
  char line[INI_MAX_LINE];
#else
  char* line;
#endif
  char section[MAX_SECTION] = "";
  char prev_name[MAX_NAME] = "";

  char *start;
  char *end;
  char *name;
  char *value;
  int lineno = 0;
  int error = 0;

#if !INI_USE_STACK
  line = (char*)malloc(INI_MAX_LINE);
    if (!line) {
        return -2;
    }
#endif

  /* Scan through stream line by line */
  while (reader(line, INI_MAX_LINE, stream) != NULL) {
    lineno++;

    start = line;
#if INI_ALLOW_BOM
    if (lineno == 1 && (unsigned char) start[0] == 0xEF &&
        (unsigned char) start[1] == 0xBB &&
        (unsigned char) start[2] == 0xBF) {
      start += 3;
    }
#endif
    start = lskip(rstrip(start));

    if (*start == ';' || *start == '#') {
      /* Per Python configparser, allow both ; and # comments at the
         start of a line */
    }
#if INI_ALLOW_MULTILINE
    else if (*prev_name && *start && start > line) {

#if INI_ALLOW_INLINE_COMMENTS
      end = find_chars_or_comment(start, NULL);
      if (*end)
        *end = '\0';
      rstrip(start);
#endif

      /* Non-blank line with leading whitespace, treat as continuation
         of previous name's value (as per Python configparser). */
      if (!handler(user, section, prev_name, start) && !error)
        error = lineno;
    }
#endif
    else if (*start == '[') {
      /* A "[section]" line */
      end = find_chars_or_comment(start + 1, "]");
      if (*end == ']') {
        *end = '\0';
        strncpy0(section, start + 1, sizeof(section));
        *prev_name = '\0';
      } else if (!error) {
        /* No ']' found on section line */
        error = lineno;
      }
    } else if (*start) {
      /* Not a comment, must be a name[=:]value pair */
      end = find_chars_or_comment(start, "=:");
      if (*end == '=' || *end == ':') {
        *end = '\0';
        name = rstrip(start);
        value = lskip(end + 1);
#if INI_ALLOW_INLINE_COMMENTS
        end = find_chars_or_comment(value, NULL);
        if (*end)
          *end = '\0';
#endif
        rstrip(value);

        /* Valid name[=:]value pair found, call handler */
        strncpy0(prev_name, name, sizeof(prev_name));
        if (!handler(user, section, name, value) && !error)
          error = lineno;
      } else if (!error) {
        /* No '=' or ':' found on name[=:]value line */
        error = lineno;
      }
    }

#if INI_STOP_ON_FIRST_ERROR
    if (error)
            break;
#endif
  }

#if !INI_USE_STACK
  free(line);
#endif

  return error;
}

/* See documentation in header file. */
inline int ini_parse_file(FILE *file, ini_handler handler, void *user) {
  return ini_parse_stream((ini_reader) fgets, file, handler, user);
}

/* See documentation in header file. */
inline int ini_parse(const char *filename, ini_handler handler, void *user) {
  FILE *file;
  int error;

  file = fopen(filename, "r");
  if (!file)
    return -1;
  error = ini_parse_file(file, handler, user);
  fclose(file);
  return error;
}

#endif /* __INI_H__ */


#ifndef __INIREADER_H__
#define __INIREADER_H__

#include <map>
#include <set>
#include <string>

// Read an INI file into easy-to-access name/value pairs. (Note that I've gone
// for simplicity here rather than speed, but it should be pretty decent.)
class IniReader {
 public:
  // Construct IniReader and parse given filename. See ini.h for more info
  // about the parsing.
  IniReader(std::string filepath);

  // Return the result of ini_parse(), i.e., 0 on success, line number of
  // first error on parse error, or -1 on file open error.
  int parseError();

  // Return the list of sections found in ini file
  std::set<std::string> sections();

  // getString a string value from INI file, returning default_value if not found.
  std::string getString(std::string section, std::string name,
                        std::string default_value);

  // getString an integer (long) value from INI file, returning default_value if
  // not found or not a valid integer (decimal "1234", "-1234", or hex "0x4d2").
  long getLong(std::string section, std::string name, long default_value);

  // getString a real (floating point double) value from INI file, returning
  // default_value if not found or not a valid floating point value
  // according to strtod().
  double getDouble(std::string section, std::string name, double default_value);

  // getString a boolean value from INI file, returning default_value if not found or if
  // not a valid true/false value. Valid true values are "true", "yes", "on", "1",
  // and valid false values are "false", "no", "off", "0" (not case sensitive).
  bool getBool(std::string section, std::string name, bool default_value);

 private:
  int _error;
  std::map<std::string, std::string> _values;
  std::set<std::string> _sections;
  static std::string makeKey(std::string section, std::string name);
  static int valueHandler(void *user, const char *section, const char *name,
                          const char *value);
};

#endif  // __INIREADER_H__


#ifndef __INIREADER__
#define __INIREADER__

#include <algorithm>
#include <cctype>
#include <cstdlib>

using std::string;

inline IniReader::IniReader(string filepath) {
  _error = ini_parse(filepath.c_str(), valueHandler, this);
}

inline int IniReader::parseError() {
  return _error;
}

inline std::set<string> IniReader::sections() {
  return _sections;
}

inline string IniReader::getString(string section, string name, string default_value) {
  string key = makeKey(section, name);
  return _values.count(key) ? _values[key] : default_value;
}

inline long IniReader::getLong(string section, string name, long default_value) {
  string valstr = getString(section, name, "");
  const char *value = valstr.c_str();
  char *end;
  // This parses "1234" (decimal) and also "0x4D2" (hex)
  long n = strtol(value, &end, 0);
  return end > value ? n : default_value;
}

inline double IniReader::getDouble(string section, string name, double default_value) {
  string valstr = getString(section, name, "");
  const char *value = valstr.c_str();
  char *end;
  double n = strtod(value, &end);
  return end > value ? n : default_value;
}

inline bool IniReader::getBool(string section, string name, bool default_value) {
  string valstr = getString(section, name, "");
  // Convert to lower case to make string comparisons case-insensitive
  std::transform(valstr.begin(), valstr.end(), valstr.begin(), ::tolower);
  if (valstr == "true" || valstr == "yes" || valstr == "on" || valstr == "1")
    return true;
  else if (valstr == "false" || valstr == "no" || valstr == "off" || valstr == "0")
    return false;
  else
    return default_value;
}

inline string IniReader::makeKey(string section, string name) {
  string key = section + "=" + name;
  // Convert to lower case to make section/name lookups case-insensitive
  std::transform(key.begin(), key.end(), key.begin(), ::tolower);
  return key;
}

inline int IniReader::valueHandler(void *user, const char *section, const char *name,
                                   const char *value) {
  IniReader *reader = (IniReader *) user;
  string key = makeKey(section, name);
  if (reader->_values[key].size() > 0)
    reader->_values[key] += "\n";
  reader->_values[key] += value;
  reader->_sections.insert(section);
  return 1;
}

#endif  // __INIREADER__


사용법

#include "IniReader.h"

#include <iostream>

int main(void) {

  IniReader reader("test.ini");

  if (reader.parseError() < 0) {
    std::cout << "Can't load 'test.ini'\n";
    return 1;
  }

  double version = reader.getDouble("version", "version", -1);
  string version_name = reader.getString("version", "version_name", "");

  printf("Version:%.2f, (%s)\n", version, version_name.c_str());

  bool mic_enabled = reader.getBool("device", "mic_enabled", false);
  bool speaker_enabled = reader.getBool("device", "speaker_enabled", false);
  bool camera_enabled = reader.getBool("device", "camera_enabled", false);
  bool motion_enabled = reader.getBool("device", "motion_enabled", false);

  printf("mic_enabled : %d, speaker_enabled: %d, camera_enabled:%d, motion_enabled:%d\n",
         mic_enabled, speaker_enabled, camera_enabled, motion_enabled);

  return 0;
}