【问题标题】:Storing a nested XML structure in form of BGL graph using BOOST library使用 BOOST 库以 BGL 图的形式存储嵌套的 XML 结构
【发布时间】:2021-05-24 15:39:35
【问题描述】:

我正在尝试存储嵌套的 XML 文件:

<?xml version="1.0" encoding="utf-8"?>
<root>
<type>
<cars>
<car name="Garfield" weight="4Kg">
<spec serial_="e_54" source ="petrol" mileage="56"/>
<spec serial_="e_52" source="diesel" mileage="52"/>
<spec serial_="m_22" source="electric" mileage="51"/>
<additions source="steam" convert="153 0 1 0"/>
</car>
<car name="Awesome" weight="3Kg">
<spec serial_="t_54" source="petrol" mileage="16"/>
<spec serial_="t_52" source="wind" mileage="62"/>
<spec serial_="t_22" source="electric" mileage="81"/>
<additions source="water" convert="123 1 1 0"/>
</car>
</cars>
<planes>
<plane id="231" name ="abc">
<utilities serial_="e456" part="567"/>
</plane>
</type>
</root>

在 boost 库的无向图中。根据 XML,我希望使每个“汽车”和“飞机”成为一个节点,同时将属性存储为节点的数据成员。接下来,它们的子节点,即“spec”、“additions”和“utilities”将连同它们的属性一起以边的形式存储。

代码结构如下:

#include <boost/property_tree/xml_parser.hpp>
using boost::property_tree::ptree;
#include <iostream>

struct Car {
std::string name, weight;
struct Spec {
    std::string serial_, source;
    double mileage;
};
std::vector<Spec> specs;
};
//Note: struct identical to struct car have been devised to store planes and additions 

static bool parse(ptree const& node, Car::Spec& into) {
into.serial_ = node.get<std::string>("<xmlattr>.serial_");
into.source  = node.get<std::string>("<xmlattr>.source");
into.mileage = node.get<double>("<xmlattr>.mileage");
return true;
}

static bool parse(ptree const& node, Car& into)  {
into.name   = node.get<std::string>("<xmlattr>.name");
into.weight = node.get<std::string>("<xmlattr>.weight");
for (auto& [name, child] : node) {
    if (name == "spec") {
        into.specs.emplace_back();
        if (!parse(child, into.specs.back())) {
            return false;
        }
    }
}
return true;
}

static std::ostream& operator<<(std::ostream& os, Car const& car) {
os << "Name: " << car.name << ", Weight: " << car.weight;
for (auto& spec : car.specs) {
    os << "\n -- [" << spec.serial_ << "; " << spec.source << "; "
       << spec.mileage << "]";
}
return os;
}

int main() 
{
boost::property_tree::ptree pt;
{
    std::ifstream ifs("input.xml");
    read_xml(ifs, pt);
}

for (auto& [key, node] : pt.get_child("root.type.cars")) {
    if ("car" == key) {
        Car car;
        parse(node, car);
        std::cout << car << "\n";
    }
}
}

我希望摆脱结构并在原地使用一个类来存储并形成所描绘的 BGL 图。

感谢所有帮助。 谢谢!!

【问题讨论】:

  • “就地上课”——那会是什么样子?我会说你有那个。从字面上看,代码和 XML 中的 nothing 都指的是图形,更不用说 BGL。您能否告诉我们您尝试实现的目标,而不是解雇 "homework style" questions 只是稍后告诉我们那不是您需要的。
  • 如果这有帮助:en.cppreference.com/book/intro/….
  • 需要一个类来存储节点形式的汽车和飞机。我正在尝试以 BGL 图的形式存储它们,因为我需要实现一些算法(例如最短路径等)。此外,一些额外的数据也以图形的形式存在,所以我不能以 property_tree 的形式拥有这个。
  • 你想知道从一辆车到飞机的最短路径吗?
  • “所以我不能以 property_tree 的形式拥有这个。” 是那个开始的。 从未发布过您自己的任何代码(此代码不是您自己的)。首先显示您拥有的图表。将其解析为节点属性是微不足道的,但我无法开始向您展示,因为信息太少。我不会,因为我从给出其他答案中学到了

标签: c++ boost xml-parsing boost-graph boost-propertytree


【解决方案1】:

好的,因为没有很多其他 流量,让我们这样做。

首先,让我们fix the input 这样它实际上是 XML:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <type>
    <cars>
      <car name="Garfield" weight="4Kg">
        <spec serial_="e_54" source ="petrol" mileage="56"/>
        <spec serial_="e_52" source="diesel" mileage="52"/>
        <spec serial_="m_22" source="electric" mileage="51"/>
        <additions source="steam" convert="153 0 1 0"/>
      </car>
      <car name="Awesome" weight="3Kg">
        <spec serial_="t_54" source="petrol" mileage="16"/>
        <spec serial_="t_52" source="wind" mileage="62"/>
        <spec serial_="t_22" source="electric" mileage="81"/>
        <additions source="water" convert="123 1 1 0"/>
      </car>
    </cars>
    <planes>
      <plane id="231" name ="abc">
        <utilities serial_="e456" part="567"/>
      </plane>
    </planes>
  </type>
</root>

现在,让我们为平面添加解析:

struct Plane {
    Id id;
    std::string name;
    struct Utilities {
        std::string serial_, part;
    };
    Utilities utilities;
};

static bool parse(ptree const& node, Plane::Utilities& into) {
    into.serial_ = node.get<std::string>("<xmlattr>.serial_");
    into.part    = node.get<std::string>("<xmlattr>.part");
    return true;
}

static bool parse(ptree const& node, Plane& into)  {
    into.id   = node.get<Id>("<xmlattr>.id");
    into.name = node.get<std::string>("<xmlattr>.name");
    if (auto child = node.get_child_optional("utilities")) {
        return parse(*child, into.utilities);
    }
    return true;
}

到目前为止,没有什么新鲜事。好吧,我们可以将additions 添加到汽车:

struct Additions {
    std::string source;
    std::vector<double> convert;
};
Additions additions;

你可以使用类似的东西来解析

static bool parse(ptree const& node, Car::Additions& into) {
    into.source = node.get<std::string>("<xmlattr>.source");
    auto values = node.get<std::string>("<xmlattr>.convert");

    if (!x3::parse(
            values.begin(), values.end(),
            x3::skip(x3::space) [*x3::double_],
            into.convert))
        return false;
    return true;
}

制作图表

您可能希望将结构附加到您的图表中,而不是“神奇地”没有结构但仍有数据(如何?):

using VertexBundle = boost::variant<Car, Plane>;
using EdgeBundle = std::string;

using Graph = boost::adjacency_list<
    boost::vecS, boost::vecS, boost::directedS,
    VertexBundle, EdgeBundle>;

顶点

好了,现在让我们从 XML 中解析这些顶点:

Graph g;

auto parse_vehicles = [&pt,&g]<typename Type>(auto path, auto key) {
    for (auto& [k, node] : pt.get_child(path)) {
        if (k == key) {
            Type vehicle;
            parse(node, vehicle);

            add_vertex(vehicle, g);
        }
    }
};

parse_vehicles.operator()<Car>("root.type.cars", "car");
parse_vehicles.operator()<Plane>("root.type.planes", "plane");

请注意解析循环已经是多么的好和通用。

边缘

您的问题中没有任何内容表明我们如何获得任何边缘信息,所以我们只是为了演示目的而编造一些东西:

// TODO add edges, but there's no information on how to
add_edge(vertex(0, g), vertex(2, g), "This demo edge has no properties", g);
add_edge(vertex(2, g), vertex(1, g), "One more", g);

现在您可以像以前一样打印整个内容了:

for (Vertex v : boost::make_iterator_range(vertices(g))) {
    std::cout << g[v] << "\n";
}

打印Live On Coliru

Name: Garfield, Weight: 4Kg
 -- [e_54; petrol; 56]
 -- [e_52; diesel; 52]
 -- [m_22; electric; 51]
 -- additions [steam; 153/0/1/0]
Name: Awesome, Weight: 3Kg
 -- [t_54; petrol; 16]
 -- [t_52; wind; 62]
 -- [t_22; electric; 81]
 -- additions [water; 123/1/1/0]
Id: 231, Name: abc
 -- utilities [e456; 567]

作为奖励,让我们包含一个 DOT 图输出:

完整的现场演示

Live On Coliru

#include <boost/property_tree/xml_parser.hpp>
#include <boost/variant.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphviz.hpp>
#include <boost/spirit/home/x3.hpp>
namespace x3 = boost::spirit::x3;
using boost::property_tree::ptree;
#include <iostream>

using Id = std::uint32_t;

struct Car {
    std::string name, weight;
    struct Spec {
        std::string serial_, source;
        double mileage;
    };
    struct Additions {
        std::string source;
        std::vector<double> convert;
    };
    std::vector<Spec> specs;
    Additions additions;
};

struct Plane {
    Id id;
    std::string name;
    struct Utilities {
        std::string serial_, part;
    };
    Utilities utilities;
};

static bool parse(ptree const& node, Car::Additions& into) {
    into.source = node.get<std::string>("<xmlattr>.source");
    auto values = node.get<std::string>("<xmlattr>.convert");

    if (!x3::parse(
            values.begin(), values.end(),
            x3::skip(x3::space) [*x3::double_],
            into.convert))
        return false;
    return true;
}

static bool parse(ptree const& node, Car::Spec& into) {
    into.serial_ = node.get<std::string>("<xmlattr>.serial_");
    into.source  = node.get<std::string>("<xmlattr>.source");
    into.mileage = node.get<double>("<xmlattr>.mileage");
    into.mileage = node.get<double>("<xmlattr>.mileage");
    return true;
}

static bool parse(ptree const& node, Car& into)  {
    into.name   = node.get<std::string>("<xmlattr>.name");
    into.weight = node.get<std::string>("<xmlattr>.weight");
    for (auto& [name, child] : node) {
        if (name == "spec") {
            into.specs.emplace_back();
            if (!parse(child, into.specs.back())) {
                return false;
            }
        }
    }
    if (auto child = node.get_child_optional("additions")) {
        return parse(*child, into.additions);
    }
    return true;
}

static bool parse(ptree const& node, Plane::Utilities& into) {
    into.serial_ = node.get<std::string>("<xmlattr>.serial_");
    into.part    = node.get<std::string>("<xmlattr>.part");
    return true;
}

static bool parse(ptree const& node, Plane& into)  {
    into.id   = node.get<Id>("<xmlattr>.id");
    into.name = node.get<std::string>("<xmlattr>.name");
    if (auto child = node.get_child_optional("utilities")) {
        return parse(*child, into.utilities);
    }
    return true;
}

static std::ostream& operator<<(std::ostream& os, Car const& car) {
    os << "Name: " << car.name << ", Weight: " << car.weight;
    for (auto& spec : car.specs) {
        os << "\n -- [" << spec.serial_ << "; " << spec.source << "; "
           << spec.mileage << "]";
    }
    auto& a = car.additions;
    if (!(a.source.empty() && a.convert.empty())) {
        os << "\n -- additions [" << a.source << ";";
        auto sep = ' ';
        for (auto d : a.convert) {
            os << std::exchange(sep, '/') << d;
        }
        os << "]";
    }
    return os;
}

static std::ostream& operator<<(std::ostream& os, Plane const& plane) {
    os << "Id: " << plane.id << ", Name: " << plane.name;
    auto& u = plane.utilities;
    if (!(u.serial_.empty() && u.part.empty())) {
        os << "\n -- utilities [" << u.serial_ << "; " << u.part << "]";
    }
    return os;
}

using VertexBundle = boost::variant<Car, Plane>;
using EdgeBundle = std::string;
using Graph = boost::adjacency_list<
    boost::vecS, boost::vecS, boost::directedS,
    VertexBundle, EdgeBundle>;

using Vertex = Graph::vertex_descriptor;
using Edge   = Graph::edge_descriptor;

int main() 
{
    boost::property_tree::ptree pt;
    {
        std::ifstream ifs("input.xml");
        read_xml(ifs, pt);
    }

    Graph g;

    auto parse_vehicles = [&pt,&g]<typename Type>(auto path, auto key) {
        for (auto& [k, node] : pt.get_child(path)) {
            if (k == key) {
                Type vehicle;
                parse(node, vehicle);

                add_vertex(vehicle, g);
            }
        }
    };

    parse_vehicles.operator()<Car>("root.type.cars", "car");
    parse_vehicles.operator()<Plane>("root.type.planes", "plane");

    // TODO add edges, but there's no information on how to
    add_edge(vertex(0, g), vertex(2, g), "This demo edge has no properties", g);
    add_edge(vertex(2, g), vertex(1, g), "One more", g);

    for (Vertex v : boost::make_iterator_range(vertices(g))) {
        std::cout << g[v] << "\n";
    }

    {
        auto vindex     = get(boost::vertex_index, g);
        auto calc_color = [&](Vertex v) { return g[v].which()? "red":"blue"; };
        auto calc_label = [&](Vertex v) {
            // multiline Mrecord label formatting
            auto txt = boost::lexical_cast<std::string>(g[v]);
            boost::algorithm::replace_all(txt, "\n --", "|");
            return "{" + txt + "}";
        };

        boost::dynamic_properties dp;
        dp.property("node_id",   vindex);
        dp.property("label",     boost::make_transform_value_property_map(calc_label, vindex));
        dp.property("fontcolor", boost::make_transform_value_property_map(calc_color, vindex));
        dp.property("style",     boost::make_static_property_map<Vertex>(std::string("filled")));
        dp.property("label",     get(boost::edge_bundle, g));

        auto pw = boost::dynamic_vertex_properties_writer { dp, "node_id" };
        using Map = std::map<std::string, std::string>;
        auto gpw = boost::make_graph_attributes_writer(Map{}, Map {{"shape", "Mrecord"}}, Map{});

        std::ofstream ofs("graph.dot");
        write_graphviz(ofs, g, pw, pw, gpw);
    }
}

打印上面显示的输出,以及下面的graph.dot

digraph G {
node [
shape=Mrecord];
0 [fontcolor=blue, label="{Name: Garfield, Weight: 4Kg| [e_54; petrol; 56]| [e_52; diesel; 52]| [m_22; electric; 51]| additions [steam; 153/0/1/0]}", style=filled];
1 [fontcolor=blue, label="{Name: Awesome, Weight: 3Kg| [t_54; petrol; 16]| [t_52; wind; 62]| [t_22; electric; 81]| additions [water; 123/1/1/0]}", style=filled];
2 [fontcolor=red, label="{Id: 231, Name: abc| utilities [e456; 567]}", style=filled];
0->2  [label="This demo edge has no properties"];
2->1  [label="One more"];
}

【讨论】:

  • 在线DOT图渲染:cutt.ly/BljyXKf
  • 嗨@sehe,再次感谢所有帮助和反馈。我花了一段时间才完全理解你的代码。写得真好,绝对优雅。
  • 我仍在试图弄清楚这部分问题:“”接下来,它们的子节点,即“spec”、“additions”和“utilities”将以边的形式存储他们的属性“”。我知道它们将以边缘属性的形式添加。我也在 E​​dgeBundle 中添加了 struct 。如果需要在 属性“源”相同的每个顶点之间添加边,您能否强调如何将它们添加为边。我尝试通过顶点列表进行迭代,但我无法通过 .或 -> 打印函数中显示的方法。
  • 我不知道如何基于 XML 创建边缘。如果你能从我可能理解的示例 XML 中给我任何示例优势。
  • 对于此 xml,如果 spec.source = something,则需要从源之间添加边,如 spec 和 dest 中所示。如汽车所示
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-01-17
  • 1970-01-01
  • 1970-01-01
  • 2017-06-10
  • 1970-01-01
  • 2014-06-19
  • 1970-01-01
相关资源
最近更新 更多