640 lines
17 KiB
C++
640 lines
17 KiB
C++
/*
|
|
Feather INI Parser - 1.40
|
|
You are free to use this however you wish.
|
|
|
|
If you find a bug, please attept to debug the cause.
|
|
Post your environment details and the cause or fix in the issues section of GitHub.
|
|
|
|
Written by Turbine.
|
|
|
|
Website:
|
|
https://github.com/Turbine1991/feather-ini-parser
|
|
http://code.google.com/p/feather-ini-parser/downloads
|
|
|
|
Help:
|
|
Bundled example & readme.
|
|
http://code.google.com/p/feather-ini-parser/wiki/Tutorials
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <string>
|
|
#include <fstream>
|
|
#include <sstream>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#define FINI_SAFE
|
|
#define FINI_BUFFER_SIZE 128
|
|
|
|
#if __cplusplus >= 201103L
|
|
#include <unordered_map>
|
|
#define FINI_CPP11
|
|
#define ALLOCATE_SECTIONS 100
|
|
#define ALLOCATE_KEYS 5
|
|
#else
|
|
#include <map>
|
|
#endif
|
|
|
|
#ifdef FINI_WIDE_SUPPORT
|
|
#include <wchar.h>
|
|
|
|
typedef std::wstringstream fini_sstream_t;
|
|
typedef std::wstring fini_string_t;
|
|
typedef wchar_t fini_char_t;
|
|
typedef std::wifstream fini_ifstream_t;
|
|
typedef std::wofstream fini_ofstream_t;
|
|
|
|
#define fini_strlen(a) wcslen(a)
|
|
#define fini_strncpy(a, b) wcscpy(a, b)
|
|
#define fini_strncpy(a, b, c) wcsncpy(a, b, c)
|
|
#define fini_strtok(a, b) wcstok(a, b)
|
|
|
|
#define _T(x) L ##x
|
|
#else
|
|
#include <cstring>
|
|
|
|
typedef std::stringstream fini_sstream_t;
|
|
typedef std::string fini_string_t;
|
|
typedef char fini_char_t;
|
|
typedef std::ifstream fini_ifstream_t;
|
|
typedef std::ofstream fini_ofstream_t;
|
|
|
|
#define fini_strlen(a) strlen(a)
|
|
#define fini_strcpy(a, b) strcpy(a, b)
|
|
#define fini_strncpy(a, b, c) strncpy(a, b, c)
|
|
#define fini_strtok(a, b) strtok(a, b)
|
|
|
|
#define _T(x) x
|
|
#endif
|
|
|
|
#define CHAR_SIZE sizeof(fini_char_t)
|
|
|
|
///Simple converter using templates and streams to effectively required for the flexibility of handling native types
|
|
class Converters
|
|
{
|
|
public:
|
|
template <typename T, typename U>
|
|
static T Convert(U value);
|
|
template <typename T>
|
|
static void GetLine(fini_sstream_t& out, T& value);
|
|
static void GetLine(fini_sstream_t& out, fini_string_t& value);
|
|
template <typename T>
|
|
static size_t GetDataSize(T& value);
|
|
static size_t GetDataSize(fini_string_t value);
|
|
};
|
|
|
|
///
|
|
template <typename T = fini_string_t, typename U = fini_string_t, typename V = fini_string_t>
|
|
class INI
|
|
{
|
|
public:
|
|
typedef T section_t;
|
|
typedef U key_t;
|
|
typedef V value_t;
|
|
typedef INI<section_t, key_t, value_t> ini_t;
|
|
|
|
///Type definition declarations
|
|
#ifdef FINI_CPP11
|
|
typedef typename std::unordered_map<key_t, value_t> keys_t;
|
|
typedef typename std::unordered_map<section_t, keys_t*> sections_t;
|
|
#else
|
|
typedef typename std::map<key_t, value_t> keys_t;
|
|
typedef typename std::map<section_t, keys_t*> sections_t;
|
|
#endif
|
|
|
|
typedef typename keys_t::iterator keysit_t;
|
|
typedef typename sections_t::iterator sectionsit_t;
|
|
|
|
typedef typename std::pair<key_t, value_t> keyspair_t;
|
|
typedef typename std::pair<section_t, keys_t*> sectionspair_t;
|
|
|
|
typedef char data_t;
|
|
|
|
enum source_e {SOURCE_FILE, SOURCE_MEMORY};
|
|
|
|
///Data members
|
|
std::string filename;
|
|
data_t* data;
|
|
size_t dataSize;
|
|
keys_t* current;
|
|
sections_t sections;
|
|
source_e source;
|
|
|
|
///Constuctor/Destructor
|
|
//Specify the filename to associate and whether to parse immediately
|
|
INI(const std::string filename, bool doParse): filename(filename)
|
|
{
|
|
init(SOURCE_FILE, doParse);
|
|
}
|
|
|
|
//Used for loading INI from memory
|
|
INI(void* data, size_t dataSize, bool doParse): data((data_t*)data), dataSize(dataSize)
|
|
{
|
|
init(SOURCE_MEMORY, doParse);
|
|
}
|
|
|
|
~INI()
|
|
{
|
|
clear();
|
|
}
|
|
|
|
///Access Content
|
|
//Provide bracket access to section contents
|
|
keys_t& operator[](section_t section)
|
|
{
|
|
#ifdef FINI_SAFE
|
|
if (!sections[section])
|
|
sections[section] = new keys_t;
|
|
#endif
|
|
|
|
return *sections[section];
|
|
}
|
|
|
|
//Create a new section and select it
|
|
bool create(const section_t section)
|
|
{
|
|
if (select(section))
|
|
return false;
|
|
|
|
current = new keys_t;
|
|
sections[section] = current;
|
|
|
|
reserveKeys(current);
|
|
|
|
return true;
|
|
}
|
|
|
|
//Select a section for performing operations
|
|
bool select(const section_t section)
|
|
{
|
|
sectionsit_t sectionsit = sections.find(section);
|
|
if (sectionsit == sections.end())
|
|
return false;
|
|
|
|
current = sectionsit->second;
|
|
|
|
return true;
|
|
}
|
|
|
|
///Set
|
|
//Assign a value for key under the selected section
|
|
bool set(const key_t key, const value_t value)
|
|
{
|
|
if (current == NULL)
|
|
return false;
|
|
|
|
(*current)[key] = value;
|
|
|
|
return true;
|
|
}
|
|
|
|
template <typename W, typename X>
|
|
bool set(const W key, const X value)
|
|
{ return set(Converters::Convert<key_t>(key), Converters::Convert<value_t>(value)); }
|
|
|
|
///Get
|
|
value_t get(const key_t key, value_t def = value_t())
|
|
{
|
|
keysit_t it = current->find(key);
|
|
if (current == NULL || it == current->end())
|
|
return def;
|
|
|
|
return it->second;
|
|
}
|
|
|
|
value_t get(const section_t section, const key_t key, value_t def)
|
|
{
|
|
if (!select(section))
|
|
return def;
|
|
|
|
return get(key, def);
|
|
}
|
|
|
|
template <typename W, typename X>
|
|
X get(const W key, const X def = value_t())
|
|
{ return Converters::Convert<X>(get(Converters::Convert<key_t>(key), Converters::Convert<value_t>(def))); }
|
|
|
|
template <typename W>
|
|
fini_string_t get(const W key, const fini_char_t* def = _T("")) //Handle C string default value without casting
|
|
{ return Converters::Convert<fini_string_t>(get(Converters::Convert<key_t>(key), Converters::Convert<value_t>(def))); }
|
|
|
|
template <typename W, typename X, typename Y>
|
|
Y get(const W section, const X key, const Y def)
|
|
{ return Converters::Convert<Y>(get(Converters::Convert<section_t>(section), Converters::Convert<key_t>(key), Converters::Convert<value_t>(def))); }
|
|
|
|
template <typename W, typename X>
|
|
fini_string_t get(const W section, const X key, const fini_char_t* def) //Handle C string default value without casting
|
|
{ return Converters::Convert<fini_string_t>(Converters::Convert<section_t>(section), get(Converters::Convert<key_t>(key), Converters::Convert<value_t>(def))); }
|
|
|
|
///Functions
|
|
void parse(std::istream& file)
|
|
{
|
|
fini_char_t line[FINI_BUFFER_SIZE];
|
|
bool first = true;
|
|
fini_sstream_t out;
|
|
|
|
while(!file.eof())
|
|
{
|
|
file.getline(line, FINI_BUFFER_SIZE);
|
|
|
|
if (first)
|
|
{
|
|
first = false;
|
|
if (line[0] == 0xEF) //Allows handling of UTF-16/32 documents
|
|
{
|
|
memmove(line, line + (CHAR_SIZE * 3), CHAR_SIZE * (FINI_BUFFER_SIZE - 3));
|
|
return;
|
|
}
|
|
}
|
|
|
|
nake(line);
|
|
|
|
if (line[0])
|
|
{
|
|
size_t len = fini_strlen(line);
|
|
if (len > 0 && !((len >= 2 && (line[0] == '/' && line[1] == '/')) || (len >= 1 && line[0] == '#'))) //Ignore comment and empty lines
|
|
{
|
|
if (line[0] == '[') //Section
|
|
{
|
|
section_t section;
|
|
size_t length = fini_strlen(line) - 2; //Without section brackets
|
|
while(isspace(line[length + 1])) //Leave out any additional new line characters, not "spaces" as the name suggests
|
|
--length;
|
|
|
|
fini_char_t* ssection = (fini_char_t*)calloc(CHAR_SIZE, length + 1);
|
|
fini_strncpy(ssection, line + 1, length); //Count after first bracket
|
|
|
|
current = new keys_t;
|
|
|
|
out << ssection;
|
|
free(ssection);
|
|
Converters::GetLine(out, section);
|
|
|
|
sections[section] = current;
|
|
}
|
|
else //Key
|
|
{
|
|
key_t key;
|
|
value_t value;
|
|
|
|
fini_char_t* skey;
|
|
fini_char_t* svalue;
|
|
|
|
skey = fini_strtok(line, _T("="));
|
|
svalue = fini_strtok(NULL, _T("\n"));
|
|
|
|
if (skey && svalue)
|
|
{
|
|
size_t index = 0; //Without section brackets
|
|
while(isspace(skey[index])) //Leave out any additional new line characters, not "spaces" as the name suggests
|
|
index++;
|
|
|
|
if (index != 0) //Has preceeding white space
|
|
fini_strcpy(skey, skey + index);
|
|
|
|
out << skey;
|
|
|
|
Converters::GetLine(out, key);
|
|
|
|
out.clear();
|
|
out.str(fini_string_t());
|
|
|
|
out << svalue;
|
|
Converters::GetLine(out, value);
|
|
|
|
if (value != value_t())
|
|
(*current)[key] = value;
|
|
}
|
|
}
|
|
|
|
out.clear();
|
|
out.str(fini_string_t()); //Clear existing stream;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//Parse an INI's contents into memory from the filename given during construction
|
|
bool parse()
|
|
{
|
|
switch(source)
|
|
{
|
|
case SOURCE_FILE: {
|
|
fini_ifstream_t file(filename.c_str());
|
|
|
|
if (!file.is_open())
|
|
return false;
|
|
|
|
parse(file);
|
|
|
|
file.close();
|
|
}
|
|
break;
|
|
|
|
case SOURCE_MEMORY: {
|
|
std::stringstream sstream;
|
|
sstream.rdbuf()->pubsetbuf(data, dataSize);
|
|
|
|
parse(sstream);
|
|
}
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool parseBinary()
|
|
{
|
|
fini_ifstream_t file(filename.c_str(), std::ios::binary);
|
|
if (!file.is_open())
|
|
return false;
|
|
|
|
size_t sectionCount;
|
|
size_t keyCount;
|
|
key_t key;
|
|
value_t value;
|
|
section_t section;
|
|
|
|
//file.read((fini_char_t*)§ionCount, sizeof(sectionCount));
|
|
file >> sectionCount;
|
|
|
|
for(size_t i = 0; i < sectionCount; i++)
|
|
{
|
|
if (i > 0)
|
|
file.seekg(1 + file.tellg());
|
|
|
|
file.read((fini_char_t*)&keyCount, sizeof(keyCount));
|
|
file >> section;
|
|
|
|
create(section);
|
|
|
|
for(size_t j = 0; j < keyCount; j++)
|
|
{
|
|
file >> key;
|
|
file >> value;
|
|
set(key, value);
|
|
}
|
|
}
|
|
|
|
file.close();
|
|
|
|
return true;
|
|
}
|
|
|
|
//Clear the contents from memory
|
|
void clear()
|
|
{
|
|
clean();
|
|
sections.clear();
|
|
}
|
|
|
|
///Output
|
|
//Save from memory into file
|
|
bool save(const std::string filename = "")
|
|
{
|
|
if (!hasFileAssociation(filename))
|
|
return false;
|
|
|
|
fini_ofstream_t file(((filename == "")? this->filename: filename).c_str(), std::ios::trunc);
|
|
if (!file.is_open())
|
|
return false;
|
|
|
|
//Loop through sections
|
|
for(typename INI::sectionsit_t i = sections.begin(); i != sections.end(); i++)
|
|
{
|
|
if (i->second->size() == 0) //No keys/values in section, skip to next
|
|
continue;
|
|
|
|
//Write section
|
|
const fini_string_t temp = makeSection(i->first);
|
|
const fini_char_t* line = temp.c_str();
|
|
file.write(line, fini_strlen(line));
|
|
|
|
for(typename INI::keysit_t j = i->second->begin(); j != i->second->end(); j++)
|
|
{
|
|
//Write key and value
|
|
const fini_string_t temp = makeKeyValue(j->first, j->second);
|
|
const fini_char_t* line = temp.c_str();
|
|
file.write(line, fini_strlen(line));
|
|
}
|
|
}
|
|
|
|
file.close();
|
|
|
|
return true;
|
|
}
|
|
|
|
//Saves it without any conventional INI formatting characters, however it only uses string streams
|
|
bool saveBinary(const std::string filename = "")
|
|
{
|
|
if (!hasFileAssociation(filename))
|
|
return false;
|
|
|
|
fini_ofstream_t file(((filename == "")? this->filename: filename).c_str(), std::ios::trunc | std::ios::binary);
|
|
if (!file.is_open())
|
|
return false;
|
|
|
|
size_t sectionCount = sections.size();
|
|
size_t keyCount;
|
|
|
|
file.write((fini_char_t*)§ionCount, sizeof(sectionCount));
|
|
|
|
//Loop through sections
|
|
for(typename INI::sectionsit_t i = sections.begin(); i != sections.end(); i++)
|
|
{
|
|
keyCount = i->second->size();
|
|
file.write((fini_char_t*)&keyCount, sizeof(keyCount));
|
|
|
|
file << i->first << std::endl;
|
|
|
|
for(typename INI::keysit_t j = i->second->begin(); j != i->second->end(); j++)
|
|
{
|
|
file << j->first << std::endl;
|
|
file << j->second << std::endl;
|
|
}
|
|
}
|
|
|
|
file.close();
|
|
|
|
return true;
|
|
}
|
|
|
|
//Saves it as a true binary file, intended to replace the existing one. Don't bother using it with all strings.
|
|
bool saveBinaryExperimental(std::string filename = "")
|
|
{
|
|
if (!hasFileAssociation(filename))
|
|
return false;
|
|
|
|
fini_ofstream_t file(((filename == "")? this->filename: filename).c_str(), std::ios::trunc | std::ios::binary);
|
|
if (!file.is_open())
|
|
return false;
|
|
|
|
size_t sectionCount = sections.size();
|
|
size_t keyCount;
|
|
|
|
file.write((fini_char_t*)§ionCount, sizeof(sectionCount));
|
|
|
|
//Loop through sections
|
|
for(typename INI::sectionsit_t i = sections.begin(); i != sections.end(); i++)
|
|
{
|
|
keyCount = i->second->size();
|
|
file.write((fini_char_t*)&keyCount, sizeof(keyCount));
|
|
|
|
file.write((fini_char_t*)&i->first, Converters::GetDataSize(i->first));
|
|
|
|
for(typename INI::keysit_t j = i->second->begin(); j != i->second->end(); j++)
|
|
{
|
|
file.write((fini_char_t*)&j->first, Converters::GetDataSize(j->first));
|
|
file.write((fini_char_t*)&j->second, Converters::GetDataSize(j->second));
|
|
}
|
|
}
|
|
|
|
file.close();
|
|
|
|
return true;
|
|
}
|
|
|
|
//Alows another INI's contents to be insert into another, with the ability to retain the original values
|
|
void merge(ini_t& other, bool retainValues = true)
|
|
{
|
|
for(typename INI::sectionsit_t i = other.sections.begin(); i != other.sections.end(); i++)
|
|
{
|
|
if (!select(i->first)) //Create and insert all key values into a missing section
|
|
{
|
|
keys_t* keys = new keys_t(*i->second);
|
|
sections.insert(std::make_pair(i->first, keys));
|
|
}
|
|
else
|
|
{
|
|
for(typename INI::keysit_t j = i->second->begin(); j != i->second->end(); j++)
|
|
{
|
|
keysit_t it = current->find(j->first);
|
|
if (it == current->end())
|
|
current->insert(std::make_pair(j->first, j->second));
|
|
else if (!retainValues)
|
|
it->second = j->second;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
///Functions
|
|
//Init the INI in with values set by constructor
|
|
void init(source_e source, bool doParse)
|
|
{
|
|
this->source = source;
|
|
|
|
reserveSections();
|
|
if (doParse)
|
|
parse();
|
|
}
|
|
|
|
//Clean the contents for descruction
|
|
void clean()
|
|
{
|
|
for(sectionsit_t i = sections.begin(); i != sections.end(); i++)
|
|
delete i->second;
|
|
|
|
current = NULL;
|
|
}
|
|
|
|
//Make any alterations to the raw line
|
|
void nake(const fini_char_t*) //Strip the line of any non-interpretable characters
|
|
{
|
|
|
|
}
|
|
|
|
void reserveSections()
|
|
{
|
|
#ifdef FINI_CPP11
|
|
sections.reserve(ALLOCATE_SECTIONS);
|
|
#endif
|
|
}
|
|
|
|
void reserveKeys(keys_t* current)
|
|
{
|
|
#ifdef FINI_CPP11
|
|
current->reserve(ALLOCATE_KEYS);
|
|
#endif
|
|
}
|
|
|
|
bool hasFileAssociation(std::string filename)
|
|
{
|
|
if (source == SOURCE_MEMORY && filename == "") //No association to a file
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
///Output
|
|
//Creates a section as a string
|
|
fini_string_t makeSection(const section_t& section)
|
|
{
|
|
fini_sstream_t line;
|
|
line << '[' << section << ']' << std::endl;
|
|
|
|
return line.str();
|
|
}
|
|
|
|
//Creates a key and a value as a string
|
|
fini_string_t makeKeyValue(const key_t& key, const value_t& value)
|
|
{
|
|
fini_sstream_t line;
|
|
line << key << '=' << value << std::endl;
|
|
|
|
return line.str();
|
|
}
|
|
};
|
|
|
|
///Definitions
|
|
template <typename T, typename U>
|
|
inline T Converters::Convert(U value)
|
|
{
|
|
fini_sstream_t sout;
|
|
T result;
|
|
|
|
sout << value;
|
|
sout >> result;
|
|
|
|
sout.str(fini_string_t());
|
|
|
|
return result;
|
|
}
|
|
|
|
template <>
|
|
inline fini_string_t Converters::Convert<fini_string_t, fini_string_t>(fini_string_t value)
|
|
{
|
|
return value;
|
|
}
|
|
|
|
template <>
|
|
inline fini_string_t Converters::Convert<fini_string_t>(const fini_char_t* value)
|
|
{
|
|
return value;
|
|
}
|
|
|
|
template <typename T>
|
|
inline void Converters::GetLine(fini_sstream_t& out, T& value)
|
|
{
|
|
out >> value;
|
|
}
|
|
|
|
inline void Converters::GetLine(fini_sstream_t& out, fini_string_t& value)
|
|
{
|
|
std::getline(out, value);
|
|
}
|
|
|
|
template <typename T>
|
|
inline size_t Converters::GetDataSize(T& value)
|
|
{
|
|
return sizeof(value);
|
|
}
|
|
|
|
inline size_t Converters::GetDataSize(fini_string_t value)
|
|
{
|
|
return value.size() + 1;
|
|
}
|