Компонент для работы с ini-файлом

Автор: Попов Юрий Юрьевич
Опубликовано: 23.09.2003
Версия текста: 1.0

Введение
Терминология
Возможности
Краткое описание компонента
Использование (Быстрый старт)
Подробное описание компонента и входящих в него классов
Основные классы
Обработка ошибок и класс CException
Работа с собственными типами данных и класс CCommonValue
Классы, представляющие типы данных
Заключение
Благодарности

Исходные коды
Демонстрационный проект

Введение

При разработке одной из программ я столкнулся с совершенно типовой проблемой: было необходимо использовать ini-файл для хранения конфигурационных данных программы. Задача стандартная, но в моем случае данные были совершенно разных типов (целые числа, числа с плавающей точкой, строки, цвет в кодировке RGB). Каждый из параметров должен был удовлетворять определенным ограниченям. Подходящих решений под рукой не оказалось, в связи с чем на свет появился этот компонент.

Терминология

Для начала, давайте определимся с используемой терминологией. Ini-файл – текстовый файл, содержащий записи о конфигурации программы. Для удобства он разбит на секции. Секция – это именованная совокупность параметров. Секция начинается с имени секции. Имя секции должно быть заключено в угловые скобки и может содержать любые символы (за исключением символов ‘[‘ и ‘]’). Секция содержит произвольное количество параметров. Параметр это строка вида key=value, где key – это ключ, а value – значение, сопоставленное с этим ключом. Ключ и значение могут содержать любые символы (за исключением символа ‘=‘). Строка, начинающаяся с символа ‘;’ считается комментарием и пропускается.

ПРИМЕЧАНИЕ

Пробельные символы вокруг ключа и параметра игнорируются (т.е. строка вида “ key = value “ будет сведена к строке “key=value”).

ПРИМЕЧАНИЕ

Префиксный и постфиксный символы для имени группы задается константами IniParser::chSectPrefix и IniParser::chSectPostfix.

Возможности

Краткое описание компонента

Все классы компонента полностью реализованы на языке C++ с использованием исключительно библиотеки STL (за исключением класса CRGBValue, в котором был использован Windows’овский тип данных COLORREF), что позволяет использовать данный компонент как в DOS’овских так и любых в Win16/32 приложениях. Все классы компонента помещены в пространство имен IniParser. Компонент включает в себя следующие классы:

Основные классы

CParserпредставляет основную функциональность по работе с ini-файлами
CSectionпредставляет секцию ini-файла
CParameterпредставляет параметр
CExceptionпредставляет исключение

Классы, представляющие типы данных

CCommonValueПредставляет абстрактный тип данных. Является базовым классом для всех классов, представляющих какие-либо типы данных.
CNumericTypesШаблон. Представляет значения числовых типов (int, float, double...).
CRangeLimitsШаблон. Представляет ограничения на значения числовых типов. Используется только совместно с шаблоном CNumericTypes.
CStdStringValueПредставляет строковое значение (строка STL – std::string).
CStringValueПредставляет строковое значение (стандартная C-строка (char *) с известной длиной).
CRGBValueПредставляет цвет в кодировке RGB.
CBoolValueПредставляет значения булевского типа.

Как видно из приведенной таблицы, на каждый тип данных заведен отдельный класс, за исключением числовых типов: они представлены шаблоном CNumericTypes. Все классы, представляющие какие-либо типы данных, наследуются от абстрактного класса CCommonValue. Если вы захотите работать с данными своего собственного типа, то вам нужно будет создать класс производный от CCommonValue, представляющий ваш тип данных и реализовать интерфейс класса CCommonValue.

Для каждого параметра вы можете определить, является он обязательным или нет. Если при чтении файла обязательный параметр не найден, то возбуждается исключение.

Использование (Быстрый старт)

Многие программисты, знакомясь с очередным компонентом, в первую очередь не читают документацию, а смотрят заголовочные файлы компонента и исходные тексты примеров, поэтому после краткого описания компонента я привожу хорошо откомментированный пример, изучение которого в совокупности с просмотром заголовочного файла позволит многим сразу же начать успешно использовать компонент.

ПРЕДУПРЕЖДЕНИЕ

Имена секций и параметров не должны содержать русских букв и символа ‘=’.

1. Скопируете файлы компонента (IniParser.cpp и IniParser.h) в папку к вашему проекту, добавьте их в проект.

2. Добавьте в файл stdafx.h следующие строки

#pragma warning (disable : 4786)
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <list>
#include <map>

3. Определить структуру будущего ini-файла, тип, диапазоны допустимых значений и обязательность для каждого параметра. Допустим, что наш ini-файл имеет следующую структуру:

[Common]
; Параметр AutoStart. Логическое значение (стиль true/false). Обязательный.
AutoStart=true

; Параметр RecentSize. Целочисленный параметр. Ограничения: от 0 до 20. Обязательный.
RecentSize=10

; Параметр Integer_hex. Целое число в 16-ричной системе счисления. Нет ограничений. Необязательный.
Integer_hex=0xdead

; Параметр Integer_oct. Целое число в 8-ричной системе счисления. Нет ограничений. Необязательный.
Integer_oct=01234

; Параметр FloatValue. Число с плавающей точкой (float). Ограничения: от -10 до -1. Обязательный.
FloatValue=-2

; Параметр Float_fixed_prec_3. Число с плавающей точкой, формат - фиксированный, точность - 3. Необязательный.
float_fixed_prec_3=3.142987098

; Параметр Double_scientific. Число с плавающей точкой, формат - научный. Необязательный.
Double_scientific=3.141593e+000

; Параметр StdStringValue. Строковый параметр (строка std::string). Длина не ограничена. Пустые строки допустимы. Необязательный.
StdString=STL String

; Параметр StringValue. Строковый параметр (стандартная C-style строка). Длина не более 10 символов. Пустые строки недопустимы. Обязательный.
CStyleString=standard C-style string

; Параметр ColorRGBValue. Цвет в кодировке RGB. Обязательный.
ColorRGBValue=255, 255, 192

4. Объявить в удобном месте (например, в классе) переменные, соответствующие параметрам

5. Создать объект класса IniParser::CParser

6. Сформировать структуру ini-файла, используя классы IniParser::CParameter и IniParser::CSection, методы IniParser::CParser::AddSection и IniParser::CSection::AddParameter.

7. Вызвать один из методов класса IniParser::CParser для считывания или сохранения данных. И не забывать про обработку исключений!

const char * const chErrMsg[] = {
	"FileSystemError",
	"UnknownSection",
	"UnknownParameter",
	"InvalidString",
	"InvalidValue",
	"ValueOutOfRanges",
	"ParameterAlreadyDefined",
	"MissingMandatoryParam"
};

//...

	using namespace IniParser;

	// (4) Переменные, соответствующие параметрам ini-файла
	bool bAutoStart = true;
	int nRecentSize = 10;
	float fFloatValue = -2.0f;
	int nInteger_hex = 0xdead, nInteger_oct = 012345;
	float float_scientific = 3.1415926f, float_fixed = 3.1415926f, float_fixed_prec_3 = 3.1415926f;
	double dDouble_scientific = 3.1415926;
	std::string strStdString("STL String");
	char chString[11] = "abcd";
	COLORREF clrColorRGBValue = RGB(255, 255, 192);

	// (5) Объект класса IniParser::CParser
	CParser pars(false);

	// (6) Формирование структуры ini-файла
	// Добавляем секцию "Common"
	CSection *pSect = pars.AddSection("Common");
	{
		// Теперь, добавим в секцию параметры
		
		// Параметр AutoStart. Логическое значение (стиль true/false). Обязательный.
		pSect->AddParameter("AutoStart", new CBoolValue(&bAutoStart), true);
		
		// Параметр RecentSize. Целочисленный параметр. Ограничения: от 0 до 20. Обязательный.
		pSect->AddParameter("RecentSize", new CNumericType<int>(&nRecentSize, new CRangeLimits<int>(CRangeLimits<int>::MinMax, 0, 20)), true);
		// Параметр Integer_hex. Целое число в шестнадцатеричной системе счисления. Нет ограничений. Необязательный.
		pSect->AddParameter("Integer_hex", new CNumericType<int>(&nInteger_hex, NULL, CNumericType<int>::hex), false);
		// Параметр Integer_oct. Целое число в шестнадцатеричной системе счисления. Нет ограничений. Необязательный.
		pSect->AddParameter("Integer_oct", new CNumericType<int>(&nInteger_oct, NULL, CNumericType<int>::hex), false);

		// Параметр FloatValue. Число с плавающей точкой (float). Ограничения: от -10 до -1. Обязательный.
		pSect->AddParameter("FloatValue", new CNumericType<float>(&fFloatValue, new CRangeLimits<float>(CRangeLimits<float>::MinMax, -10, -1)), true);
		// Параметр Float_fixed_prec_3. Число с плавающей точкой, формат - фиксированный, точность - 3. Необязательный.
		pSect->AddParameter("float_fixed_prec_3", new CNumericType<float>(&float_fixed_prec_3, NULL, CNumericType<float>::fixed, 3), false);
		// Параметр Double_scientific. Число с плавающей точкой, формат - научный. Необязательный.
		pSect->AddParameter("Double_scientific", new CNumericType<double>(&dDouble_scientific, NULL, CNumericType<double>::scientific), false);
		
		// Параметр StdStringValue. Строковый параметр (строка std::string). Длина не ограничена. Пустые строки допустимы. Необязательный.
		pSect->AddParameter("StdString", new CStdStringValue(&strStdString, -1, true), false);
		// Параметр StringValue. Строковый параметр (стандартная C-style строка). Длина не более 10 символов. Пустые строки недопустимы. Обязательный.
		pSect->AddParameter("CStyleString", new CStringValue(chString, 10, false), true);
		
		// Параметр ColorRGBValue. Цвет в кодировке RGB. Обязательный.
		pSect->AddParameter("ColorRGBValue", new CRGBValue(&clrColorRGBValue), true);
	}

	// (7) Вызвать один из методов класса IniParser::CParser для считывания или сохранения данных. И не забывать про обработку исключений!
	try {
		// Читаем файл
		pars.ParseFile("test.ini");
 
		// Изменим некоторые параметры
		bAutoStart = false;
		clrColorRGBValue = RGB(0, 192, 252);
		strStdString = "new std-style string";

		// Выводим на экран для проверки
		std::cout << "  *** Ini file data ***" << std::endl << pars;

		// И, для окончательной проверки, сохраняем прочитанное в новый файл
		pars.StoreToFile("test_temp.ini");
	} catch (CException *pException) {
		// Обработка исключений
		const IniParser::CParameter *pParameter = pException->GetParameter();

		std::cout << "Caught an exception!" << std::endl;
		if (IniParser::FileSystemError == pException->GetCode()) {
			std::cout << "   File system error. Code: " << pException->GetLineNum() << std::endl;
		} else if (IniParser::MissingMandatoryParam == pException->GetCode()) {
			std::cout << "   Missing mandatory parameter" << std::endl;
		} else {
			std::cout << "   Line: " << pException->GetLineNum() << std::endl << 
			"   Cause: " << chErrMsg[pException->GetCode()] << std::endl;
		}
		if (pParameter)
			std::cout << "   Key: " << pParameter->GetKey() << std::endl;

		pException->Delete();
	}

Внимательный читатель заметит, что в приведенном примере для создания объектов классов представляющих типы данных используется оператор new и нигде нет соответствующих операторов delete. Все дело в том, что выделенная память освобождается в деструкторе класса CParameter, так что в явном вызове оператора delete нет необходимости.

Подробное описание компонента и входящих в него классов

В данном разделе приведено достаточно подробное описание классов, составляющих данный компонент.

ПРИМЕЧАНИЕ

Обратите внимание, что все классы компонента имеют виртуальный деструктор, что дает вам возможность наращивать функциональность компонента.

Основные классы

Класс CParser

Назначение: предоставляет основную функциональность по работе с ini-файлами.

Конструктор:

CParser(bool bAllowInvalidStrings = false, bool bAllowUnkSections = true, bool bAllowUnkKeys = true);

Параметры конструктора:

bAllowInvalidStringsопределяет поведение в случае обнаружения неверной (не являющейся именем секции или параметром) строкиtrue – пропустить
false - возбудить исключение
bAllowUnkSectionsопределяет поведение в случае обнаружения неизвестной секцииtrue – пропустить
false - возбудить исключение
bAllowUnkKeysопределяет поведение в случае обнаружения неизвестного ключаtrue – пропустить
false - возбудить исключение

Методы:

void ParseFile (const char *pchFilePath) throw (CException *);Читает данные из ini-файла в связанные переменные, в случае ошибок выбрасывает исключение.
void StoreToFile(const char *pchFilePath) const throw (CException *);Сохраняет данные из связанных переменных в ini-файл, в случае ошибок выбрасывает исключение.
void ParseStream(std::istream &rStream) throw (CException *);Читает данные из потока в связанные переменные, в случае ошибок выбрасывает исключение.
void StoreToStream(std::ostream &rStream) const throw (CException *);Сохраняет данные из связанных переменных в поток, в случае ошибок выбрасывает исключение.

CSection *AddSection(const char *pchSectionName);Добавляет секцию. Возвращает указатель на добавленную секцию.
bool RemoveSection(const char *pchSectionName);Удаляет секцию по имени (без префиксного и постфиксного символов), в случае успеха возвращает true.
void RemoveAllSections();Удаляет все секции и освобождает связанную с ними память.
CSection *GetSection(const std::string &rString) const;CSection *GetSection(const char *pchString) const;Возвращает секцию по её имени.

TSectionsConstIt GetSectionsBegin() const;Возвращает итератор на начало списка секций.
TSectionsConstIt GetSectionsEnd() const;Возвращает итератор на конец списка секций.

Операторы:

std::ostream& operator<<(std::ostream& stream, const CParser &pars);Выводит данные из связанных переменных в поток, в случае ошибок выбрасывает исключение.

Пример:
CParser pars;

// …

std::cout << pars

Данный код выведет ini-файл в стандартный поток вывода.
std::istream& operator>>(std::istream& stream, CParser &pars);Читает данные из потока в связанные переменные, в случае ошибок выбрасывает исключение.

Класс CSection.

Назначение: представляет секцию ini-файла

Методы:

void AddParameter(const char *pchKey, CCommonValue *pValue, bool bMandatory = false);Добавляет параметр в секцию.
pchKey – константный указатель на строку, содержащую имя ключа
pValue – указатель на объект значения
bMandatory – определяет обязательность параметра (true – обязательный, false – нет).
Если при разборе файла обязательный параметр не найден, возбуждается исключение.
Объект, на который указывает pValue будет удален в деструкторе класса CSection.

Класс CParameter.

Назначение: представляет параметр

Методы:

const std::string &GetKey() const;Возвращает ключ
const CCommonValue *GetValue() const;Возвращает константный указатель на объект значения
std::string ToString() const throw (CException *);Возвращает строковое представление параметра

Обработка ошибок и класс CException

При возникновении ошибочной ситуации при чтении или сохранении ini-файла генерируется исключение. Исключение представлено классом CException. В обработчике исключения можно узнать номер строки файла, при работе с которой произошло исключение, код ошибки, и указатель на объект класса CParameter (он может быть NULL). Возможные коды ошибок прописаны в перечислении TErrorCodes, вот они:

FileSystemErrorОшибка файловой системы. Метод GetLineNum() класса CException будет содержать возвращать код ошибки.
UnknownSectionНеизвестная секция.
UnknownParameterНеизвестный параметр.
InvalidStringНеверная строка.
InvalidValueНеверное значение (ошибка преобразования).
ValueOutOfRangesЗначение не соответствует наложенным ограничениям.
ParameterAlreadyDefinedПараметр определен несколько раз.
MissingMandatoryParamОтсутствует обязательный параметр.

Методы класса CException:

int GetLineNum() const;Возвращает номер строки файла, при работе с которой произошла ошибка.
TErrorCodes GetCode() const;Возвращает код ошибки.
const CParameter *GetParameter() const;Возвращает указатель на объект класса CParameter.

Во избежании утечки памяти в обработчике catch следует удалить объект исключения вызвав метод CException::Delete().

Работа с собственными типами данных и класс CCommonValue

Класс CCommonValue

Назначение: представляет абстрактный тип данных. Является базовым классом для всех классов, представляющих какие-либо типы данных.

Методы:

virtual void Parse(const std::string &rString) throw (CException *) = 0;Пытается пребразовать содержимое строки rString в значение определенного типа, в случае неудачи выбрасывает исключение.
virtual std::string ToString() const throw (CException *) = 0;Возвращает строковое представление значения
virtual bool IsSatisfy() const = 0;Проверяет состояние объекта и возвращает true, если в связанной переменной находится корректное (удовлетворяющее ограничениям) значение

Классы, представляющие типы данных

Шаблон CNumericTypes<T>

Назначение: представляет значения числовых типов (int, float, double...)

Конструктор:

CNumericType(T *pData, CRangeLimits<T> *pLimits, EFlags flags = dec, int nPrecision = 6);

Параметры конструктора:

pDataУказатель на переменную типа T, в которую будут записываться/считываться значения
pLimitsУказатель на экземпляр класса CNumericType::CRangeLimits, с помощью которого задаются ограничения на значение. Если ограничений нет, то pLimits д.б. равен NULL.
Объект, на который указывает, pLimits будет удален в деструкторе шаблона CNumericType.
flagsФлаги форматирования. Допустимы следующие значения флагов:
dec – десятичное число (только целые числа)
hex – шестнадцатеричное число (только целые числа)
oct – восьмеричное число (только целые числа)
scientific – число выводится в научном формате (только числа с плавающей точкой)
fixed – число выводится в фиксированном формате (только числа с плавающей точкой)
nPrecisionТочность (количество знаков после запятой)

Шаблон CRangeLimits<T>

Назначение: представляет ограничения на значение

Конструктор:

CRangeLimits(ELimits lim, T min, T max)

Параметры конструктора:

limВид ограничения, может принимать следующие значения:
CRangeLimits::MinMax – ограничение сверху и снизу
CRangeLimits::Min – ограничение снизу
CRangeLimits::Max – ограничение сверху
minМинимально допустимое значение
maxМаксимально допустимое значение
ПРЕДУПРЕЖДЕНИЕ

Параметр у шаблона CRangeLimits должен быть таким же как и у шаблона CNumericTypes.

Класс CStdStringValue

Назначение: представляет строковое значение (строка STL - std::string)

Конструктор:

CStdStringValue(std::string *pstrData, int nMaxLen, bool bAllowEmpty);

Параметры конструктора

pstrDataАдрес строки std::string, по которому будет записываться/читаться значение.
nMaxLenМаксимальная длина строки, если == -1, то длина не ограничена.
bAllowEmptyЕсли равен true, то допускаются пустые строки.

Класс CStringValue

Назначение: представляет строковое значение (стандартная C-строка с известной длиной)

Конструктор:

CStringValue(char *pchData, int nMaxLen, bool bAllowEmpty);

Параметры конструктора

pchDataАдрес строки char *, по которому будет записываться/читаться значение.
nMaxLenМаксимальная длина строки
bAllowEmptyЕсли равен true, то допускаются пустые строки.

Класс CRGBValue

Назначение: представляет цвет записанный в кодировке RGB

	 Грамматика
	 <part> ::= <digit>[<digit>][<digit>]
	 <sep> ::= <,><space>*
	 <space> ::= < >
	 <color> ::= <part><sep><part><sep><part> 

Конструктор:

CRGBValue(COLORREF *pColor);

Параметры конструктора:

pColorАдрес переменной типа COLORREF, по которому будет записываться/читаться значение

Класс CBoolValue

Конструктор:

CBoolValue(bool *pbData, TBoolStyle style = TrueFalse);

Параметры конструктора:

pbDataАдрес переменной типа bool, по которому будет записываться/читаться значение
styleОпределяет используемый стиль. Существует два стиля:
TrueFalse – при этом значение может быть “true” либо “false”
YesNo – при этом значение может быть “yes” либо “no”

Заключение

Автор был бы рад любым дополнениям, замечаниям, вопросам, присылайте их на адрес Yuri32@nm.ru.

Благодарности

Автор выражает благодарность всем участникам форумов RSDN, отвечавших на его вопросы.


Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.