65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.40/5 (6投票s)

2016年1月29日

CPOL

6分钟阅读

viewsIcon

25541

downloadIcon

214

intros_ptree:一个库,允许您自动将结构或类填充到 XML 文件(或 json 或 ini 文件)中,反之亦然。

引言

现代 C++ 是 C++ 编程语言的一项变革性技术。我们可以用它做一些非常酷的事情。在这里,我将向您介绍其中一项。使用 boost property tree 库和一些现代 C++ 技术,我将向您展示如何(几乎)自动地将 C++ 结构或类对象填充到 xml、json 或 ini 文件中,反之亦然。

这是一个分两部分的系列文章。

  1. 在本部分中,我将向您展示如何使用 intros_ptree 库。
  2. 而在第二部分(链接),我们将详细介绍该库的工作原理。

即使您不关心 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 支持。

  1. 结构名称和 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。第二个参数是您想为类型指定的新名称。

  2. 项名称和 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

  3. 多个元素

    这次,我们的 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)

    要点:您可以像使用其他类型一样轻松地使用容器。

  4. 为 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 元素下)。在这里,第一个参数是您想为变量指定的名称,第二个参数是作用域名称。第三个参数我们将在下一个示例中看到。

  5. 使用 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 的第三个参数将该项标记为属性。

  6. 在另一个类型中使用类型

    我们可以为一个在其内部使用另一种类型的类型获得 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 支持。

其他说明

  1. C 风格数组(例如 int a[10])不支持作为 intros_item
  2. intros 定义必须在全局作用域内。
  3. 我们不能为一种类型提供两个 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++ 实现这一点的。

© . All rights reserved.