文章目录
  1. 1. class的设计
    1. 1.1. 有关智能指针的初始化:
    2. 1.2. 有关std::move
  2. 2. 想法
    1. 2.1. 直接获取xml的标签的值,
    2. 2.2. 获取所有子标签
    3. 2.3. 获取所有孙标签
    4. 2.4. 得到一个标签的attribute
    5. 2.5. 得到一组attribute
    6. 2.6. 根据已经知道的attribute去查找未知的attribute
  3. 3. 插入数据的操作
    1. 3.1. 加入一个<key>value</key>标签
    2. 3.2. 插入一个父标签
    3. 3.3. 插入一个数组
    4. 3.4. 插入一个新的attribute
    5. 3.5. 创建一个新行,并插入一组attribute,
    6. 3.6. 保存结果
  4. 4. 我的测试详情请访问test_xml.cpp
  5. 5. 最后贴上完整的源代码:
  6. 6. parse_xml.h
  7. 7. parse_xml.cpp

请注意,这段代码只能运行在boost的版本比较高的情况下。我的ubuntu 14.04LTSboost版本是1.54,所以能编译通过,但是今天我在我的centos 6.5上编译通不过,因为我的centos 6.5boost版本是1.41。我在centos 6.5下是用yum安装boost的命令是

1
2
yum -y groupinstall "Development Tools"
yum -y install boost-*

然而这个yum -y install boost-*在我的zsh环境下执行不了,我只好切换到bash执行。boost安装路劲是/usr/include/boost,然后有一个专门的version.hpp是显示boost版本的,这个时候我查看到我用yum安装的boost1.41版本。随后我重新下载了boost的源代码,使用b2安装了最新版。不得不说centos的软件更新速度真是慢。同样是安装,我的Ubuntu 14.04LTS用的是

1
sudo apt-get install libboost-all-dev

安装的boost就是1.54版本。

=========================== 2015年12月13日 ========================================

class的设计

这个ParseXmlObj的设计当然和前面一样都是遵守RAII的法则。这个构造函数的设计很简单,就是通过传入xml文件的路径从而得到ParseXmlObj的对象的。默认路径是../config/messageQueue.xml

使用方法很简单:

1
ParseXmlObj myXml("../config/testXml.xml");

这样就根据../config/testXml.xml构造了ParseXmlObj对象。

有关智能指针的初始化:

需要提到的一点是有关智能指针作为类的成员的时候,类初始化必须把智能指针放在初始化列表:
是这样的,由于解析xml必须用到ptree类型的对象,所以我把它封装在了ParseXmlObj的成员当中,但是由于我并不知道ptree初始化需要什么样的参数,换句话来说,就是如果ParseXmlObj的成员是ptree类型的对象,那么构造函数初始化的时候就会失败,为了避免这种情况,我使用指针来充当成员而不是使用ptree的对象。
对于指针的使用,我早就条件反射式的用shared_ptr,可以说我已经不用普通指针很多年了:)
那么在构造函数当中就需要解决shared_ptr成员的初始化。

shared_ptr的初始化必须使用初始化列表

正确的形式是这样的:

1
2
3
4
5
6
ParseXmlObj::ParseXmlObj() 
: pt_(new ptree) {
configPath_ = "../config/messageQueue.xml";
//pt_ = new ptree;
boost::property_tree::read_xml(configPath_, *pt_);
}

如果写成这种形式将编译通不过:

1
2
3
4
5
6
7
ParseXmlObj::ParseXmlObj() {
configPath_ = "../config/messageQueue.xml";
pt_ = new ptree;
//或者这种形式编译也通不过:
//pt_(new ptree);
boost::property_tree::read_xml(configPath_, *pt_);
}

有关std::move

std::move是用来减少不必要的拷贝和销毁的。有关std::moveRVO(Return value optimization)也就是编译器做的返回对象的优化,可以参看这篇英文文章RVO V.S. std::move,写的很好,我想有空的话我来做个翻译。

想法

解析xml我觉得有以下几种情况:

直接获取xml的标签的值,

1
2
3
4
5
6
<root>
<child>
<a>“1”</a>
<b>2</b>
</child>
</root>

比如说我想要直接获取<a>标签的值,这种情况是我已经知道了标签的名字和路径。
这个时候我提供了一个接口:

1
string GetChildData(const string& path);

用于处理已经知道标签的路径,想要得到标签里面的值的情况。

获取所有子标签

我不知道这个标签下有多少个子标签,但是我知道这些子标签的格式都是最简单的<key>value</key>型:
就是说这些子标签不会有它们自己的子标签,而且也没有attribute, 而且这些子标签的标签名不会有重复。

比如这个xml当中的child标签:

1
2
3
4
5
6
<root>
<child>
<a>“1”</a>
<b>2</b>
</child>
</root>

对于这种情况,我提供了接口:

1
map<string, string> GetChildDataMap(const string& path);

mapfirst就是标签名,second就是标签值。
由于子标签的标签名不会有重复,所以可以保证数据的完整性。

获取所有孙标签

我不知道这个标签下有多少个子标签,也不知道有多少个孙标签,但是我知道这些子标签的格式,以及孙标签的格式。

子标签的格式就是没有attribute<key>value</key>型,孙标签也是。

比如这里的root标签:

1
2
3
4
5
6
7
8
9
10
<root>
<child>
<a>"1"</a>
<b>2</b>
</child>
<newchild>
<a>"3"</a>
<b>"4"</b>
</newchild>
</root>

我认为用的最多的场景就是这样的:每个子标签格式相同(值的是孙标签不多也不少,而且孙标签的名字都相同),每个子标签代表这初始化一个资源所需的参数(就想我的server_connection_pool.cpp一样),用这个接口可以快速初始化所有资源。这也是我想写parse_xml.cpp代码的原因所在。

当然不符合“每个子标签格式相同”的这个场景也没问题,因为这里用的是map,对于key没有任何要求。

1
vector<map<string, string> > GetChildDataArray(const string& path);

仔细看这个接口,由于孙标签是map,所以一个子标签下的孙标签的标签名不能相同。由于子标签是vector,就是说子标签的标签名不会存储在vector<map<string, string> >当中,所以子标签的标签名随便写。

其实刚开始我是没有设计这个接口的,但是因为parse_json.cpp当中有获取数组的接口,所以我在xml的解析当中也就顺带实现了这么一个接口。

得到一个标签的attribute

已经知道标签的路径,和attribute的名字,就能获取attribute的值。
比如

1
2
3
4
<errors>
<error id="DB_ERROR_EXECUTE" value="1" prompt="操作数据库失败" msg="操作数据库失败" test="ttttt"/>
<error id="DB_ERROR_COMMAND" value="2" prompt="创建数据库操作指令失败" msg="创建数据库操作指令失败"/>
</errors>

这种情况,根据路径errors.error能获取到两个标签,那么我的这个接口就只能返回第一个标签指定的attribute。这个接口只能处理一个路径下只有一个标签的情况。

1
string GetAttr(string path, const string& attr);

得到一组attribute

如果要获得errors这个父标签下的所有子标签的指定的attribute,就可以使用这个接口:

1
vector<string> GetAttrArray(string path, const string& attr);

比如这样使用

1
GetAttrArray("errors", "id");

获取errors标签下所有子标签的id这个attribute

根据已经知道的attribute去查找未知的attribute

我认为更多的关于attribute的使用是用这个接口

1
string GetAttrByAttr(string path, const string& know_attr, const string& know_value, const string& attr);

比如已知了id,要想拿到value。很多情况下在自己的代码当中要用到错误码的时候,都是用string类型的,这个时候,就要用到这个接口,根据id来找到错误码的对应的值。

比如:

1
GetAttr("errors", "id", "DB_ERROR_EXECUTE", "value");

就是在errors标签下遍历,找到id等于DB_ERROR_EXECUTE这个标签,并取出这个标签当中value这个attribute的值。
根据这个接口就能够建立起来attributeattribute的映射关系。

插入数据的操作

我觉得xmljson多用于配置文件,绝大多数情况下都是程序读取配置文件,而不是程序写入配置文件,写配置文件只要人为写入就可以了,而且配置文件都是具有重复性,只要复制下来改一改就好了。

但是我在这里还是实现了几个写配置文件的接口。

加入一个<key>value</key>标签

最简单的形式,自己构建一个路径,把路径和值传给接口:

1
void PutChildData(const string& key, const string& value);

比如:

1
PutChildData("testput.a.b.c", "1");

就能够产生这种xml:

1
2
3
4
5
6
7
<testput>
<a>
<b>
<c>"1"</c>
</b>
</a>
</testput>

testput这个标签并不需要已经存在,同理abc也是。

插入一个父标签

由于使用了map所以插入的数据的标签不一样。

1
void PutChildDataMap(const string& key, const map<string, string>& key_value_map);

比如说我要插入一个<newchild>的父标签在这个xml当中:

1
2
3
4
5
6
<root>
<child>
<a></a>
<b></b>
</child>
</root>

那就应该这样用:

1
2
3
myMap.insert(make_pair("a", "1"));
myMap.insert(make_pair("b", "2"));
PutChildData("root.newchild", myMap);

得到结果是:

1
2
3
4
5
6
7
8
9
10
11

<root>
<child>
<a></a>
<b></b>
</child>
<newchild>
<a>"1"</a>
<b>"2"</b>
</newchild>
</root>

插入一个数组

这个只要重复调用上面的“插入一系列数据”的接口就可以了。

插入一个新的attribute

1
void PutAttr(string path, const string& attribute, const string& attrvalue);

这个接口对应的是

1
string GetAttr(string path, const string& attr);

同样的,调用PutAttr接口的路径下也只能找到唯一的一个标签,否则只会对第一个标签做操作。
使用方法:

1
PutAttr("errors.error", "id", "DB_ERROR_EXECUTE");

得到结果

1
2
3
<errors>
<error id="DB_ERROR_EXECUTE"/>
</errors>

至于在一组标签中都插入attribute的接口我觉得这个实现实在是没有必要,因为如果设计出来必然是这样的形式:

1
void PutAttrArray(string path, const string& attr, const string& attrvalue);

这样操作的结果就是在一组标签下都加入相同的attribute,关键还是attribute的值都一模一样,那这种操作根本没有意义,因为每一个标签都有相同的attribute的值,毫无区分性可言。

创建一个新行,并插入一组attribute

如果对单一的标签插入一组attribute的话,可以反复调用上面的接口PutAttr,但是这里为了加入一行新行,我特意写了这个接口

1
void AddLineByAttr(string path, const map<string, string>& key_value_map);

使用方法:

1
2
3
4
5
myMap.insert("id", "ttt");
myMap.insert("value", "000");
myMap.insert("prompt", "ppp");
myMap.insert("msg", "mmm");
AddLineByAttribute("errors.error", myMap);

得到结果:

1
2
3
<errors>
<error id="ttt" value="000" prompt="ppp" msg="mmm"/>
</errors>

保存结果

在对xml进行写入之后,不要忘记调用接口

1
void SaveConfig();

来保存结果。
因为之前的写入都是写入到ptree这个数据结构当中的,使用这个接口将ptree的数据同步到xml文件。

我的测试详情请访问test_xml.cpp

最后贴上完整的源代码:

parse_xml.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
#ifndef MQUEUE_INCLUDE_PARSE_XML_H_
#define MQUEUE_INCLUDE_PARSE_XML_H_

/* 以前一直都是把解析xml文件的代码放在池的构造函数当中,这里把它独立出来
* 使用boost的xml解析库,也推荐使用rapidxml,而且boost的xml解析库用的就是rapidxml
*/

#include <string>
#include <vector>
#include <map>
#include <boost/shared_ptr.hpp>
#include <boost/property_tree/ptree.hpp>

using std::string;
using std::vector;
using std::map;
using std::make_pair;
using boost::property_tree::ptree;

typedef boost::shared_ptr<boost::property_tree::ptree> ptreePtr;

class ParseXmlObj {
public:
explicit ParseXmlObj();
explicit ParseXmlObj(string configPath);
virtual ~ParseXmlObj();
ParseXmlObj(const ParseXmlObj&) = delete;
ParseXmlObj& operator=(const ParseXmlObj&) = delete;
void Dump() const;

string GetConfigPath() const;

ptreePtr GetPtree() const;

/*
* GetChildData("root.child.a") will get <a>
* <root>
* <child>
* <a></a>
* <b></b>
* </child>
* </root>
*/
string GetChildData(const string& path);

/*
* GetChildData("root.child") will get <a> and <b>
* <root>
* <child>
* <a></a>
* <b></b>
* </child>
* </root>
*/
map<string, string> GetChildDataMap(const string& path);

/*
* 请注意这里和parse_json当中的GetChildData的区别,严格的来说xml并没有数组的概念
* 这里只是为了方便拿数据所以实现了这个接口
* GetChildData("root") will get <a> and <b>
* <root>
* <child>
* <a></a>
* <b></b>
* </child>
* <child>
* <a></a>
* <b></b>
* </child>
* </root>
*/
vector<map<string, string> > GetChildDataArray(const string& path);

/*
* get attribute by path
* GetAttr("errors.error", "id") get the first attribute
* <errors>
* <error id="DB_ERROR_EXECUTE" value="1" prompt="操作数据库失败" msg="操作数据库失败" test="ttttt"/>
* <error id="DB_ERROR_COMMAND" value="2" prompt="创建数据库操作指令失败" msg="创建数据库操作指令失败"/>
* </errors>
* the result is "1"
*/
string GetAttr(string path, const string& attr);

/*
* get attribute by path
* GetAttrArray("errors", "id") get the first attribute
* <errors>
* <error id="DB_ERROR_EXECUTE" value="1" prompt="操作数据库失败" msg="操作数据库失败" test="ttttt"/>
* <error id="DB_ERROR_COMMAND" value="2" prompt="创建数据库操作指令失败" msg="创建数据库操作指令失败"/>
* </errors>
* the result is a vector
*/
vector<string> GetAttrArray(string path, const string& attr);

/*
* get attribute by path and attribute
* GetAttr("errors", "id", "DB_ERROR_EXECUTE", "value") get the "value" attribute where "id" is "DB_ERROR_EXECUTE"
* 注意这个接口的path传的是errors 而不是errors.error
* <errors>
* <error id="DB_ERROR_EXECUTE" value="1" prompt="操作数据库失败" msg="操作数据库失败" test="ttttt"/>
* <error id="DB_ERROR_COMMAND" value="2" prompt="创建数据库操作指令失败" msg="创建数据库操作指令失败"/>
* </errors>
* result is "1"
*/
string GetAttrByAttr(string path, const string& know_attr, const string& know_value, const string& attr);

//===========================================================
/*
* PutChildData("testput", "testput") will add "testput" in
* <root>
* <child>
* <a></a>
* <b></b>
* </child>
* </root>
* new:
* <root>
* <child>
* <a></a>
* <b></b>
* </child>
* </root>
* <testput>"testput"</testput>
*/
void PutChildData(const string& key, const string& value);

/*
* myMap.insert(make_pair("a", "1")) myMap.insert(make_pair("b", "2"))
* PutChildData("root.newchild", myMap) will add "newchild" in
* <root>
* <child>
* <a></a>
* <b></b>
* </child>
* </root>
* new:
* <root>
* <child>
* <a></a>
* <b></b>
* </child>
* <newchild>
* <a></a>
* <b></b>
* </newchild>
* </root>
*/
void PutChildDataMap(const string& key, const map<string, string>& key_value_map);

/*
* 上面说过了xml并没有数组的概念,所以这里没有 PutChildDataArray()函数
*/

/*
* PutAttr("errors.error", "id", "DB_ERROR_EXECUTE")
* <error id="DB_ERROR_EXECUTE"/>
*/
void PutAttr(string path, const string& attribute, const string& attrvalue);

/*
* 这个通过attribute找到对应的标签,再在标签中加入一个attribute的功能很难实现,所以这个函数就没有实现 PutAttrByAttr()
* 而且我觉得要用到这个功能会比较少
*/

/*
*
* add a new line in xml
* myMap.insert("id", "ttt");
* myMap.insert("value", "000");
* myMap.insert("prompt", "ppp");
* myMap.insert("msg", "mmm");
* AddLineByAttribute("errors.error", myMap);
* <errors>
* <error id="DB_ERROR_EXECUTE" value="1" prompt="操作数据库失败" msg="操作数据库失败" />
* <error id="DB_ERROR_COMMAND" value="2" prompt="创建数据库操作指令失败" msg="创建数据库操作指令失败"/>
* </errors>
* new:
* <errors>
* <error id="DB_ERROR_EXECUTE" value="1" prompt="操作数据库失败" msg="操作数据库失败" />
* <error id="DB_ERROR_COMMAND" value="2" prompt="创建数据库操作指令失败" msg="创建数据库操作指令失败"/>
* <error id="ttt" value="000" prompt="ppp" msg="mmm"/>
* </errors>
*/
void AddLineByAttr(string path, const map<string, string>& key_value_map);

/*
* save config
*/
void SaveConfig();

private:
string configPath_;
//这里不用ptree对象而用指针的意义在于如果使用ptree对象的话,构造的时候就必须完全构造这个对象
ptreePtr pt_;
//ptree* pt_;
};

typedef boost::shared_ptr<ParseXmlObj> ParseXmlObjPtr;
#endif /* MQUEUE_INCLUDE_PARSE_XML_H */

parse_xml.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#include "MQueue/parse_xml.h"
#include <boost/property_tree/xml_parser.hpp>


ParseXmlObj::ParseXmlObj()
: pt_(new ptree) {
configPath_ = "../config/messageQueue.xml";
//pt_ = new ptree;
boost::property_tree::read_xml(configPath_, *pt_);
}

ParseXmlObj::ParseXmlObj(string configPath)
: configPath_(configPath), pt_(new ptree) {
//pt_ = new ptree;
boost::property_tree::read_xml(configPath_, *pt_);
}

ParseXmlObj::~ParseXmlObj() {
//delete pt_;
}

void ParseXmlObj::Dump() const {
printf("\n=====ParseXmlObj Dump START ========== \n");
printf("configPath__=%s ", configPath_.c_str());
//printf("pt_=%p ", pt_);
printf("\n===ParseXMlObj DUMP END ============\n");
}

string ParseXmlObj::GetConfigPath() const {
return configPath_;
}

ptreePtr ParseXmlObj::GetPtree() const {
return pt_;
}

string ParseXmlObj::GetChildData(const string& path) {
return pt_->get<string>(path);
}

map<string, string> ParseXmlObj::GetChildDataMap(const string& path) {
map<string, string> key_value_map;

auto child = pt_->get_child(path);
for (auto pos = child.begin(); pos!= child.end(); ++pos) {
key_value_map.insert(make_pair(pos->first, pos->second.data()));
}
return std::move(key_value_map);
}

vector<map<string, string> > ParseXmlObj::GetChildDataArray(const string& path) {
vector<map<string, string> > result_array;
map<string, string> key_value_map;

auto child = pt_->get_child(path);
for (auto pos = child.begin(); pos!= child.end(); ++pos) {
auto nextchild = pos->second.get_child("");
for (auto nextpos = nextchild.begin(); nextpos!= nextchild.end(); ++nextpos) {
key_value_map.insert(make_pair(nextpos->first, nextpos->second.data()));
}
result_array.push_back(key_value_map);
key_value_map.clear();
}
return std::move(result_array);
}

string ParseXmlObj::GetAttr(string path, const string& attr) {
if(pt_ == NULL || attr == "")
return "";
if(path != ""){
path += "."; //add the domain descriptor
}
path += "<xmlattr>.";
path += attr;
return pt_->get<string>(path, "");
}

vector<string> ParseXmlObj::GetAttrArray(string path, const string& attr) {
vector<string> result;
if (pt_ != NULL && attr != "") {
auto child = pt_->get_child(path);
for (auto pos = child.begin(); pos!= child.end(); ++pos) {
auto nextchild = pos->second.get_child("");
for (auto nextpos = nextchild.begin(); nextpos!= nextchild.end(); ++nextpos) {
result.push_back(nextpos->second.get<string>(attr));
}
}
}
return std::move(result);
}

string ParseXmlObj::GetAttrByAttr(string path, const string& know_attr, const string& know_value, const string& attr) {
string result = "";
auto child = pt_->get_child(path);
for (auto pos = child.begin(); pos!= child.end(); ++pos) {
auto nextchild = pos->second.get_child("");
for (auto nextpos = nextchild.begin(); nextpos!= nextchild.end(); ++nextpos) {
if (nextpos->second.get<string>(know_attr) == know_value) {
result = nextpos->second.get<string>(attr);
break;
}
}
}
return result;
}


void ParseXmlObj::PutChildData(const string& key, const string& value) {
if(pt_ != NULL) {
pt_->put(key, value);
}
}

void ParseXmlObj::PutChildDataMap(const string& key, const map<string, string>& key_value_map) {
if(pt_ != NULL) {
ptree child;
for (auto myPair : key_value_map) {
child.put(myPair.first, myPair.second);
}
pt_->add_child(key, child);
}
}

void ParseXmlObj::PutAttr(string path, const string& attribute, const string& attrvalue) {
if (pt_ != NULL) {
if(path != "") {
path += "."; //add the domain descriptor
}
path += "<xmlattr>.";
path += attribute;
pt_->put<string>(path, attrvalue);
}
}

void ParseXmlObj::AddLineByAttr(string path, const map<string, string>& key_value_map) {
if (pt_ != NULL) {
ptree temp;
for (auto myPair : key_value_map) {
temp.put<string>("<xmlattr>." + myPair.first, myPair.second.data());
}
pt_->add_child(path, temp);
}
}

void ParseXmlObj::SaveConfig() {
if(pt_ != NULL) {
boost::property_tree::write_xml(configPath_, *pt_);
}
}

文章目录
  1. 1. class的设计
    1. 1.1. 有关智能指针的初始化:
    2. 1.2. 有关std::move
  2. 2. 想法
    1. 2.1. 直接获取xml的标签的值,
    2. 2.2. 获取所有子标签
    3. 2.3. 获取所有孙标签
    4. 2.4. 得到一个标签的attribute
    5. 2.5. 得到一组attribute
    6. 2.6. 根据已经知道的attribute去查找未知的attribute
  3. 3. 插入数据的操作
    1. 3.1. 加入一个<key>value</key>标签
    2. 3.2. 插入一个父标签
    3. 3.3. 插入一个数组
    4. 3.4. 插入一个新的attribute
    5. 3.5. 创建一个新行,并插入一组attribute,
    6. 3.6. 保存结果
  4. 4. 我的测试详情请访问test_xml.cpp
  5. 5. 最后贴上完整的源代码:
  6. 6. parse_xml.h
  7. 7. parse_xml.cpp