使用 XML 自动填充结构(第 1 部分,共 2 部分)






3.40/5 (6投票s)
intros_ptree:一个库,允许您自动将结构或类填充到 XML 文件(或 json 或 ini 文件)中,反之亦然。
引言
现代 C++ 是 C++ 编程语言的一项变革性技术。我们可以用它做一些非常酷的事情。在这里,我将向您介绍其中一项。使用 boost property tree 库和一些现代 C++ 技术,我将向您展示如何(几乎)自动地将 C++ 结构或类对象填充到 xml、json 或 ini 文件中,反之亦然。
这是一个分两部分的系列文章。
- 在本部分中,我将向您展示如何使用
intros_ptree
库。 - 而在第二部分(链接),我们将详细介绍该库的工作原理。
即使您不关心 xml 文件,通过观看现代 C++ 取得的酷炫成果,您仍然可以享受这篇文章。
文章附带了 intros_ptree
库,以及如何使用的示例。
必备组件
编译器:我们需要一个现代 C++ 编译器来使用此库。我已经使用 VS2015 update 1、GCC 5.2 和 clang 3.6 测试了此代码。
第三方库:Boost。我在开发此库时使用了 boost 1.60。
开始之前
这是一个小型 C++ 仅标头库。 您可以在此处获取最新版本。
您需要添加以下内容来编译本文中提供的示例。
#include <iostream>
#include <intros_ptree.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
using namespace std;
using namespace utils::intros_ptree;
using namespace boost::property_tree;
本文中的所有示例都包含在 utils_examples.cpp 文件中。
在示例中,我将只引用 xml 文件,但对于 json 和 ini 文件也同样适用。
Using the Code
快速入门
Boost property tree 库可以打开 xml、json 或 ini 文件并将属性填充到 property tree。这是我们如何从 XML 文件加载 property tree
的方法。
ptree tree;
read_xml(filename, tree);
这是我们将 tree
写入 XML 文件的方法。
write_xml(filename, tree);
通过使用 intros_ptree
库,我们可以将结构填充到 tree
中,反之亦然。
首先,让我们创建一个我们希望从 tree
中填充的结构。
struct test
{
int x;
string y;
;
现在,我们需要定义一个内部数据结构供库使用(“几乎”的部分)。
BEGIN_INTROS_TYPE(test)
ADD_INTROS_ITEM(x)
ADD_INTROS_ITEM(y)
END_INTROS_TYPE(test)
现在,要从 tree 填充测试对象,我们只需执行以下操作:
auto a = make_intros_object<test>(tree);
并且,要将 property tree 从对象填充:
ptree tree = make_ptree(a);
就是这样。
现在,让我们用一个具体的例子来总结一下。
这里是一个示例数据。
string sample_xml = R"(
<book>
<id>102</id>
<name>i am a book</name>
<author>i am a writer</author>
</book>
)";
这里,我们定义了用于存储数据的结构。
struct book
{
int id;
string name;
string author;
};
我们需要为库声明一些内部数据结构。
BEGIN_INTROS_TYPE(book)
ADD_INTROS_ITEM(id)
ADD_INTROS_ITEM(name)
ADD_INTROS_ITEM(author)
END_INTROS_TYPE(book)
现在,要填充 book
对象,我们只需执行以下操作:
ptree tree;
stringstream ss(sample_xml);
read_xml(ss, tree);
auto ob = make_intros_object<book>(tree);
并且,要将数据写入 XML 文件,我们只需执行以下操作:
ob.name = "new name"; // lets make some modification before writing
auto tree2 = make_ptree(ob);
xml_writer_settings<string> settings(' ', 4); // this is needed for xml printing
// to have proper whitespace
write_xml(cout, tree2, settings);
描述
intros_ptree
库提供了以下工具供我们使用。
template<typename T>
boost::property_tree::ptree make_ptree(const T& in);
此函数将接受任何已定义 intros
结构的结构或类。它将返回该结构的 ptree
。
template<typename T>
T make_intros_object(const boost::property_tree::ptree& tree);
此函数将接受一个 ptree
,并创建一个类型为 T
的结构或类,前提是已为类型 T
定义了 intros
结构。
因此,我们看到,这些函数允许我们获取一个已定义 intros
结构的类型的 ptree
,反之亦然。
但要做到这一点,我们需要定义 intros
结构。宏的作用就在于此。让我们看看它们能做什么。
BEGIN_INTROS_TYPE(type)
使用此宏,您可以开始为您的类型定义 intros
结构。
END_INTROS_TYPE(type)
使用此宏,您可以结束您类型的声明。
ADD_INTROS_ITEM(x)
使用此宏,您可以将您类型的一个项添加到 intros
结构中。
BEGIN_INTROS_TYPE_USER_NAME(type, name)
当您需要自己设置名称而不是使用默认名称时,将使用此宏代替 BEGIN_INTROS_TYPE
。默认名称是您的类型名称。
ADD_INTROS_ITEM_USER_NAME(x, name)
与 BEGIN_INTROS_TYPE_USER_NAME
类似,当您需要使用与默认用户名称不同的用户名称时,将使用 ADD_INTROS_ITEM_USER_NAME
代替 ADD_INTROS_ITEM
。默认用户名称是您的变量名称。
MAKE_USER_NAME(name, scope, is_attribute)
您将此宏用作 ADD_INTROS_ITEM_USER_NAME
的第二个参数。当您想说明该项在不同的作用域内,或者想将您的项标记为 xml 属性时,您将需要它。我们将在接下来的示例中进一步了解它们的作用。
更多示例
一旦我们为我们的类型添加了 intros
支持,从 xml 文件填充它或将其保存到 xml 文件就变得微不足道了。这是我们必须这样做的方式。
// here is how we populate a structure
ptree tree;
read_xml(xml_file_name, tree);
auto ob = make_intros_object<MyStruct>(tree);
// here is how we write the data to xml file
auto tree2 = make_ptree(ob);
xml_writer_settings<string> settings(' ', 4);
write_xml(xml_file_name, tree2, settings);
有趣的部分在于我们如何为我们的类型添加 intros
支持。在以下示例中,我们将看到如何在不同场景下添加 intros
支持。
- 结构名称和 xml 标签不同
假设,我们的 xml 数据如下所示:
string sample_xml = R"( <root> <id>102</id> <name>i am a book</name> <author>i am a writer</author> </root> )";
而我们的结构是这样的:
struct book { int id; string name; string author; };
xml 标签显示为
root
,但结构名称为book
。然后我们需要如下定义intros
结构:BEGIN_INTROS_TYPE_USER_NAME(book, "root") ADD_INTROS_ITEM(id) ADD_INTROS_ITEM(name) ADD_INTROS_ITEM(author) END_INTROS_TYPE(book)
要点:当您需要使用不同于类型名称的名称时,请使用
BEGIN_INTROS_TYPE_USER_NAME
。第二个参数是您想为类型指定的新名称。 - 项名称和 xml 标签不同
这次,我们的 xml 结构如下所示:
string sample_xml = R"( <book> <book_id>102</book_id> <name>i am a book</name> <author>i am a writer</author> </book> )";
而结构如下:
struct book { int id; string name; string author; };
在这里,我们需要将 xml 标签
book_id
映射到book::id
。这是我们这样做的原因:BEGIN_INTROS_TYPE(book) ADD_INTROS_ITEM_USER_NAME(id, "book_id") ADD_INTROS_ITEM(name) ADD_INTROS_ITEM(author) END_INTROS_TYPE(book)
要点:当您想为变量使用不同于变量名称的名称时,请使用
ADD_INTROS_ITEM_USER_NAME
。 - 多个元素
这次,我们的 xml 结构如下所示:
string sample_xml = R"( <book> <book_id>102</book> <name>i am a book</name> <author>i am writer 1</author> <author>i am writer 2</author> <author>i am writer 3</author> </book> )";
而我们的结构是这样的:
struct book { int id; string name; vector<string> author; };
为此,我们将如下定义
intros
结构:BEGIN_INTROS_TYPE(book) ADD_INTROS_ITEM_USER_NAME(id, "book_id") ADD_INTROS_ITEM(name) ADD_INTROS_ITEM(author) END_INTROS_TYPE(book)
要点:您可以像使用其他类型一样轻松地使用容器。
- 为 xml 标签添加作用域
这次,让我们看一下,我们的 xml 数据如下:
string sample_xml = R"( <book> <book_id>102</book> <name>i am a book</name> <all_authors> <author>i am writer 1</author> <author>i am writer 2</author> <author>i am writer 3</author> </all_authors> </book> )";
在这里,
author
元素被添加到一个新标签all_authors
下。为了支持这一点,我们需要如下定义 intros 结构:BEGIN_INTROS_TYPE(book) ADD_INTROS_ITEM_USER_NAME(id, "book_id") ADD_INTROS_ITEM(name) ADD_INTROS_ITEM_USER_NAME(author, MAKE_USER_NAME("author", "all_authors", false)) END_INTROS_TYPE(book)
要点:您可以通过
MAKE_USER_NAME
将一个项放入另一个作用域(在一个新的 xml 元素下)。在这里,第一个参数是您想为变量指定的名称,第二个参数是作用域名称。第三个参数我们将在下一个示例中看到。 - 使用 xml 属性
这次,我们的 xml 数据是这样的:
string sample_xml = R"( <book book_id="102"> <name>i am a book</name> <author>i am a writer</author> </book> )";
而结构是这样的:
struct book { int id; string name; string author; };
这次,我们需要如下定义
intros
结构:BEGIN_INTROS_TYPE(book) ADD_INTROS_ITEM_USER_NAME(id, MAKE_USER_NAME("book_id", "", true)) ADD_INTROS_ITEM(name) ADD_INTROS_ITEM(author) END_INTROS_TYPE(book)
要点:
MAKE_USER_NAME
的第三个参数将该项标记为属性。 - 在另一个类型中使用类型
我们可以为一个在其内部使用另一种类型的类型获得
intros
支持,前提是这两种类型都具有intros
支持。这次,我们的 xml 数据如下所示:
string sample_xml = R"( <catalog> <name>I am the catalog</name> <book> <id>102</id> <name>i am a book</name> <author>i am writer 1</author> </book> <book> <id>103</id> <name>i am also book</name> <author>i am writer 2</author> </book> <book> <id>104</id> <name>i am another book</name> <author>i am writer 3</author> </book> </catalog> )";
现在,我们的结构是这样的:
struct book { int id; string name; string author; }; struct catalog { string name; vector<book> books; };
book
的 Intros 定义与以前一样。BEGIN_INTROS_TYPE(book) ADD_INTROS_ITEM(id) ADD_INTROS_ITEM(name) ADD_INTROS_ITEM(author) END_INTROS_TYPE(book)
并且 catalog 的
intros
定义也应该符合我们的预期。BEGIN_INTROS_TYPE(catalog) ADD_INTROS_ITEM(name) ADD_INTROS_ITEM_USER_NAME(books, "book") END_INTROS_TYPE(catalog)
要点:对于
intros_ptree
,我们可以将一种类型用于另一种类型,只要这两种类型都具有intros
支持。
其他说明
- C 风格数组(例如
int a[10]
)不支持作为intros_item
。 intros
定义必须在全局作用域内。- 我们不能为一种类型提供两个 intros 定义。也就是说,对于类型
T
,我们只能提供一个BEGIN_INTROS_TYPE
/END_INTROS_TYPE
块。
调试技巧
从上面的示例可以看出,定义 intros struct
是非常直观的。随时,如果您不确定您定义的 intros struct
是否正确,您可以打印它并查看结果。让我用上面 catalog 结构的例子给您看。
catalog c;
c.books.resize(2);
ptree tree = make_ptree(c);
xml_writer_settings<string> settings(' ', 4); // this is needed for xml printing
// to have proper whitespace
write_xml(cout, tree, settings);
这将打印:
<?xml version="1.0" encoding="utf-8"?>
<catalog>
<name/>
<books>
<id>0</id>
<name/>
<author/>
</books>
<books>
<id>0</id>
<name/>
<author/>
</books>
</catalog>
这样,我们可以检查,xml 格式是否符合我们的喜好。
第一部分到此结束。在下一部分中,我将向您展示 intros_ptree
库是如何使用现代 C++ 实现这一点的。