きままにブログ

プログラミングを主とした私のメモ帳です。寂しいのでコメントください笑

INIファイルの書き込み

仕様

整数・実数・文字列をパラメータとしてとれるINIファイルの読み書きを行う。

テストフォーマット

[section_name]
parameterA = 100
parameterB[0] = 101
parameterB[1] = 102
[another_section_name]
parameterC = 3.14
parameterD = "string"

制約

  • 最後の行は空行でなければならない
  • 空のセクション名、パラメータ名、空のデータは認められない
  • 文字列中に改行があってもいいが、"はエスケープできない
  • 実装上、2のべき乗の個数の配列を確保していくため、余り巨大なファイルは読み込めない

この制約に関しては先読みによって先にサイズを取得し、其の分だけメモリを確保する方法が考えられる。そもそも、型に応じての分岐が大量となってしまってその辺の処理が大変汚くなってしまった。

テスト

#include <iostream>
#include "INIFile.h"

using namespace std;

int main() {
	INIFile ini_file("C:\\Users\\milfeulle\\Documents\\Visual Studio 2013\\Projects\\test\\test\\mytext.txt");

	auto& section = ini_file["section_name"];
	auto& b = section["parameterB"];
	cout << b.get_int(0) << endl;
	cout << b.get_int(1) << endl;

	cin.get();
}

実装例

  • INIFile.h
#pragma once

#include <fstream>
#include <string>
#include <map>
#include <vector>
#include <cassert>

class INIFile {
public:
	enum class TYPE { STRING, INT, REAL };
private:
	class Value {
		template <class T>
		using DataType = std::vector<T>*;

	public:
		Value();
		~Value();
		
		void create(TYPE type);

		inline size_t Length() const {
			switch(type) {
			case TYPE::INT:
				return ((DataType<int>)data)->size();
			case TYPE::REAL:
				return ((DataType<double>)data)->size();
			case TYPE::STRING:
				return ((DataType<std::string>)data)->size();
			default:
				return 0;
			}
		}

		inline TYPE Type() const {
			return type;
		}
		
		inline int& get_int(int no = 0) const {
			return ((DataType<int>)data)->at(no);
		}

		inline double& get_real(int no = 0) const {
			return ((DataType<double>)data)->at(no);
		}

		inline std::string& get_string(int no = 0) const {
			return ((DataType<std::string>)data)->at(no);
		}
		
		inline void set_int(int no, int value) {
			((DataType<int>)data)->at(no) = value;
		}

		inline void set_real(int no, double value) {
			((DataType<double>)data)->at(no) = value;
		}

		inline void set_string(int no, const std::string& value) {
			((DataType<std::string>)data)->at(no) = value;
		}

	private:
		TYPE type;
		void* data;
	};
	using Parameters = std::map<std::string, Value>;

	class Section {
	public:
		Value& operator[](std::string section_name);
		Parameters::const_iterator begin() const;
		Parameters::const_iterator end() const;
	private:
		Parameters parameters;
	};
	using Sections = std::map<std::string, Section>;

public:
	INIFile(std::string file_name);
	~INIFile();
	Section& operator[](std::string section_name);
	void write();

private:
	Sections sections;
	std::string file_name;
};
  • INIFile.cpp
#include "INIFile.h"
#include <iostream>
#include <list>

INIFile::INIFile(std::string file_name) : file_name(file_name) {
	std::ifstream fs(file_name);

	if(!fs.is_open()) {
		throw std::exception();
	}

	enum class STATE {
		HEAD, SECTION, PARAMETER_NAME, PARAMETER_NAME_INDEX, PARAMETER_VALUE, COMMENT
	} state;

	state = STATE::HEAD;
	std::string buffer;
	Section* section = nullptr;
	Value* value = nullptr;
	int index = 0;
	int c = fs.get();
	bool string_flag = false;

	while(c != EOF) {
		switch(state) {
		case STATE::HEAD:
			if(c == '[') {
				state = STATE::SECTION;
				buffer.clear();
			}
			else if(c == ';') {
				state = STATE::COMMENT;
			}
			// 空白は無視
			else if(c == ' ' || c == '\n') {

			}
			else {
				// 空のセクションは認められない
				if(section == nullptr) {
					throw std::exception();
				}

				state = STATE::PARAMETER_NAME;
				continue;
			}
			break;
		case STATE::SECTION:
			if(c == ']') {
				// セクションの名前がない
				if(buffer.empty()) {
					throw std::exception();
				}

				section = &sections[buffer];
				state = STATE::HEAD;
				buffer.clear();
			}
			else {
				buffer += (char)c;
			}
			break;
		case STATE::PARAMETER_NAME:
			if(c == '=') {
				// パラメータの名前がない
				if(buffer.empty()) {
					throw std::exception();
				}

				value = &(*section)[buffer];
				state = STATE::PARAMETER_VALUE;
				string_flag = false;
				index = 0;
				buffer.clear();
			}
			// 添え字があれば続けて取得
			else if(c == '[') {
				// パラメータの名前がない
				if(buffer.empty()) {
					throw std::exception();
				}

				value = &(*section)[buffer];
				state = STATE::PARAMETER_NAME_INDEX;
				buffer.clear();
			}
			// 空白は無視
			else if(c == ' ') {

			}
			else {
				buffer += (char)c;
			}
			break;
		case STATE::PARAMETER_NAME_INDEX:
			if(c == '=') {
				state = STATE::PARAMETER_VALUE;
				string_flag = false;
				buffer.clear();
			}
			// 添え字の数値を取得
			else if(c == ']') {
				// 添え字がない
				if(buffer.empty()) {
					throw std::exception();
				}

				try {
					index = std::stoi(buffer);
				}
				// 数値でない
				catch(const std::invalid_argument&) {
					throw std::exception();
				}
			}
			// =がない
			else if(c == '\n') {
				throw std::exception();
			}
			else {
				buffer += (char)c;
			}
			break;
			/*
				この追加処理が良くない。サイズが分かるはずだから予め確保すべき
			*/
		case STATE::PARAMETER_VALUE:
			if(c == '\n') {
				// 文字列用ダブルクォートがあったら
				if(string_flag) {
					value->create(TYPE::STRING);
					value->set_string(index, buffer);
				}
				else {
					try {
						double x = std::stod(buffer);
						// 実数の場合
						if(x - (int)x) {
							value->create(TYPE::REAL);
							// value->set_real(index, x);
						}
						// 整数の場合
						else {
							value->create(TYPE::INT);
							value->set_int(index, (int)x);
						}
					}
					catch(const std::invalid_argument&) {
						throw std::exception();
					}
				}
				state = STATE::HEAD;
				buffer.clear();
			}
			// 文字列の開始フラグ
			else if(c == '"') {
				string_flag = true;
			}
			// 文字列の開始フラグが立っていなくて空白だった場合無視
			else if(!string_flag && c == ' ') {
				// none
			}
			else {
				buffer += (char)c;
			}
			break;
		}
		
		c = fs.get();
	}
}

INIFile::~INIFile() {

}

INIFile::Section& INIFile::operator[](std::string section_name) {
	return sections[section_name];
}

void INIFile::write() {
	std::ofstream fs(file_name, std::ios::trunc);

	if(!fs.is_open()) {
		throw std::exception();
	}

	for(auto& section : sections) {
		fs << "[" << section.first << "]" << std::endl;

		for(auto& value : section.second) {
			int value_length = value.second.Length();

			if(value_length == 1) {
				switch(value.second.Type()) {
				case TYPE::INT:
					fs << value.first << "=" << value.second.get_int(0) << std::endl;
					break;
				case TYPE::STRING:
					fs << value.first << "=\"" << value.second.get_string(0) << "\"" << std::endl;
					break;
				}
			}
			else {
				for(int i = 0; i < value_length; ++i) {
					switch(value.second.Type()) {
					case TYPE::INT:
						fs << value.first << "[" << i << "]=" << value.second.get_int(i) << std::endl;
						break;
					case TYPE::STRING:
						fs << value.first << "[" << i << "]=" << "=\"" << value.second.get_string(i) << "=\"" << std::endl;
						break;
					}
				}
			}
		}
	}
}

INIFile::Value::Value() : data(nullptr) {
	// none
}

INIFile::Value::~Value() {
	switch(type) {
	case TYPE::INT:
		delete (DataType<int>)data;
		break;
	case TYPE::REAL:
		delete (DataType<double>)data;
		break;
	case TYPE::STRING:
		delete (DataType<std::string>)data;
		break;
	}
}

void INIFile::Value::create(TYPE type) {
	if(data == nullptr) {
		switch(type) {
		case TYPE::INT:
			this->type = TYPE::INT;
			data = new std::vector<int>(2);
			break;
		case TYPE::REAL:
			this->type = TYPE::REAL;
			data = new std::vector<double>(1);
			break;
		case TYPE::STRING:
			this->type = TYPE::STRING;
			data = new std::vector<std::string>(1);
			break;
		}
	}
	else {
		switch(type) {
		case TYPE::INT:
			((DataType<int>)data)->resize(((DataType<int>)data)->size() * 2);
			break;
		case TYPE::REAL:
			((DataType<double>)data)->resize(((DataType<double>)data)->size() * 2);
			break;
		case TYPE::STRING:
			((DataType<std::string>)data)->resize(((DataType<std::string>)data)->size() * 2);
			break;
		}
	}
}

INIFile::Value& INIFile::Section::operator[](std::string section_name) {
	return parameters[section_name];
}

INIFile::Parameters::const_iterator INIFile::Section::begin() const {
	return parameters.begin();
}
INIFile::Parameters::const_iterator INIFile::Section::end() const {
	return parameters.end();
}