Pull to refresh

Boost Property Tree и его парсер XML

Reading time 4 min
Views 47K
image

О чем эта статья


В статье рассказывается про библиотеку Property Tree Library, а именно:
  • Что такое Property Tree;
  • Примеры использования Property Tree;
  • Как конвертировать Property Tree в XML-код и обратно.



Property Tree


В Boost начиная с версии 1.41.1 появилась библиотека Property Tree. Эта библиотека предоставляет новый тип данных, древовидную структуру boost::propetry_tree::ptree.

ptree это обычная древовидная структура, каждый элемент которой, помимо данных, может содержать упорядоченный список дочерних элементов, каждый из которых имеет свое имя.

Структура выглядит так:
struct ptree
{
   data_type data;                         // Данные элемента
   list<pair<key_type, ptree>> children;   // упорядочненный список дочерних элементов
};


В основном в качестве key_type используется обычная строка.

Примеры использования Property Tree


Структура ptree очень удобна для чтения, записи, сохранения и загрузки древовидных данных.
Добавлять элементы в это дерево проще простого:
boost::property_tree::ptree heroTree;

heroTree.put("Name", "John");
heroTree.put("Exp", 150);
heroTree.put("Inventory.Weapon", "Blue Sword");
heroTree.put("Inventory.Money", 3000);


Поскольку ptree содержит связный список, то можно добавить несколько предметов, имеющих один ключ:
heroTree.put("Inventory.Item", "Stone");
heroTree.put("Inventory.Item", "Golden helmet");
heroTree.put("Inventory.Item", "Thomb key");


Желаете получить под-дерево? Тоже ничего сложного:
boost::property_tree::ptree inventoryTree = heroTree.get_child("Inventory");
inventoryTree.put("Item", "Shield of Honor");


Получать элементы тоже проще простого:
int exp = pt.get<int>("Exp");

std::string weapon = pt.get<std::string>("Inventory.Weapon");


Если элемент не найдет, выбрасывается исключение. Если вы этого не желаете, просто задайте вторым параметром значение, которое вы желаете видеть по умолчанию:
int exp = pt.get<int>("Exp", 0);


Если элементов ожидается несколько, то придется перебирать их:
BOOST_FOREACH(auto &v, inventoryTree)
{
	if (v.first == "Item")
	{
		std::cout << "Hero has item: " << v.second.get<std::string>("") << std::endl;
	}
}


XML-парсер


Самое замечательное в Property Tree Library это то, что в библиотеке содержатся встроенные парсеры XML и JSON. Парсеры не идеальные, но работают прямо «из коробки» — не нужно тянуть никаких дополнительных библиотек и зависимостей.
С JSON-парсером я не работал, но XML-парсер использую уже на полную катушку. О нем и напишу подробнее.

Для чтения xml и записи xml применяются, соответственно, методы read_xml и write_xml. Использование очень простое:

//XML-код для парсинга
std::string xmlCode = "<ButtonList>\
	<Button>B1</Button>\
	<Button>B2</Button>\
	</ButtonList>";

//Создаем поток
std::stringstream stream(xmlCode);

try
{
	boost::property_tree::ptree propertyTree;
	
	//Читаем XML
	boost::property_tree::read_xml(stream, propertyTree);
	
	//Читаем значения:
	BOOST_FOREACH(auto &v, propertyTree)
	{
		std::cout << "Button is " << v.second.get<std::string>("") << std::endl;
	}
	
	
	//Добавляем пару значений
	propertyTree.put("ButtonList.Button", "B3");
	propertyTree.put("ButtonList.Button", "B4");
	
	std::stringstream output_stream;
	
	//Записываем в другой поток
	boost::property_tree::write_xml(output_stream, propertyTree);
	
	//Получаем XML из потока
	std::string outputXmlCode = output_stream;

}
catch(boost::property_tree::xml_parser_error)
{
	
	std::cout<<"XML parser error!"<<std::endl;
	
	throw;
}


Если есть необходимость прочитать атрибут узла, то сделать это можно через псевдо-поддерево "<xmlattr>":

//XML-код для парсинга
std::string xmlCode = "<Data name="Position" x="5" y="5"/>";

//... парсим XML

//Получаем значения
std::string name = propertyTree.get<std::string>("Data.<xmlattr>.name");
int x = propertyTree.get<std::string>("Data.<xmlattr>.x");
int y = propertyTree.get<std::string>("Data.<xmlattr>.y");


C псевдо-поддеревом "<xmlattr>" связаны небольшие грабли, на которые можно нечаянно наступить. Допустим, как в вышеописанном примере, у вас есть элемент ButtonList в котором есть 4 элемента Button — это элементы, характеризующие кнопки.
Если у элемента ButtonList не указаны атрибуты, то в созданном на основе XML дереве будет 4 поддерева с кнопками, как и положено. Если же у ButtonList указаны какие-нибудь атрибуты, то — сюрприз! — к 4 поддеревьям с кнопками добавляется еще одно поддерево, с ключом "<xmlattr>", которое, очевидно, к кнопкам отношения не имеет.
Если мы будем перебирать прямо все поддеревья, то вместе с кнопками мы попробуем обработать "<xmlattr>", а это вызовет ошибку.
Следовательно, при переборе дочерних деревьев придется делать дополнительную проверку, которая исключит <xmlattr> из списка перебора. Например, так:

BOOST_FOREACH(auto &v, propertyTree)
{
	if (v.first == "Button") //Вводим дополнительную проверку
	{
		std::cout << "Button is " << v.second.get<std::string>("") << std::endl;
	}
} 


Прочее


Помимо XML-парсера, Property Tree Library содержит также JSON-, INI- и INFO-парсеры. С ними я еще не разбирался, но предполагаю, что там все примерно то же самое.

Список использованной литературы:


www.boost.org/doc/libs/1_52_0/doc/html/property_tree.html
Tags:
Hubs:
+10
Comments 5
Comments Comments 5

Articles