在下面的随笔中,我会根据xml的结构,给出Qt中解析这个xml的三种方式的代码。虽然,这个代码时通过调用Qt的函数实现的,但是,很多开源的C++解析xml的库,甚至很多其他语言解析xml的库,都和下面三种解析xml采用相同的原理,所以就算你不是学习qt,也可以大致参看一下代码,对三种解析方式有一种大致的感觉。
先给出xml如下:
1 <?xml version="1.0" encoding="utf-8"?>
2 <school>
3 <teacher>
4 <entry name="Job">
5 <age>30</age>
6 <sport>soccer</sport>
7 </entry>
8 <entry name="Tom">
9 <age>32</age>
10 <sport>swimming</sport>
11 </entry>
12 </teacher>
13 <student>
14 <entry name="Lily">
15 <age>20</age>
16 <sport>dancing</sport>
17 </entry>
18 <entry name="Keith">
19 <age>21</age>
20 <sport>running</sport>
21 </entry>
22 </student>
23 </school>
下面给出qt中解析xml的三种方式,通过解析xml,创建student列表和teacher列表。先给出存储的结构体和辅助函数:
1 #include <string>
2 #include <ostream>
3
4 namespace School 5 { 6
7 struct Teacher 8 { 9 std::string name; 10 int age; 11 std::string loveSport; 12
13 Teacher(std::string name_, int age_, std::string loveSport_) 14 : name(std::move(name_)), age(age_), loveSport(std::move(loveSport_)) 15 { 16
17 } 18 }; 19
20 struct Student 21 { 22 std::string name; 23 int age; 24 std::string loveSport; 25
26 Student(std::string name_, int age_, std::string loveSport_) 27 : name(std::move(name_)), age(age_), loveSport(std::move(loveSport_)) 28 { 29
30 } 31 }; 32
33 inline void print(std::ostream &out, const Teacher& teacher) 34 { 35 out << "teacher: " << teacher.name << std::endl; 36 out << "\tage: " << teacher.age << std::endl; 37 out << "\tfavorite sport: " << teacher.loveSport << std::endl; 38 } 39
40 inline void print(std::ostream& out, const Student& student) 41 { 42 out << "student: " << student.name << std::endl; 43 out << "\tage: " << student.age << std::endl; 44 out << "\tfavorite sport: " << student.loveSport << std::endl; 45 } 46
47 }
另外需要注意在.pro中添加
QT += xml
(1)通过QXmlStreamReader:
1 #include <QXmlStreamReader>
2 #include "schooldefine.h"
3
4 class XmlStreamReader 5 { 6 public: 7 XmlStreamReader(); 8
9 bool readFile(const QString& fileName); 10 void printAllMembers(); 11
12 private: 13 void readSchoolMembers(); 14 void readTeacherMembers(); 15 void readTeacher(const QStringRef& teacherName); 16 void readStudentMembers(); 17 void readStudent(const QStringRef& studentName); 18 void skipUnknownElement(); 19
20 QXmlStreamReader reader; 21
22 std::vector<School::Teacher> m_teachers; 23 std::vector<School::Student> m_students; 24 };
1 #include "XmlStreamReader.h"
2 #include <QFile>
3 #include <iostream>
4 #include <QDebug>
5
6 XmlStreamReader::XmlStreamReader() 7 { 8
9 } 10
11 bool XmlStreamReader::readFile(const QString &fileName) 12 { 13 QFile file(fileName); 14 if (!file.open(QFile::ReadOnly | QFile::Text)) 15 { 16 std::cerr << "Error: Cannot read file " << qPrintable(fileName) 17 << ": " << qPrintable(file.errorString()) 18 << std::endl; 19 return false; 20 } 21 reader.setDevice(&file); 22
23 reader.readNext(); 24 while (!reader.atEnd()) 25 { 26 if (reader.isStartElement()) 27 { 28 if (reader.name() == "school") 29 { 30 readSchoolMembers(); 31 } 32 else
33 { 34 reader.raiseError(QObject::tr("Not a school file")); 35 } 36 } 37 else
38 { 39 reader.readNext(); 40 } 41 } 42
43 file.close(); 44 if (reader.hasError()) 45 { 46 std::cerr << "Error: Failed to parse file "
47 << qPrintable(fileName) << ": "
48 << qPrintable(reader.errorString()) << std::endl; 49 return false; 50 } 51 else if (file.error() != QFile::NoError) 52 { 53 std::cerr << "Error: Cannot read file " << qPrintable(fileName) 54 << ": " << qPrintable(file.errorString()) 55 << std::endl; 56 return false; 57 } 58 return true; 59 } 60
61 void XmlStreamReader::printAllMembers() 62 { 63 std::cout << "All teachers: " << std::endl; 64 for (const auto& teacher : m_teachers) 65 { 66 School::print(std::cout, teacher); 67 } 68 std::cout << "All students: " << std::endl; 69 for (const auto& student : m_students) 70 { 71 School::print(std::cout, student); 72 } 73 } 74
75 void XmlStreamReader::readSchoolMembers() 76 { 77 reader.readNext(); 78 while (!reader.atEnd()) 79 { 80 if (reader.isEndElement()) 81 { 82 reader.readNext(); 83 break; 84 } 85
86 if (reader.isStartElement()) 87 { 88 if (reader.name() == "teacher") 89 { 90 readTeacherMembers(); 91 } 92 else if (reader.name() == "student") 93 { 94 readStudentMembers(); 95 } 96 else
97 { 98 skipUnknownElement(); 99 } 100 } 101 else
102 { 103 reader.readNext(); 104 } 105 } 106 } 107
108 void XmlStreamReader::readTeacherMembers() 109 { 110 reader.readNext(); 111 while (!reader.atEnd()) 112 { 113 if (reader.isEndElement()) 114 { 115 reader.readNext(); 116 break; 117 } 118
119 if (reader.isStartElement()) 120 { 121 if (reader.name() == "entry") 122 { 123 readTeacher(reader.attributes().value("name")); 124 } 125 else
126 { 127 skipUnknownElement(); 128 } 129 } 130 else
131 { 132 reader.readNext(); 133 } 134 } 135 } 136
137 void XmlStreamReader::readTeacher(const QStringRef& teacherName) 138 { 139 reader.readNext(); 140
141 int age = 0; 142 std::string favoriteSport; 143
144 while (!reader.atEnd()) 145 { 146 if (reader.isEndElement()) 147 { 148 reader.readNext(); 149 break; 150 } 151
152 if (reader.isStartElement()) 153 { 154 if (reader.name() == "age") 155 { 156 age = reader.readElementText().toInt(); 157 } 158 else if (reader.name() == "sport") 159 { 160 favoriteSport = reader.readElementText().toStdString(); 161 } 162 else
163 { 164 skipUnknownElement(); 165 } 166 } 167 reader.readNext(); 168 } 169
170 m_teachers.emplace_back(teacherName.toString().toStdString(), age, favoriteSport); 171 } 172
173 void XmlStreamReader::readStudentMembers() 174 { 175 reader.readNext(); 176 while (!reader.atEnd()) 177 { 178 if (reader.isEndElement()) 179 { 180 reader.readNext(); 181 break; 182 } 183
184 if (reader.isStartElement()) 185 { 186 if (reader.name() == "entry") 187 { 188 readStudent(reader.attributes().value("name")); 189 } 190 else
191 { 192 skipUnknownElement(); 193 } 194 } 195 else
196 { 197 reader.readNext(); 198 } 199 } 200 } 201
202 void XmlStreamReader::readStudent(const QStringRef &studentName) 203 { 204 reader.readNext(); 205
206 int age = 0; 207 std::string favoriteSport; 208
209 while (!reader.atEnd()) 210 { 211 if (reader.isEndElement()) 212 { 213 reader.readNext(); 214 break; 215 } 216
217 if (reader.isStartElement()) 218 { 219 if (reader.name() == "age") 220 { 221 age = reader.readElementText().toInt(); 222 } 223 else if (reader.name() == "sport") 224 { 225 favoriteSport = reader.readElementText().toStdString(); 226 } 227 else
228 { 229 skipUnknownElement(); 230 } 231 } 232 reader.readNext(); 233 } 234
235 m_students.emplace_back(studentName.toString().toStdString(), age, favoriteSport); 236 } 237
238 void XmlStreamReader::skipUnknownElement() 239 { 240 reader.readNext(); 241 while (!reader.atEnd()) 242 { 243 if (reader.isEndElement()) 244 { 245 reader.readNext(); 246 break; 247 } 248
249 if (reader.isStartElement()) 250 { 251 skipUnknownElement(); 252 } 253 else
254 { 255 reader.readNext(); 256 } 257 } 258 }
(2)通过DOM方式:
1 #include <QString>
2 #include <QDomElement>
3 #include "schooldefine.h"
4
5 class DomParser 6 { 7 public: 8 DomParser(); 9
10 bool readFile(const QString &fileName); 11 void printAllMembers(); 12
13 private: 14 void parseSchoolMembers(const QDomElement &element); 15 void parseTeacherMembers(const QDomElement &element); 16 void parseStudentMembers(const QDomElement &element); 17 void parseTeacher(const QDomElement &element); 18 void parseStudent(const QDomElement &element); 19
20 std::vector<School::Teacher> m_teachers; 21 std::vector<School::Student> m_students; 22 };
1 #include "domparser.h"
2 #include <QDomDocument>
3 #include <QFile>
4 #include <iostream>
5
6 DomParser::DomParser() 7 { 8
9 } 10
11 bool DomParser::readFile(const QString &fileName) 12 { 13 QFile file(fileName); 14 if (!file.open(QFile::ReadOnly | QFile::Text)) { 15 std::cerr << "Error: Cannot read file " << qPrintable(fileName) 16 << ": " << qPrintable(file.errorString()) 17 << std::endl; 18 return false; 19 } 20
21 QString errorStr; 22 int errorLine; 23 int errorColumn; 24
25 QDomDocument doc; 26 if (!doc.setContent(&file, false, &errorStr, &errorLine, &errorColumn)) 27 { 28 std::cerr << "Error: Parse error at line " << errorLine << ", "
29 << "column " << errorColumn << ": "
30 << qPrintable(errorStr) << std::endl; 31 return false; 32 } 33
34 QDomElement root = doc.documentElement(); 35 if (root.tagName() != "school") 36 { 37 std::cerr << "Error: Not a school file" << std::endl; 38 return false; 39 } 40
41 parseSchoolMembers(root); 42 return true; 43 } 44
45 void DomParser::printAllMembers() 46 { 47 std::cout << "All teachers: " << std::endl; 48 for (const auto& teacher : m_teachers) 49 { 50 School::print(std::cout, teacher); 51 } 52 std::cout << "All students: " << std::endl; 53 for (const auto& student : m_students) 54 { 55 School::print(std::cout, student); 56 } 57 } 58
59
60 void DomParser::parseSchoolMembers(const QDomElement &element) 61 { 62 QDomNode child = element.firstChild(); 63 while (!child.isNull()) 64 { 65 if (child.toElement().tagName() == "teacher") 66 { 67 parseTeacherMembers(child.toElement()); 68 } 69 else if (child.toElement().tagName() == "student") 70 { 71 parseStudentMembers(child.toElement()); 72 } 73 child = child.nextSibling(); 74 } 75 } 76
77 void DomParser::parseTeacherMembers(const QDomElement &element) 78 { 79 QDomNode child = element.firstChild(); 80 while (!child.isNull()) 81 { 82 if (child.toElement().tagName() == "entry") 83 { 84 parseTeacher(child.toElement()); 85 } 86 child = child.nextSibling(); 87 } 88 } 89
90 void DomParser::parseStudentMembers(const QDomElement &element) 91 { 92 QDomNode child = element.firstChild(); 93 while (!child.isNull()) 94 { 95 if (child.toElement().tagName() == "entry") 96 { 97 parseStudent(child.toElement()); 98 } 99 child = child.nextSibling(); 100 } 101 } 102
103 void DomParser::parseTeacher(const QDomElement &element) 104 { 105 auto children = element.childNodes(); 106 auto firstChild = children.at(0).toElement(); 107 auto secondChild = children.at(1).toElement(); 108 int age = firstChild.text().toInt(); 109
110 m_teachers.emplace_back(element.attribute("name").toStdString(), 111 age, secondChild.text().toStdString()); 112 } 113
114 void DomParser::parseStudent(const QDomElement &element) 115 { 116 auto children = element.childNodes(); 117 auto firstChild = children.at(0).toElement(); 118 auto secondChild = children.at(1).toElement(); 119 int age = firstChild.text().toInt(); 120
121 m_students.emplace_back(element.attribute("name").toStdString(), 122 age, secondChild.text().toStdString()); 123 }
3. 采用QXmlSimpleReader方式,也就是回调函数方式:
1 #include <QXmlDefaultHandler>
2 #include "schooldefine.h"
3
4 class SaxHandler : public QXmlDefaultHandler 5 { 6 public: 7 SaxHandler(); 8
9 bool readFile(const QString &fileName); 10 void printAllMembers(); 11
12 protected: 13 bool startElement(const QString &namespaceURI, 14 const QString &localName, 15 const QString &qName, 16 const QXmlAttributes &atts) override; 17 bool endElement(const QString &namespaceURL, 18 const QString &localName, 19 const QString &qName) override; 20 bool characters(const QString &ch) override; 21 bool fatalError(const QXmlParseException &exception) override; 22
23 private: 24 bool m_isStudent = false; 25 QString m_currentContext; 26 std::vector<School::Teacher> m_teachers; 27 std::vector<School::Student> m_students; 28 };
1 #include "saxhandler.h"
2 #include <iostream>
3
4 SaxHandler::SaxHandler() 5 { 6
7 } 8
9 bool SaxHandler::readFile(const QString &fileName) 10 { 11 QFile file(fileName); 12 QXmlInputSource inputSource(&file); 13 QXmlSimpleReader reader; 14 reader.setContentHandler(this); 15 reader.setErrorHandler(this);; 16 return reader.parse(inputSource); 17 } 18
19 void SaxHandler::printAllMembers() 20 { 21 std::cout << "All teachers: " << std::endl; 22 for (const auto& teacher : m_teachers) 23 { 24 School::print(std::cout, teacher); 25 } 26 std::cout << "All students: " << std::endl; 27 for (const auto& student : m_students) 28 { 29 School::print(std::cout, student); 30 } 31 } 32
33 bool SaxHandler::startElement(const QString &namespaceURI, 34 const QString &localName, 35 const QString &qName, 36 const QXmlAttributes &atts) 37 { 38 if (qName == "teacher") 39 { 40 m_isStudent = false; 41 } 42 else if (qName == "student") 43 { 44 m_isStudent = true; 45 } 46 else if (qName == "entry") 47 { 48 if (m_isStudent) 49 { 50 m_students.push_back(School::Student("", 0, "")); 51 m_students.back().name = atts.value("name").toStdString(); 52 } 53 else
54 { 55 m_teachers.push_back(School::Teacher("", 0, "")); 56 m_teachers.back().name = atts.value("name").toStdString(); 57 } 58 } 59 else if (qName == "age") 60 { 61 m_currentContext.clear(); 62 } 63 else if (qName == "sport") 64 { 65 m_currentContext.clear(); 66 } 67 return true; 68 } 69
70 bool SaxHandler::characters(const QString &ch) 71 { 72 m_currentContext += ch; 73 return true; 74 } 75
76 bool SaxHandler::endElement(const QString &namespaceURL, 77 const QString &localName, 78 const QString &qName) 79 { 80 if (qName == "age") 81 { 82 if (m_isStudent) 83 { 84 m_students.back().age = m_currentContext.toInt(); 85 } 86 else
87 { 88 m_teachers.back().age = m_currentContext.toInt(); 89 } 90 } 91 else if (qName == "sport") 92 { 93 if (m_isStudent) 94 { 95 m_students.back().loveSport = m_currentContext.toStdString(); 96 } 97 else
98 { 99 m_teachers.back().loveSport = m_currentContext.toStdString(); 100 } 101 } 102 m_currentContext.clear(); 103 return true; 104 } 105
106 bool SaxHandler::fatalError(const QXmlParseException &exception) 107 { 108 std::cerr << "Parse error at line" << exception.lineNumber() 109 << ", " << "column " << exception.columnNumber() << ": "
110 << qPrintable(exception.message()) << std::endl; 111 return false; 112 }
下面简单对上述三种方式予以说明:
(1) 从代码行数来看,采用DOM和QXmlSimpleReader的方式,代码行数比较少,而QXmlStreamReader代码行数较多。
(2) 从代码逻辑分析来看,采用DOM方式最容易理解,采用QXmlStreamReader的方式稍微难理解一些,而采用QXmlSimpleReader由于使用了较多的回调,引入了大量的类数据成员,使得代码会很难理解。
(3) 从内存占用来看,DOM的方式会耗费最多的内存,因为需要一次性将所有的内容构建成树,DOM和QXmlSimpleReader对内存要求都较低。
(4) 从运行时间消耗来看,DOM的消耗,可能会稍微大一些,因为DOM正常要经历2次的遍历,一次遍历构建树,一次遍历,构建自己需要的数据。而QXmlSimpleReader和QXmlStreamReader正常只需要遍历一次。
(5) 从处理异常来看,DOM和QXmlStreamReader应该会更容易一些,因为不涉及回调函数,但是对于xml来说,很多时候主要确认内容正确与否,如果错误就退出,查看xml中的错误。当然,这个也是比较重要的项。
对于我来说,因为大多数情况下,解析的xml不是很大,而且基本只涉及加载过程中,所以使用DOM的情况比较多。如果xml比较大,或者调用比较频繁,可以考虑使用QXmlStreamReader的方式。