qt解析xml(1)-----QXmlStreamReader

XML

l  XML(eXtensible Markup Language,可扩展标记语言)是普通用于数据交换和数据存储的一种多用途文本文件格式;SVG(可标量矢量图形)XML格式,QtSvg模块提供了可用于载入并呈现SVG图像的类;

l  MathML(数学标记语言)XML格式的绘制文档,可以使用Qt Solution中的QtMmlWidget操作;

l  由万维网协会开发。

Qt对XML数据处理的三种方式:

1、  QXmlSreamReader:

  QXmlStreamReader:一种快速的基于流的方式访问良格式 XML 文档,特别适合于实现一次解析器(所谓“一次解析器”,可以理解成我们只需读取文档一次,然后像一个遍历器从头到尾一次性处理 XML 文档,期间不会有反复的情况,也就是不会读完第一个标签,然后读第二个,读完第二个又返回去读第一个,这是不允许的);

2、 DOM文档对象模型

  将整个 XML 文档读入内存,构建成一个树结构,允许程序在树结构上向前向后移动导航,这是与另外两种方式最大的区别,也就是允许实现多次解析器(对应于前面所说的一次解析器)。DOM 方式带来的问题是需要一次性将整个 XML 文档读入内存,因此会占用很大内存;

3、 SAX XML简单应用程序编程接口

  提供大量虚函数,以事件的形式处理 XML 文档。这种解析办法主要是由于历史原因提出的,为了解决 DOM 的内存占用提出的(在现代计算机上,这个一般已经不是问题了)。

 

1、使用QXmlStreamReader读取XML

适用于诸如查找XML文档中一个给定的标记符出现的次数,读取内容容纳不了的特大文件,组装定制的数据结构以反映XML文档的内容等。

在.pro文件中添加QT += xml,并加如相应的头文件#include <QDomDocument>或者#include <QXmlStreamReader>。

流读取器的基本概念是将XML文档报告为tokens,类似于SAX。 QXmlStreamReader和SAX之间的主要区别是如何报告这些XML tokens。使用SAX,应用程序必须提供处理程序(回调函数),在解析器方便时从解析器接收所谓的XML事件。使用QXmlStreamReader,应用程序代码本身驱动循环,并从读取器,一个接一个地,因为它需要它们。这通过调用readNext()来完成,读取器从输入流读取,直到它完成下一个token,此时它返回tokenType()。然后可以使用一组方便的函数,包括isStartElement()和text()来检查token以获取有关已读取的信息。这种拉方法的最大优点是可以使用它构建递归下降解析器,这意味着您可以将XML解析代码轻松地分成不同的方法或类。这使得在解析XML时可以轻松跟踪应用程序自己的状态。

各节点获取方式

<?xml version="1.0" encoding="UTF-8"?>

<COMMAND>

    <OBJECT>USER</OBJECT>

    <ACTION>LOGIN</ACTION>

    <DATA>

        <USER NAME="root" PASSWORD="123456"/>

    </DATA>

</COMMAND>

对应各节点获取方法

“ACTION”: 使用Name:m_pReader->name()

“LOGIN”:对应ElementText使用:m_pReader->readElementText();

“USER”:Name

“NAME”和“PASSWORD”: QXmlStreamAttributes  m_pReader->attributes(); 可以使用attributes.hasAttribute("NAME")判断是否有节点,attributes.value("NAME").toString();读取其值。

readNext:读取下一个token :返回QXmlStreamReader::TokenType,可于判断节点类型。

isStartElement():是否是开始节点

readElementText();获取数据

如果在解析时发生错误,atEnd()和hasError()返回true,而error()返回发生的错误。 函数errorString(),lineNumber(),columnNumber()和characterOffset()用于构造相应的错误或警告消息。 为了简化应用程序代码,QXmlStreamReader包含一个raiseError()机制,让您提出自定义错误,触发同样的错误处理描述。

增量解析

QXmlStreamReader是一个增量解析器。它可以处理文档不能一次解析的情况,因为它到达块(例如从多个文件或通过网络连接)。当读取器在完整文档被解析之前缺少数据时,它报告一个PrematureEndOfDocumentError。当更多的数据到达时,由于调用addData()或者由于通过网络设备()有更多的数据可用,阅读器从PrematureEndOfDocumentError错误中恢复,并继续下一次调用readNext()来解析新的数据。

例如,如果您的应用程序使用网络访问管理器从网络读取数据,您将向管理器发出网络请求,并作为回应接收网络回复。由于QNetworkReply是一个QIODevice,您将其readyRead()信号连接到自定义插槽,例如。 slotReadyRead()在QNetworkAccessManager的讨论中显示的代码片段。在这个插槽中,您使用readAll()读取所有可用数据,并使用addData()将其传递到XML流读取器。然后调用自定义解析函数,从阅读器读取XML事件。 

性能和内存消耗

QXmlStreamReader是内存保守的设计,因为它不存储整个XML文档树在内存中,但只有当前的令牌在报告时。 此外,QXmlStreamReader避免了许多小字符串分配,它通常需要将XML文档映射到一个方便的Qt-ish API。 它通过报告所有字符串数据作为QStringRef而不是真正的QString对象来做到这一点。 QStringRef是一个围绕QString子字符串的薄包装,它提供了QString API的一个子集,而没有内存分配和引用计数开销。 对任何这些对象调用toString()会返回一个等效的真正的QString对象。  

使用具体流程是:

1. 创建一个QXmlStreamReader的类对象

2. 通过setDevice()设置好要处理的XML文件

3. 通过readNext()挨个读入节点

4.通过isStartElement()和isEndElement()判断是节点的开始和结束

5可以通过name()得到当前节点名字

6可以通过readElementText()访问当前节点的内容

7通过attributes()获取含有属性的节点的属性

 

实操:

#ifndef STREAMPARSEXML_2_H
#define STREAMPARSEXML_2_H

#include <QXmlStreamReader>

class StreamParseXML_2
{
public:
    StreamParseXML_2();
    ~StreamParseXML_2();

    void readXML();

    void readXML_2();
private:
    QString m_sFileName;
    QXmlStreamReader *m_pReader;
};

#endif // STREAMPARSEXML_2_H

 

/*读取流程
2、QXmlStreamReader接口说明

创建一个QXmlStreamReader的类对象
通过setDevice()设置好要处理的XML文件
通过readNext()挨个读入节点,
通过isStartElement()和isEndElement()判断是节点的开始和结束.

通过name()得到当前节点名字
通过readElementText()访问当前节点的内容

通过attributes()获取含有属性的节点的属性
*/

/*
<?xml version="1.0" encoding="UTF-8"?>
<Msg>
    <Data id="1">
        <title>
            <xmlname>hello.xml</xmlname>
            <owner>zhj</owner>
        </title>
        <Number>123456</Number>
        <Name>zhangjie</Name>
        <email>zhangjie@sina.cn</email>
        <website>zhangjie.hello.cn</website>
    </Data>
    <Data id="2">
        <Number>789012</Number>
        <Name>haier</Name>
        <email>haier@sina.cn</email>
        <website>haier.sina.cn</website>
    </Data>
    <Data id="3">
        <Number>345678</Number>
        <Name>sum</Name>
        <email>sum@sina.cn</email>
        <website>sum.sina.cn</website>
    </Data>
    <Data id="4">
        <Number>901234</Number>
        <Name>niil</Name>
        <email>niil@sina.cn</email>
        <website>niil.sina.cn</website>
    </Data>
</Msg>

*/
#include "StreamParseXML_2.h"
#include <QApplication>
#include <QFile>
#include <QDebug>

StreamParseXML_2::StreamParseXML_2()
{
    m_sFileName = qApp->applicationDirPath() + "/" + "Test2.xml";
}

StreamParseXML_2::~StreamParseXML_2()
{

}

void StreamParseXML_2::readXML()
{
    QFile file(m_sFileName);
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
    {
        qDebug()<<"Open file hello.xml failure";
        return  ;
    }

    // 创建对象
    QXmlStreamReader *pReader = new QXmlStreamReader(&file);
    while (!pReader->atEnd())
    {
//        qDebug() << pReader->text() << pReader->readElementText()<<pReader->name();
        // 判断节点是否是开始节点
        if (pReader->isStartElement())
        {
            /*此段代码存在逻辑漏洞,因为第一次进来的时候是MSG几点,然后获取所有的attribute,全部都是
             * data节点,这样处理是不合适,如果msg下面有一堆的如
                <Number>123456</Number>
                <Name>zhangjie</Name>
                那么将会读取到这些数据,所以应该是拆分成子函数,
                判断是否是data节点,if (pReader->name() == "Data"),然后才进入下面的详细的代码
                修改参见readXML2()
             *
             *
             */
            qDebug() << pReader->name();
            // 将属性读出 例如id = 3 age = 23
            QXmlStreamAttributes attributes = pReader->attributes();
            // 判断是否存在属性id
            if (attributes.hasAttribute("id"))
            {
                qDebug() << "id:" << attributes.value("id").toString();
            }
            // 判断当前节点的名字是否为number
            if (pReader->name() == "Number")
            {
                qDebug() << "Number:" << pReader->readElementText();
            }
            //判断当前节点的名字是否为Name
            else if (pReader->name() == "Name")
            {
                qDebug() << "Name:" << pReader->readElementText();
            }
            //判断当前节点的名字是否为email
            else if(pReader->name() == "email")
            {
                qDebug() << "email:" << pReader->readElementText();
            }
            //判断当前节点的名字是否为website
            else if(pReader->name() == "website")
            {
                qDebug() << "website:" << pReader->readElementText();
            }
        }
        //节点结束、并且节点名字为Data(含有子节点)
        else if(pReader->isEndElement() && pReader->name() == "Data")
        {
            qDebug() << "--------------------";
        }
        pReader->readNext();
    }
    file.close();
    pReader->clear();
    delete pReader;
    pReader = NULL;
}

void StreamParseXML_2::readXML_2()
{
    QFile file(m_sFileName);
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
    {
        qDebug()<<"Open file hello.xml failure";
        return  ;
    }

    // 创建对象
    QXmlStreamReader *pReader = new QXmlStreamReader(&file);
    while (!pReader->atEnd())
    {
        // 判断节点是否是开始节点
        if (pReader->isStartElement())
        {
            qDebug() << pReader->name();
            if (pReader->name() == "Data")
            {
                // 将属性读出 例如id = 3 age = 23
                QXmlStreamAttributes attributes = pReader->attributes();
                // 判断是否存在属性id
                if (attributes.hasAttribute("id"))
                {
                    qDebug() << "id:" << attributes.value("id").toString();
                }
                // 判断当前节点的名字是否为number
                if (pReader->name() == "Number")
                {
                    qDebug() << "Number:" << pReader->readElementText();
                }
                //判断当前节点的名字是否为Name
                else if (pReader->name() == "Name")
                {
                    qDebug() << "Name:" << pReader->readElementText();
                }
                //判断当前节点的名字是否为email
                else if(pReader->name() == "email")
                {
                    qDebug() << "email:" << pReader->readElementText();
                }
                //判断当前节点的名字是否为website
                else if(pReader->name() == "website")
                {
                    qDebug() << "website:" << pReader->readElementText();
                }
            }
        }
        //节点结束、并且节点名字为Data(含有子节点)
        else if(pReader->isEndElement() && pReader->name() == "Data")
        {
            qDebug() << "--------------------";
        }
        pReader->readNext();
    }
    file.close();
    pReader->clear();
    delete pReader;
    pReader = NULL;
}

 

因为QXmlStreamReader读取的时候使用pReader->readNext();读取下一行,所以读取的时候只会不断的读取下一行记录,不会判断是否退出,导致直接到xml最后,下面是判断是否结束的例子:

#ifndef STREAMPARSEXML_H
#define STREAMPARSEXML_H
#include <QXmlStreamReader>

class StreamParseXML
{
public:
    StreamParseXML();
    ~StreamParseXML();

    void readXML();
    void writeXML();

private:
    void parseUserInformation();
    QString getValue(const QString &name);

private:
    QString m_sFileName;
    QXmlStreamReader *m_pReader;
};

#endif // STREAMPARSEXML_H
#include "StreamParseXML.h"
#include <QFile>
#include <QMessageBox>
#include <QDebug>
#include <QApplication>
#include <QFileInfo>

/*
<?xml version="1.0" encoding="UTF-8"?>
<COMMAND>
    <OBJECT>USER</OBJECT>
    <ACTION>LOGIN</ACTION>
    <DATA>
        <USER NAME="root" PASSWORD="123456"/>
    </DATA>

    <OBJECT>USER</OBJECT>
    <ACTION>LOGIN</ACTION>
    <DATA>
        <USER NAME="root1" PASSWORD="1234567"/>
    </DATA>
</COMMAND>

*/

StreamParseXML::StreamParseXML()
{  
    qDebug() << qApp->applicationDirPath();
    qDebug() << qApp->applicationDisplayName();
    qDebug() << qApp->applicationName();
    qDebug() << qApp->applicationFilePath();
    m_sFileName = qApp->applicationDirPath() + "/" + "streamparse.xml";
}

StreamParseXML::~StreamParseXML()
{

}

void StreamParseXML::readXML()
{
    if (m_sFileName.isEmpty())
        return;

    QFile *pFile = new QFile(m_sFileName);
    if (!pFile->open(QIODevice::ReadOnly | QFile::Text))
    {
        QMessageBox::information(NULL, QString("title"), QString("open error!"));
        return;
    }

    m_pReader = new QXmlStreamReader(pFile);
    while (!m_pReader->atEnd() && !m_pReader->hasError())
    {
        m_pReader->lineNumber();
        QXmlStreamReader::TokenType token = m_pReader->readNext();
        if (token == QXmlStreamReader::StartDocument)
            continue;

        qDebug() << m_pReader->text();
        if (m_pReader->isStartElement() && m_pReader->name() == "OBJECT")
        {
            QString elementText = m_pReader->readElementText();
            if (elementText == "USER")
            {
                parseUserInformation();
            }
        }
    }
    if (m_pReader->hasError())
    {
        qDebug() << m_pReader->errorString();
    }
    m_pReader->clear();
    delete m_pReader;
    m_pReader = NULL;
    pFile->close();
    delete pFile;
    pFile = NULL;
}

void StreamParseXML::writeXML()
{
    QFileInfo fileInfo(m_sFileName);
    QString sFileName = fileInfo.absolutePath() + "/" + "Write.xml";
    QFile file(sFileName);
    if (file.open(QIODevice::WriteOnly | QIODevice::Text))
    {
        QXmlStreamWriter writer(&file);
        writer.setAutoFormatting(true);
        writer.writeStartDocument();
        writer.writeStartElement("COMMAND");
        writer.writeTextElement("OBJECT", "USER");
        writer.writeTextElement("ACTION", "LOGIN");
        writer.writeStartElement("DATA");
        writer.writeStartElement("USER");
        writer.writeAttribute("NAME", "root");
        writer.writeAttribute("PASSWORD", "123456");
        writer.writeComment("AAAA"); // 添加注释
        writer.writeCDATA("DDDDD"); // 添加CDDATA
        writer.writeCharacters("zhongjianshuju");
//        writer.writeStartElement("Text");
        writer.writeTextElement("Text", "123456789");// 添加一行
//        writer.writeEndElement();
        writer.writeEndElement();
        writer.writeEndElement();
        writer.writeEndElement();
        file.close();
    }
    return ;
}

void StreamParseXML::parseUserInformation()
{
    QString elementString = getValue("ACTION");
    if (elementString == "LOGIN")
    {
        while(!m_pReader->atEnd())
        {
            m_pReader->readNext();
            qDebug() << "**********";
            qDebug() << m_pReader->lineNumber();
            qDebug() << m_pReader->name();
            qDebug() << m_pReader->isEndElement();

            // 用于判断当前标签结束了,那就不继续走了,否则会在这里一直到结束
            if ("DATA" == m_pReader->name() && m_pReader->isEndElement())
                break;

            if (m_pReader->name() == "USER")
            {
                QXmlStreamAttributes attributes = m_pReader->attributes();
                if (attributes.hasAttribute("NAME"))
                {
                    qDebug() << "USER=" << attributes.value("NAME").toString();
                }

                if (attributes.hasAttribute("PASSWORD"))
                {
                    qDebug() << "PASSWORD=" << attributes.value("PASSWORD").toString();
                }
            }
        }
    }
}

QString StreamParseXML::getValue(const QString &name)
{
    while(!m_pReader->atEnd())
    {
        m_pReader->readNext();
        if (m_pReader->isStartElement() && m_pReader->name() == name)
        {
            return m_pReader->readElementText();
        }
    }
    return "";
}

 

posted on 2017-04-05 09:27  965452300  阅读(2066)  评论(0编辑  收藏  举报