QXmlStreamReader
QXmlStreamReader クラスは、単純なストリーミング API を通じて XML ファイルを高速に読み取る方法を提供します。Qt 自体で使用される SAX 解析方法よりも高速です。
いわゆるストリーミング読み取りとは、SAX と同様に、XML ドキュメントを一連のマークされたストリームに読み取ることです。QXmlStreamReader クラスと SAX の主な違いは、これらのタグの解析方法です。SAX 解析を使用する場合、アプリケーションはパーサーからの一連のいわゆる XML イベントを処理するためにいくつかのプロセッサ (コールバック関数) を提供する必要があり、異なる XML タグは対応する処理に対して異なるイベントをトリガーします。QXmlStreamReader を使用すると、アプリケーション自体がループ全体を駆動し、パーサーから XML タグを 1 つずつ取得できます。このアクションは、次の完全なトークンを読み取り、次にその tokenType() を読み取る readNext() によって実行できます。次に、isStartElement()、text() などの一連の便利な関数を使用して、特定の読み取りコンテンツを決定または取得できます。このプル モード (プル) 解析方法の利点は、XML ドキュメントの解析を複数の関数に分割し、異なるタグに個別の関数を使用できることです。
QXmlStreamReader クラスの一般的な使用法は次のとおりです。
QXmlStreamReader xml;
...
while (!xml.atEnd()) {
xml.readNext();
... // do processing
}
if (xml.hasError()) {
... // do error handling
}
解析中にエラーが発生した場合、atEnd() と hasError() は true を返し、error() は発生した特定のエラーの種類を返します。errorString()、lineNumber()、columnNumber()、およびcharacterOffset()関数を使用して、特定のエラー情報を取得できます。通常、これらの関数を使用して、ユーザーに特定のエラー情報の入力を求めるエラー文字列を構築します。同時に、アプリケーション コードを簡素化するために、QXmlStreamReader は、必要に応じてカスタム エラー メッセージをトリガーできる raiseError() メカニズムも提供します。
QXmlStreamReader は増分パーサーです。たとえば、xml ファイルが複数のファイルまたはネットワークから取得された場合など、ドキュメントを一度に処理できない状況に対処できます。QXmlStreamReader がすべてのデータを解析しても XML ドキュメントが不完全な場合、PrematureEndOfDocumentError タイプのエラーが返されます。その後、さらにデータが到着すると、このエラーから回復し、readNext() の呼び出しに進み、新しいデータを解析します。
QXmlStreamReader は、XML ドキュメント ツリー全体をメモリに保存せず、現在解析中のタグのみをメモリに保存するため、メモリをあまり消費しません。さらに、QXmlStreamReader は、実際の QString オブジェクトではなく QStringRef を使用してすべての文字列データを解析します。これにより、不必要な小さな文字列メモリ割り当てコストを回避できます。QStringRef は、QString またはその部分文字列の単純なラッパーであり、QString クラスに似たいくつかの API を提供しますが、メモリを割り当てず、下部で参照カウントを使用してデータを共有します。必要に応じて QStringRef の toString() を呼び出して実際の QString オブジェクトを取得できます。
XMLファイルを読む
例.xml
<?xml version="1.0" encoding="UTF-8"?>
<labels map="demo1" ver="1.0">
<label id="1802232">
<x>1568</x>
<y>666</y>
</label>
<label id="1802230">
<x>1111</x>
<y>622</y>
</label>
</labels>
#ifndef XMLREAGER_H
#define XMLREAGER_H
#include <QXmlStreamReader>
class xmlreader
{
public:
xmlreader();
bool readFile(const QString &fileName);
private:
void readlabelsElement(); //读取label标签
void readlabelElement(); //读取label标签
void readxElement(); //读取x标签
void readyElement(); //读取y标签
void skipUnknownElement(); //跳过未知标签
QXmlStreamReader reader;
};
#endif // XMLREAGER_H
#include "xmlreader.h"
#include <iostream>
#include <QDebug>
#include <QFile>
xmlreader::xmlreader()
{
}
bool xmlreader::readFile(const QString &fileName)
{
//以只读和文本方式打开文件,如果打开失败输出错误日志,并且返回false
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;
}
//将文件设置成xml阅读器的输入设备
reader.setDevice(&file);
reader.readNext(); //直接读取下一个节点,因为首先读到的标签是XML文件的头部(第一行)
while (!reader.atEnd()) //外部循环,未到文件结尾就一直循环读取
{
if (reader.isStartElement()) //外部分支,如果不是起始标签,则直接读取下一个节点
{
if (reader.name() == "labels") //内部分支,如果根节点不是 == labels,
//说明读取的文件是错误的
{
qDebug() << reader.name();//通过qDebug()输出当前节点的名字,这里输出labels
readlabelsElement(); //读取labels节点的内容
}
else
{ //raiseError()函数用来自定义输出错误日志的内容,这里输出Not a labels file
reader.raiseError(QObject::tr("Not a labels file"));
}
}
else
{
reader.readNext();
}
}
//关闭文件,如果读取发生错误(hasError())或者文件有错误,输出错误信息,返回false,
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 xmlreader::readlabelsElement()
{
reader.readNext();//读取了根节点labels后,继续读取下一个节点
while (!reader.atEnd())
{
if (reader.isEndElement())
{
reader.readNext();
break; //如果是结束节点,则结束循环
//循环执行下去,读到的第一个结束节点是</labels>,而不是</label>;
//这是执行readlabelElement()函数中得到的结果,当读到</label>时,
//该函数跳出循环并读取下一个节点,而下一个节点是<label>或者</labels>
}
if (reader.isStartElement())
{
if (reader.name() == "label")
{ //获得label的attributes()值,也就是id,转换成字符串输出
qDebug() << reader.attributes().value("id").toString();
qDebug() << reader.name();
readlabelElement();
}
else
{
skipUnknownElement();//未知节点直接跳过
}
}
else
{
reader.readNext();
}
}
}
void xmlreader::readlabelElement()
{
reader.readNext();
while (!reader.atEnd())
{
if (reader.isEndElement())
{
reader.readNext();
break;
}
if (reader.isStartElement())
{
if (reader.name() == "x")
{
readxElement();
}
else if (reader.name() == "y")
{
readyElement();
}
else
{
skipUnknownElement();
}
}
else
{
reader.readNext();
}
}
}
void xmlreader::readxElement()
{
QString x = reader.readElementText();
qDebug() <<"x:" << x;
if (reader.isEndElement())
reader.readNext();
}
void xmlreager::readyElement()
{
QString y = reader.readElementText();//执行这个函数以后,y获得了坐标值,并且当前节点
//自动变成结束节点</y>
qDebug() << "y:" << y;
if (reader.isEndElement())
reader.readNext(); //在这里,读取下一个节点,就是</label>
}
//是一个递归函数
void xmlreader::skipUnknownElement()
{
reader.readNext();
while (!reader.atEnd()) {
if (reader.isEndElement()) {
reader.readNext();
break;
}
if (reader.isStartElement()) {
skipUnknownElement();//函数的递归调用
} else {
reader.readNext();
}
}
}
#include <QtCore/QCoreApplication>
#include "xmlreader.h"
#include <iostream>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
xmlreader reader;
reader.readFile("labels.xml");
return a.exec();
}
読み取り結果は次の図に示されます。
教師.xml:
その中には、学校(スクール)の3階(floor3)の先生の情報と、生徒の情報があります。
教師6名、生徒1名
<?xml version="1.0" ?>
<school>
<floor3 id="3" time="2019/10/11">
<teacher>
<entry name="Job">
<age>30</age>
<sport>soccer</sport>
</entry>
<entry name="Tom">
<age>32</age>
<sport>swimming</sport>
</entry>
</teacher>
<teacher>
<entry name="Job2">
<age>30</age>
<sport>soccer</sport>
</entry>
<entry name="Tom">
<age>32</age>
<sport>swimming</sport>
</entry>
</teacher>
<teacher>
<entry name="Job3">
<age>30</age>
<sport>soccer</sport>
</entry>
<entry name="Tom">
<age>32</age>
<sport>swimming</sport>
</entry>
</teacher>
<teacher>
<entry name="Job4">
<age>30</age>
<sport>soccer</sport>
</entry>
<entry name="Tom">
<age>32</age>
<sport>swimming</sport>
</entry>
</teacher>
<teacher>
<entry name="Job5">
<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>
<teacher>
<entry name="Job6">
<age>30</age>
<sport>soccer</sport>
</entry>
<entry name="Tom">
<age>32</age>
<sport>swimming</sport>
</entry>
</teacher>
</floor3>
</school>
知らせ:
移植後に使用できる関数内のコードに焦点を当てています。
「カウントに使用される」変数を整理することを忘れないでください。
ファイルのアドレスもあり、これは置き換える必要があります。
まず XML ファイルの内容が変数に読み込まれ、次に変数の内容が分析されます。
XMLファイルのエンコード形式に大きく影響されますので、漢字化け現象が発生する場合は読み込めなくなる可能性があるので注意してください。
#include <QtCore/QCoreApplication>
#include <QXmlStreamReader>
#include <QFile>
#include <iostream>
void ReadXml()
{
//用来计数
int teacherCount = 0;
int ageCount = 0;
int sanlouCount = 0;
int schoolCount = 0;
//读取文件
QString fileName = "D:/JBXML/teachers.xml";
QFile file(fileName);
if (!file.open(QFile::ReadOnly | QFile::Text))
{
return ;
}
//QXmlStreamReader操作任何QIODevice.
QXmlStreamReader xml(&file);
//解析XML,直到结束
while (!xml.atEnd() && !xml.hasError())
{
//读取下一个element.
QXmlStreamReader::TokenType token = xml.readNext();
/*以下内容用于分析读取的内容,可以将每一个读取到的标签名字打印出来*//*
if (token == QXmlStreamReader::Invalid)
{
//如果有读取错误,将被打印出来
std::cout << xml.errorString().toStdString();
}
std::cout << xml.tokenString().toStdString() << "\t";
std::cout << xml.name().toString().toStdString() << std::endl;*/
/*显示这个分析过程,你会看到很清晰的读取过程*/
//如果获取的仅为StartDocument,则进行下一个
if (token == QXmlStreamReader::StartDocument)
{
continue;
}
//如果获取了StartElement,则尝试读取
if (token == QXmlStreamReader::StartElement)
{
//如果为person,则对其进行解析
if (xml.name() == "teacher")
{
teacherCount++;
}
if (xml.name() == "age")
{
ageCount++;
}
if (xml.name() == "floor3")
{
sanlouCount++;
}
if (xml.name() == "school")
{
schoolCount++;
}
}
}
if (xml.hasError())
{
//QMessageBox::information(NULL, QString("parseXML"), xml.errorString());
}
file.close();
std::cout << teacherCount << " teacher" << std::endl;
std::cout << ageCount << " ages" << std::endl;
std::cout << sanlouCount << " 3rdFloors" << std::endl;
std::cout << schoolCount << " schools" << std::endl;
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
ReadXml();
return a.exec();
}
XMLファイルでは、各タグのタグ、つまり山括弧内の情報を取得して判定することができます。
「先生」であればカウントされ、合計6人の先生がいることがわかります。
実行結果を次の図に示します。
途中のコメント化されたコードを開くと、各ラベルが読み取られ、読み取りプロセスが出力されるのが確認できます。実行結果を次の図に示します。