Зачастую необходимо вынести некоторые относительно постоянные настройки программы из хардкода (жестко зафиксированных в коде констант) куда-то во вне, делать какую-то выдачу, пересылать текстовую информацию и даже устраивать мини базу данных. Для этого годится текстовый формат XML.
Муки выбора
Писать велосипедный парсер - сложно новичкам, затратно по времени умелым, проще воспользоваться проверенной внешней библиотекой. Сейчас популярны две разновидности парсеров: работающих на основе DOM (document object model) и поточных, так называемых SAX-парсеров (Simple API for XML). SAX - стандарт парсеров, пришедший из Java API. Часто представляет собой базовый класс с набором виртуальных методов, которые вызываются по мере просмотра xml-файла (встретил открывающий тег - вызвал определенный метод, встретил аттрибут - вызвал другой метод, встретил закрывающий тег - вызвал третий, и т.д.). Этот класс можно расширить (унаследоваться), заменив необходимые методы (в базовом классе они ничего не делают) на что-то значимое в рамках нашей программы. Парсеры на основе DOM работают по принципу, близкому к доступу к DOM html-документа в javascript: имея ссылку на некоторый объект DOM, мы можем обратиться к его потомкам/родителю/соседним объектам. Безусловно, этот подход удобнее, чем в SAX, однако, требует загрузить в память весь файл, что бывает сложно, если он становится крупным объемом. SAX-парсер всего-навсего проходит вдоль всего xml, храня в памяти только текущий элемент, позволяя обрабатывать xml-файлы, ограниченные только правилами ОС.
TinyXML
Сейчас я легко задену один популярный XML-DOM-парсер TinyXML. Там же можно найти ссылку на SourceForge для скачивания исходников. Распространяется библиотека в виде исходных кодов. Для Windows, например, в виде тестового проекта под MSVS2010. Оттуда с корнем можно забрать 6 файлов (2 заголовочных (.h) и 4 исходных(.brush: cpp), все, кроме xmltest.brush: cpp) и просто добавить в свой проект и приинклудить tinyxml.h где необходимо.
Также внутри архива с библиотекой лежит неплохой справочник по классам, однако, туториал, на мой взгляд, там просто ужасен! =) Собственно, потому решил и писать данный Quick Guide.
К станку!
Для примера, пусть есть записная книжка с названием и набором записей, у записей есть название, сам текст записи и набор тегов, у тегов есть имя. У нас есть классы:
// Будем считать, что эти заголовки тоже включаем
#include <string> // Ради строк std::string
#include <vector> // Ради массивов std::vector
#include <memory> // Ради умных указателей std::shared_ptr и std::make_shared()
struct Note {
std::string title;
std::string text;
std::vector<std::string> tags;
};
struct Notebook {
std::string title;
std::vector<Note> notes;
};
Пусть это struct, чтобы не захламлять код методами доступа. У нас есть объект Notebook ntb. Напоминаю, что метод std::string::c_str() возвращаяет const char*, необходимый библиотеке. Нам нужно сгенерить и считать такой файл:
<?xml version="1.0" encoding="UTF-8"/>
<Notebook title="Name of notebook">
<Note title="Title of note" text="Text of note">
<Tag name="tagName"/>
<Tag name="tagName2"/>
</Note>
<Note title="Title of note2" text="SomeText">
<Tag name="tagName"/>
</Note>
</Notebook>
Сначала нужно создать объект документа и добавить строчку декларации:
TiXmlDocument doc;
TiXmlDeclaration *decl = new TiXmlDeclaration("1.0", "UTF-8", "");
doc.LinkEndChild(decl);
Да! Как и в Qt, здесь дочерние объекты создаются динамически и удаляются автоматически (delete не нужен здесь и даже опасен!). Теперь нам нужно добавить элемент Notebook, заполнить аттрибут title.
auto *notebookItem = new TiXmlElement("Notebook"); // Создаем элементdoc.LinkEndChild(notebookItem); // Цепляем его к документу в конец
notebookItem->SetAttribute("title", ntb.title.c_str()); // Назначаем аттрибут
Теперь нужен цикл для добавления отдельных записей:
for (int i=0, sizeOfNotesArray=ntb.notes.size(); i<sizeOfNotesArray; ++i) {
auto *noteItem = new TiXmlElement("Note");
notebookItem->LinkEndChild(noteItem); // Цепляем теперь уже к элементу записной книжки!
noteItem->SetAttribute("title", ntb.notes[i].text.c_str());
noteItem->SetAttribute("text", ntb.notes[i].text.c_str());
...
Отлично! Однако, внутри еще и теги, сделаем цикл и для них!
for (int j=0, sizeOfTagsArray=ntb.notes.size(); j<sizeOfTagsArray; ++j) {
auto *tagItem = new TiXmlElement("Tag");
noteItem->LinkEndChild(tagItem);
tagItem->SetAttribute("name", ntb.notes[i].tags[j].c_str());
}
Теперь нужно записать это либо в файл командой:
doc.SaveFile ("filepath");
или сохранить документ в строку, чтобы делать с ним ужасы:
TiXmlPrinter printer;
doc.Accept(&printer);
std::string result = printer.CStr();
Все, готово, вы великолепны! Итого:
std::shared_ptr<std::string> writeToXML( Notebook ntb ){
TiXmlDocument doc;
TiXmlDeclaration *decl = new TiXmlDeclaration("1.0", "UTF-8", "");
doc.LinkEndChild(decl);
// Adding notebook element
auto *notebookItem = new TiXmlElement("Notebook"); // Создаем элемент
doc.LinkEndChild(notebookItem); // Цепляем его к документу в конец
notebookItem->SetAttribute("title", ntb.title.c_str()); // Назначаем аттрибут
// Adding notes elements
for (int i=0, sizeOfNotesArray=ntb.notes.size(); i < sizeOfNotesArray; ++i) {
auto *noteItem = new TiXmlElement("Note");
notebookItem->LinkEndChild(noteItem); // Цепляем теперь уже к элементу записной книжки!
noteItem->SetAttribute("title", ntb.notes[i].text.c_str());
noteItem->SetAttribute("text", ntb.notes[i].text.c_str());
// Adding tags elements
for (int j=0, sizeOfTagsArray=ntb.notes.tags.size(); j < sizeOfTagsArray; ++j) {
auto *tagItem = new TiXmlElement("Tag");
noteItem->LinkEndChild(tagItem);
tagItem->SetAttribute("name", ntb.notes[i].tags[j].c_str());
}
}
TiXmlPrinter printer;
doc.Accept(&printer);
std::shared_ptr<std::string> result = std::make_shared < std::string > (printer.CStr());
return result;
}
Теперь аналогично запись. Предлагаю попробовать переделать пример, учитывая, что Notebook может быть несколько в документе и функция возвращает, например, std::vector.
Notebook readFromXML( const std::string& xmlString ){
// Пытаемся обработать документ
TiXmlDocument doc;
doc.Parse(xmlString.c_str(), 0, TIXML_ENCODING_UTF8);
// Обработка ошибок. Например, XML не валиден
if ( doc.Error() )
return 0;
// Считываем первый элемент документа (Notebook)
TiXmlElement *notebookItem = doc.FirstChildElement();
Notebook resultNotebook;
resultNotebook.title = notebookItem->Attribute("title"));
// Читаем записи
for (TiXmlElement *noteItem = notebookItem->FirstChildElement();
noteItem != nullptr;
noteItem=noteItem->NextSiblingElement("Note"))
{
// Читаем заголовок и текст записи из аттрибутов
Note newNote { noteItem->Attribute("title"), noteItem->Attribute("text") };
// Читаем теги
for (TiXmlElement *tag = noteItem->FirstChildElement();
tag != nullptr;
tag=tag->NextSiblingElement("Tag"))
{
newNote.tags.push_back(tag->Attribute("name"));
}
// Добавляем очередную запись в массив
resultNotebook.notes.push_back(newNote);
}
return resultNotebook;
}
Комментариев нет:
Отправить комментарий