Исходные коды
Демонстрационный проект
При разработке одной из программ я столкнулся с совершенно типовой проблемой: было необходимо использовать 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 нет необходимости.
В данном разделе приведено достаточно подробное описание классов, составляющих данный компонент.
ПРИМЕЧАНИЕ Обратите внимание, что все классы компонента имеют виртуальный деструктор, что дает вам возможность наращивать функциональность компонента. |
Назначение: предоставляет основную функциональность по работе с 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); | Читает данные из потока в связанные переменные, в случае ошибок выбрасывает исключение. |
Назначение: представляет секцию ini-файла
Методы:
void AddParameter(const char *pchKey, CCommonValue *pValue, bool bMandatory = false); | Добавляет параметр в секцию. pchKey – константный указатель на строку, содержащую имя ключа pValue – указатель на объект значения bMandatory – определяет обязательность параметра (true – обязательный, false – нет). Если при разборе файла обязательный параметр не найден, возбуждается исключение. Объект, на который указывает pValue будет удален в деструкторе класса CSection. |
---|
Назначение: представляет параметр
Методы:
const std::string &GetKey() const; | Возвращает ключ |
---|---|
const CCommonValue *GetValue() const; | Возвращает константный указатель на объект значения |
std::string ToString() const throw (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().
Назначение: представляет абстрактный тип данных. Является базовым классом для всех классов, представляющих какие-либо типы данных.
Методы:
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, если в связанной переменной находится корректное (удовлетворяющее ограничениям) значение |
Назначение: представляет значения числовых типов (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(ELimits lim, T min, T max) |
Параметры конструктора:
lim | Вид ограничения, может принимать следующие значения: CRangeLimits::MinMax – ограничение сверху и снизу CRangeLimits::Min – ограничение снизу CRangeLimits::Max – ограничение сверху |
---|---|
min | Минимально допустимое значение |
max | Максимально допустимое значение |
ПРЕДУПРЕЖДЕНИЕ Параметр у шаблона CRangeLimits должен быть таким же как и у шаблона CNumericTypes. |
Назначение: представляет строковое значение (строка STL - std::string)
Конструктор:
CStdStringValue(std::string *pstrData, int nMaxLen, bool bAllowEmpty); |
Параметры конструктора
pstrData | Адрес строки std::string, по которому будет записываться/читаться значение. |
---|---|
nMaxLen | Максимальная длина строки, если == -1, то длина не ограничена. |
bAllowEmpty | Если равен true, то допускаются пустые строки. |
Назначение: представляет строковое значение (стандартная C-строка с известной длиной)
Конструктор:
CStringValue(char *pchData, int nMaxLen, bool bAllowEmpty); |
Параметры конструктора
pchData | Адрес строки char *, по которому будет записываться/читаться значение. |
---|---|
nMaxLen | Максимальная длина строки |
bAllowEmpty | Если равен true, то допускаются пустые строки. |
Назначение: представляет цвет записанный в кодировке RGB
Грамматика <part> ::= <digit>[<digit>][<digit>] <sep> ::= <,><space>* <space> ::= < > <color> ::= <part><sep><part><sep><part> |
Конструктор:
CRGBValue(COLORREF *pColor); |
Параметры конструктора:
pColor | Адрес переменной типа COLORREF, по которому будет записываться/читаться значение |
---|
Конструктор:
CBoolValue(bool *pbData, TBoolStyle style = TrueFalse);
|
Параметры конструктора:
pbData | Адрес переменной типа bool, по которому будет записываться/читаться значение |
---|---|
style | Определяет используемый стиль. Существует два стиля: TrueFalse – при этом значение может быть “true” либо “false” YesNo – при этом значение может быть “yes” либо “no” |
Автор был бы рад любым дополнениям, замечаниям, вопросам, присылайте их на адрес Yuri32@nm.ru.
Автор выражает благодарность всем участникам форумов RSDN, отвечавших на его вопросы.