在下面的隨筆中,我會根據xml的結構,給出Qt中解析這個xml的三種方式的代碼。雖然,這個代碼時經過調用Qt的函數實現的,可是,不少開源的C++解析xml的庫,甚至不少其餘語言解析xml的庫,都和下面三種解析xml採用相同的原理,因此就算你不是學習qt,也能夠大體參看一下代碼,對三種解析方式有一種大體的感受。ios
先給出xml以下:dom
<?xml version="1.0" encoding="utf-8"?> <school> <teacher> <entry name="Job"> <age>30</age> <sport>soccer</sport> </entry> <entry name="Tom"> <age>32</age> <sport>swimming</sport> </entry> </teacher> <student> <entry name="Lily"> <age>20</age> <sport>dancing</sport> </entry> <entry name="Keith"> <age>21</age> <sport>running</sport> </entry> </student> </school>
下面給出qt中解析xml的三種方式,經過解析xml,建立student列表和teacher列表。先給出存儲的結構體和輔助函數:ide
#include <string> #include <ostream> namespace School { struct Teacher { std::string name; int age; std::string loveSport; Teacher(std::string name_, int age_, std::string loveSport_) : name(std::move(name_)), age(age_), loveSport(std::move(loveSport_)) { } }; struct Student { std::string name; int age; std::string loveSport; Student(std::string name_, int age_, std::string loveSport_) : name(std::move(name_)), age(age_), loveSport(std::move(loveSport_)) { } }; inline void print(std::ostream &out, const Teacher& teacher) { out << "teacher: " << teacher.name << std::endl; out << "\tage: " << teacher.age << std::endl; out << "\tfavorite sport: " << teacher.loveSport << std::endl; } inline void print(std::ostream& out, const Student& student) { out << "student: " << student.name << std::endl; out << "\tage: " << student.age << std::endl; out << "\tfavorite sport: " << student.loveSport << std::endl; } }
另外須要注意在.pro中添加函數
QT += xml
(1)經過QXmlStreamReader:學習
#include <QXmlStreamReader> #include "schooldefine.h" class XmlStreamReader { public: XmlStreamReader(); bool readFile(const QString& fileName); void printAllMembers(); private: void readSchoolMembers(); void readTeacherMembers(); void readTeacher(const QStringRef& teacherName); void readStudentMembers(); void readStudent(const QStringRef& studentName); void skipUnknownElement(); QXmlStreamReader reader; std::vector<School::Teacher> m_teachers; std::vector<School::Student> m_students; };
#include "XmlStreamReader.h" #include <QFile> #include <iostream> #include <QDebug> XmlStreamReader::XmlStreamReader() { } bool XmlStreamReader::readFile(const QString &fileName) { QFile file(fileName); if (!file.open(QFile::ReadOnly | QFile::Text)) { std::cerr << "Error: Cannot read file " << qPrintable(fileName) << ": " << qPrintable(file.errorString()) << std::endl; return false; } reader.setDevice(&file); reader.readNext(); while (!reader.atEnd()) { if (reader.isStartElement()) { if (reader.name() == "school") { readSchoolMembers(); } else { reader.raiseError(QObject::tr("Not a school file")); } } else { reader.readNext(); } } file.close(); if (reader.hasError()) { std::cerr << "Error: Failed to parse file " << qPrintable(fileName) << ": " << qPrintable(reader.errorString()) << std::endl; return false; } else if (file.error() != QFile::NoError) { std::cerr << "Error: Cannot read file " << qPrintable(fileName) << ": " << qPrintable(file.errorString()) << std::endl; return false; } return true; } void XmlStreamReader::printAllMembers() { std::cout << "All teachers: " << std::endl; for (const auto& teacher : m_teachers) { School::print(std::cout, teacher); } std::cout << "All students: " << std::endl; for (const auto& student : m_students) { School::print(std::cout, student); } } void XmlStreamReader::readSchoolMembers() { reader.readNext(); while (!reader.atEnd()) { if (reader.isEndElement()) { reader.readNext(); break; } if (reader.isStartElement()) { if (reader.name() == "teacher") { readTeacherMembers(); } else if (reader.name() == "student") { readStudentMembers(); } else { skipUnknownElement(); } } else { reader.readNext(); } } } void XmlStreamReader::readTeacherMembers() { reader.readNext(); while (!reader.atEnd()) { if (reader.isEndElement()) { reader.readNext(); break; } if (reader.isStartElement()) { if (reader.name() == "entry") { readTeacher(reader.attributes().value("name")); } else { skipUnknownElement(); } } else { reader.readNext(); } } } void XmlStreamReader::readTeacher(const QStringRef& teacherName) { reader.readNext(); int age = 0; std::string favoriteSport; while (!reader.atEnd()) { if (reader.isEndElement()) { reader.readNext(); break; } if (reader.isStartElement()) { if (reader.name() == "age") { age = reader.readElementText().toInt(); } else if (reader.name() == "sport") { favoriteSport = reader.readElementText().toStdString(); } else { skipUnknownElement(); } } reader.readNext(); } m_teachers.emplace_back(teacherName.toString().toStdString(), age, favoriteSport); } void XmlStreamReader::readStudentMembers() { reader.readNext(); while (!reader.atEnd()) { if (reader.isEndElement()) { reader.readNext(); break; } if (reader.isStartElement()) { if (reader.name() == "entry") { readStudent(reader.attributes().value("name")); } else { skipUnknownElement(); } } else { reader.readNext(); } } } void XmlStreamReader::readStudent(const QStringRef &studentName) { reader.readNext(); int age = 0; std::string favoriteSport; while (!reader.atEnd()) { if (reader.isEndElement()) { reader.readNext(); break; } if (reader.isStartElement()) { if (reader.name() == "age") { age = reader.readElementText().toInt(); } else if (reader.name() == "sport") { favoriteSport = reader.readElementText().toStdString(); } else { skipUnknownElement(); } } reader.readNext(); } m_students.emplace_back(studentName.toString().toStdString(), age, favoriteSport); } void XmlStreamReader::skipUnknownElement() { reader.readNext(); while (!reader.atEnd()) { if (reader.isEndElement()) { reader.readNext(); break; } if (reader.isStartElement()) { skipUnknownElement(); } else { reader.readNext(); } } }
(2)經過DOM方式:this
#include <QString> #include <QDomElement> #include "schooldefine.h" class DomParser { public: DomParser(); bool readFile(const QString &fileName); void printAllMembers(); private: void parseSchoolMembers(const QDomElement &element); void parseTeacherMembers(const QDomElement &element); void parseStudentMembers(const QDomElement &element); void parseTeacher(const QDomElement &element); void parseStudent(const QDomElement &element); std::vector<School::Teacher> m_teachers; std::vector<School::Student> m_students; };
#include "domparser.h" #include <QDomDocument> #include <QFile> #include <iostream> DomParser::DomParser() { } bool DomParser::readFile(const QString &fileName) { QFile file(fileName); if (!file.open(QFile::ReadOnly | QFile::Text)) { std::cerr << "Error: Cannot read file " << qPrintable(fileName) << ": " << qPrintable(file.errorString()) << std::endl; return false; } QString errorStr; int errorLine; int errorColumn; QDomDocument doc; if (!doc.setContent(&file, false, &errorStr, &errorLine, &errorColumn)) { std::cerr << "Error: Parse error at line " << errorLine << ", " << "column " << errorColumn << ": " << qPrintable(errorStr) << std::endl; return false; } QDomElement root = doc.documentElement(); if (root.tagName() != "school") { std::cerr << "Error: Not a school file" << std::endl; return false; } parseSchoolMembers(root); return true; } void DomParser::printAllMembers() { std::cout << "All teachers: " << std::endl; for (const auto& teacher : m_teachers) { School::print(std::cout, teacher); } std::cout << "All students: " << std::endl; for (const auto& student : m_students) { School::print(std::cout, student); } } void DomParser::parseSchoolMembers(const QDomElement &element) { QDomNode child = element.firstChild(); while (!child.isNull()) { if (child.toElement().tagName() == "teacher") { parseTeacherMembers(child.toElement()); } else if (child.toElement().tagName() == "student") { parseStudentMembers(child.toElement()); } child = child.nextSibling(); } } void DomParser::parseTeacherMembers(const QDomElement &element) { QDomNode child = element.firstChild(); while (!child.isNull()) { if (child.toElement().tagName() == "entry") { parseTeacher(child.toElement()); } child = child.nextSibling(); } } void DomParser::parseStudentMembers(const QDomElement &element) { QDomNode child = element.firstChild(); while (!child.isNull()) { if (child.toElement().tagName() == "entry") { parseStudent(child.toElement()); } child = child.nextSibling(); } } void DomParser::parseTeacher(const QDomElement &element) { auto children = element.childNodes(); auto firstChild = children.at(0).toElement(); auto secondChild = children.at(1).toElement(); int age = firstChild.text().toInt(); m_teachers.emplace_back(element.attribute("name").toStdString(), age, secondChild.text().toStdString()); } void DomParser::parseStudent(const QDomElement &element) { auto children = element.childNodes(); auto firstChild = children.at(0).toElement(); auto secondChild = children.at(1).toElement(); int age = firstChild.text().toInt(); m_students.emplace_back(element.attribute("name").toStdString(), age, secondChild.text().toStdString()); }
3. 採用QXmlSimpleReader方式,也就是回調函數方式:spa
#include <QXmlDefaultHandler> #include "schooldefine.h" class SaxHandler : public QXmlDefaultHandler { public: SaxHandler(); bool readFile(const QString &fileName); void printAllMembers(); protected: bool startElement(const QString &namespaceURI, const QString &localName, const QString &qName, const QXmlAttributes &atts) override; bool endElement(const QString &namespaceURL, const QString &localName, const QString &qName) override; bool characters(const QString &ch) override; bool fatalError(const QXmlParseException &exception) override; private: bool m_isStudent = false; QString m_currentContext; std::vector<School::Teacher> m_teachers; std::vector<School::Student> m_students; };
#include "saxhandler.h" #include <iostream> SaxHandler::SaxHandler() { } bool SaxHandler::readFile(const QString &fileName) { QFile file(fileName); QXmlInputSource inputSource(&file); QXmlSimpleReader reader; reader.setContentHandler(this); reader.setErrorHandler(this);; return reader.parse(inputSource); } void SaxHandler::printAllMembers() { std::cout << "All teachers: " << std::endl; for (const auto& teacher : m_teachers) { School::print(std::cout, teacher); } std::cout << "All students: " << std::endl; for (const auto& student : m_students) { School::print(std::cout, student); } } bool SaxHandler::startElement(const QString &namespaceURI, const QString &localName, const QString &qName, const QXmlAttributes &atts) { if (qName == "teacher") { m_isStudent = false; } else if (qName == "student") { m_isStudent = true; } else if (qName == "entry") { if (m_isStudent) { m_students.push_back(School::Student("", 0, "")); m_students.back().name = atts.value("name").toStdString(); } else { m_teachers.push_back(School::Teacher("", 0, "")); m_teachers.back().name = atts.value("name").toStdString(); } } else if (qName == "age") { m_currentContext.clear(); } else if (qName == "sport") { m_currentContext.clear(); } return true; } bool SaxHandler::characters(const QString &ch) { m_currentContext += ch; return true; } bool SaxHandler::endElement(const QString &namespaceURL, const QString &localName, const QString &qName) { if (qName == "age") { if (m_isStudent) { m_students.back().age = m_currentContext.toInt(); } else { m_teachers.back().age = m_currentContext.toInt(); } } else if (qName == "sport") { if (m_isStudent) { m_students.back().loveSport = m_currentContext.toStdString(); } else { m_teachers.back().loveSport = m_currentContext.toStdString(); } } m_currentContext.clear(); return true; } bool SaxHandler::fatalError(const QXmlParseException &exception) { std::cerr << "Parse error at line" << exception.lineNumber() << ", " << "column " << exception.columnNumber() << ": " << qPrintable(exception.message()) << std::endl; return false; }
下面簡單對上述三種方式予以說明:code
(1) 從代碼行數來看,採用DOM和QXmlSimpleReader的方式,代碼行數比較少,而QXmlStreamReader代碼行數較多。xml
(2) 從代碼邏輯分析來看,採用DOM方式最容易理解,採用QXmlStreamReader的方式稍微難理解一些,而採用QXmlSimpleReader因爲使用了較多的回調,引入了大量的類數據成員,使得代碼會很難理解。blog
(3) 從內存佔用來看,DOM的方式會耗費最多的內存,由於須要一次性將全部的內容構建成樹,DOM和QXmlSimpleReader對內存要求都較低。
(4) 從運行時間消耗來看,DOM的消耗,可能會稍微大一些,由於DOM正常要經歷2次的遍歷,一次遍歷構建樹,一次遍歷,構建本身須要的數據。而QXmlSimpleReader和QXmlStreamReader正常只須要遍歷一次。
(5) 從處理異常來看,DOM和QXmlStreamReader應該會更容易一些,由於不涉及回調函數,可是對於xml來講,不少時候主要確認內容正確與否,若是錯誤就退出,查看xml中的錯誤。固然,這個也是比較重要的項。
對於我來講,由於大多數狀況下,解析的xml不是很大,並且基本只涉及加載過程當中,因此使用DOM的狀況比較多。若是xml比較大,或者調用比較頻繁,能夠考慮使用QXmlStreamReader的方式。