用DOM方式读取xml

DOM

DOM 是由 W3C 提出的一种处理 XML 文档的标准接口。Qt 实现了 DOM Level 2 级别的不验证读写 XML 文档的方法。DOM 一次性读入整个 XML 文档,在内存中构造为一棵树(被称为 DOM 树)。我们能够在这棵树上进行导航,比如移动到下一节点或者返回上一节点,也可以对这棵树进行修改,或者是直接将这颗树保存为硬盘上的一个 XML 文件。

示例1

在这里插入图片描述

头文件

QDomDocument doc;
QStringList PlaySongName;
QString historyName;
QStringList pathList;
QString fileName = "Song.xml";
//写XML
void WriteXml(QStringList &MusicPaths);
//读XML
void readXml(QString& fileName, QDomDocument &XmlDoc);
void addXml(const QString & name, const QString & text, QDomDocument &document);
void AnalysisListData(QDomDocument &xmldoc,QFile &File);
QString DeleteFileNameSpecialCharacters(QString &songName);

void WriteXml(QStringList &MusicPaths);

void MainWindow::WriteXml(QStringList &MusicPaths)
{
    doc.clear();
 
    if (doc.toString().compare("")==0){
        //创建根节点
        QDomElement root = doc.createElement("Song");
        //添加根节点
        doc.appendChild(root);
    }
//    QStringList::iterator iter;
    int k = 0,i = 0;
    for(i = 0;i < MusicPaths.size();++i)
    {
        //分离出多个MP3文件路径中每个文件的所有信息
        QString musicPath = MusicPaths.at(k++);
        //从MP3文件信息中获取文件名及文件格式信息
        QString FileName = QFileInfo( musicPath ).fileName();
        QString songName = DeleteFileNameSpecialCharacters(FileName);
        addXml(songName,musicPath,doc);
        //qDebug()<<"i="<<i<<" size="<<MusicPaths.size();
    }
//    fileName = "Song.xml";
    QFile file(fileName);
    if (!file.open(QIODevice::WriteOnly| QIODevice::Text))
        return ;
    //将文本流保存到文件,4为子元素缩进的字符数
    QTextStream out(&file);
    doc.save(out, 4, QDomNode::EncodingFromTextStream);
    file.close();
   readXml(fileName,doc);
}

void readXml(QString& fileName, QDomDocument &XmlDoc);

void MainWindow::readXml(QString &fileName,
                         QDomDocument &XmlDoc)
{
    QFile file(fileName);
 
    qDebug()<<fileName<<" readonly history list";
    if (!file.open(QIODevice::ReadOnly)){
//        qDebug()<<file.errorString();
//        qDebug()<<file.error();
//        qDebug()<<"open file failed";
        return ;
    }
    AnalysisListData(XmlDoc,file);
    QDomElement root = XmlDoc.documentElement();//读取根节点
    //读取第一个子节点
    QDomNode node = root.firstChild();
    //遍历节点
    QDomNodeList list = root.childNodes();
    int count = list.count();
    qDebug() << "node count:" << count;
    int i = 0;
    while (i < count)
    {
        //获取节点的内容
        QString data = node.toElement().text();
//        qDebug () << "read == "<<data;
        m_playList->addMedia(QUrl::fromLocalFile(data));
//        PlaySongName.push_back(QFileInfo( data ).fileName());
        historyName = data.split("/").last();
        ui->MusicWidget->addItem(historyName);
        //读取下一个兄弟节点
        node = node.nextSibling();
        node.nextSiblingElement();
        i++;
    }
    file.close();
}

void addXml(const QString & name, const QString & text, QDomDocument &document);

void MainWindow::addXml(const QString &name,
                        const QString &text,
                        QDomDocument &document)
{
    //读取根节点
    QDomElement root = document.documentElement();
    //创建元素节点
    QDomElement newChild = document.createElement(name);
    //添加元素节点到根节点
    root.appendChild(newChild);
    //创建元素文本
    QDomText newChildText = document.createTextNode(text);
 
    //添加元素文本到元素节点
    newChild.appendChild(newChildText);
}

void MainWindow::AnalysisListData(QDomDocument &xmldoc, QFile &File)

void MainWindow::AnalysisListData(QDomDocument &xmldoc, QFile &File)
{
    QString errorStr;
    int errorLine = 0,errorCol = 0;
    if(!xmldoc.setContent(&File,true,&errorStr,&errorLine,&errorCol))
    {
        //如果出错,则会进入这里。errorStr得到的是出错说明,errorLine和errorCol则是出错的行和列
        qDebug() << errorStr << "line: " << errorLine << "col: " << errorCol;
        return;
    }
}

QString MainWindow::DeleteFileNameSpecialCharacters(QString &songName)

QString MainWindow::DeleteFileNameSpecialCharacters(QString &songName)
{
    //处理特殊字符等(可自定义)
    songName = songName.replace(' ',"").replace(':',"");
    songName = songName.replace('-',"").replace('(',"");
    songName = songName.replace(')',"").replace('.',"");
    return songName;
}

示例2

一个汽车销售记录的xml,以时间为顺序进行记录。

xml中用这个作为一个节点 : …

下面给出这个xml的树形结构图:
在这里插入图片描述

<?xml version='1.0' encoding='utf-8'?>
<日销售清单>
    <日期 Date="2018--06--17">
        <时间 time="11--33--22">
            <工厂>一汽大众</工厂>
            <品牌>奥迪A6</品牌>
            <报价>36</报价>
            <数量>1</数量>
            <金额>36</金额>
        </时间>
        <时间 time="11--33--25">
            <工厂>一汽大众</工厂>
            <品牌>奔驰</品牌>
            <报价>83</报价>
            <数量>2</数量>
            <金额>166</金额>
        </时间>
        <时间 time="11--33--31">
            <工厂>二汽神龙</工厂>
            <品牌>毕加索</品牌>
            <报价>39</报价>
            <数量>2</数量>
            <金额>78</金额>
        </时间>
        <时间 time="11--33--35">
            <工厂>二汽神龙</工厂>
            <品牌>富康</品牌>
            <报价>28</报价>
            <数量>2</数量>
            <金额>56</金额>
        </时间>
        <时间 time="11--33--40">
            <工厂>二汽神龙</工厂>
            <品牌>标致307</品牌>
            <报价>27</报价>
            <数量>3</数量>
            <金额>81</金额>
        </时间>
    </日期>
</日销售清单>
#include "domxml.h"
#include <QFile>
#include <QDomDocument> //文件
#include <QDomProcessingInstruction> //格式头部
#include <QDomElement>  //元素
#include <QTextStream> //文件流
#include <QDebug>
#include <QDateTime>
#define cout qDebug() << "[" << __FILE__ << ":" << __LINE__ << "]"
 
DomXML::DomXML()
{
 
}
 
//创建xml空文件
void DomXML::createXML(QString filePath)
{
    QFile file(filePath); //关联文件名字
    if( true == file.exists() ) //如果存在不创建
    {
        cout << "文件已经存在";
        return;
    }
    else
    { //不存在才创建
        //只写方式打开文件
        bool isOk = file.open(QIODevice::WriteOnly);
        if(true == isOk)//如果打开成功
        {
            //创建xml文档对象
            QDomDocument doc;
            //创建xml头部格式 <?xml version='1.0' encoding='utf-8'?>
            QDomProcessingInstruction ins;
            ins = doc.createProcessingInstruction("xml", "version=\'1.0\' encoding=\'utf-8\'");
            //追加元素
            doc.appendChild(ins);
 
            //根节点元素
            QDomElement root = doc.createElement("日销售清单");
            doc.appendChild(root);
 
            //保存
            QTextStream stream(&file); //文本流关联文件
            doc.save(stream, 4); //4 缩进字符
 
            file.close(); //关闭文件
 
        }
        else
        {
            cout << "WriteOnly error";
            return;
        }
    }
 
}
 
void DomXML::appendXML(QString filePath, QStringList list)
{
    QFile file(filePath);
    bool isOk = file.open(QIODevice::ReadOnly);
    if(true == isOk) //打开成功
    {
        //file和xml文档对象关联
        QDomDocument doc;
        isOk = doc.setContent(&file);
        if(isOk) //如果关联成功
        {
            file.close(); //关闭文件
            //获取根节点元素
            QDomElement root = doc.documentElement();
 
            //获取当前时间
            QDateTime date = QDateTime::currentDateTime();
            QString dateStr = date.toString("yyyy-MM-dd"); //2016-01-08
 
            //判断根节点下有没有子节点
            if( root.hasChildNodes() ) //如果有子节点
            {
                //查找最后一个子节点
                QDomElement lastEmt = root.lastChildElement();
                if(lastEmt.attribute("date") == dateStr)
                {//有有当天日期
                    //写有效数据
                    writeXML(doc, lastEmt, list);
 
                }
                else //如果没有
                {
                    //创建日期子节点元素
                    QDomElement dateEmt = doc.createElement("日期");
                    //创建date属性
                    QDomAttr dateAttr = doc.createAttribute("date");
                    //设置属性的值
                    dateAttr.setNodeValue(dateStr);
                    //节点和属性关联
                    dateEmt.setAttributeNode(dateAttr);
 
                    //把日期节点追加到根节点上
                    root.appendChild(dateEmt);
 
                    //写有效数据
                    writeXML(doc, dateEmt, list);
                }
 
            }
            else //没有子节点
            {
               //创建日期子节点元素
               QDomElement dateEmt = doc.createElement("日期");
               //创建date属性
               QDomAttr dateAttr = doc.createAttribute("date");
               //设置属性的值
               dateAttr.setNodeValue(dateStr);
               //节点和属性关联
               dateEmt.setAttributeNode(dateAttr);
 
               //把日期节点追加到根节点上
               root.appendChild(dateEmt);
 
               //写有效数据
               writeXML(doc, dateEmt, list);
 
            }
 
            //保存文件
            isOk = file.open(QIODevice::WriteOnly);
            if(isOk)
            {
                QTextStream stream(&file);
                doc.save(stream, 4);
                file.close();
 
            }
 
        }
        else
        {
            cout << "setContent error";
            return;
        }
 
 
    }
    else
    {
        cout << "ReadOnly error";
    }
 
}
 
void DomXML::writeXML(QDomDocument &doc, QDomElement &root, QStringList &list)
{
    //获取当前时间
    QDateTime time = QDateTime::currentDateTime();
    QString timeStr = time.toString("hh-mm-ss"); //16:05:22
 
    //创建时间节点元素
    QDomElement timeEmt = doc.createElement("时间");
    //创建属性
    QDomAttr timeAttr = doc.createAttribute("time");
    //给属性设置值
    timeAttr.setNodeValue(timeStr);
    //时间节点元素和属性关联
    timeEmt.setAttributeNode(timeAttr);
    //把时间节点追击到日期节点后面
    root.appendChild(timeEmt);
 
    QDomElement factory = doc.createElement("厂家");
    QDomElement brand = doc.createElement("品牌");
    QDomElement price = doc.createElement("报价");
    QDomElement num = doc.createElement("数量");
    QDomElement total = doc.createElement("金额");
 
 
    QDomText text = doc.createTextNode(list.at(0));
    factory.appendChild(text);
 
    text = doc.createTextNode(list.at(1));
    brand.appendChild(text);
 
    text = doc.createTextNode(list.at(2));
    price.appendChild(text);
 
    text = doc.createTextNode(list.at(3));
    num.appendChild(text);
 
    text = doc.createTextNode(list.at(4));
    total.appendChild(text);
 
 
    timeEmt.appendChild(factory);
    timeEmt.appendChild(brand);
    timeEmt.appendChild(price);
    timeEmt.appendChild(num);
    timeEmt.appendChild(total);
 
}
 
void DomXML::readXML(QString filePath, QStringList &fList, QStringList &bList, QStringList &pList, QStringList &nList, QStringList &tList)
{
    QFile file(filePath);
    bool isOk = file.open(QIODevice::ReadOnly);
    if(true == isOk) //打开成功
    {
        //file和xml文档对象关联
        QDomDocument doc;
        isOk = doc.setContent(&file);
 
        if(isOk) //如果关联成功
        {
            //获取根节点
            QDomElement root = doc.documentElement();
            file.close();
 
            QDateTime date = QDateTime::currentDateTime();
            QString dateStr = date.toString("yyyy-MM-dd");
 
 
            if(root.hasChildNodes()) //有没有子节点
            {
                //找最后一个节点元素
                QDomElement lastEmt = root.lastChildElement();
                if(lastEmt.attribute("date") == dateStr)//判断有没有当天日期
                {
                    //找出当前日期下所有时间子节点
                    QDomNodeList list = lastEmt.childNodes();
                    for(int i = 0; i < list.size(); i++)
                    {
                        //list.at(0).toElement();
                        //转换为元素,找到时间节点下的所有子节点
                        QDomNodeList subList = list.at(i).toElement().childNodes();
                        //厂家
                        QString factory = subList.at(0).toElement().text();
                        fList.append(factory);
 
                        QString brand = subList.at(1).toElement().text();
                        bList.append(brand);
 
                        QString price = subList.at(2).toElement().text();
                        pList.append(price);
 
                        QString num = subList.at(3).toElement().text();
                        nList.append(num);
 
                        QString total = subList.at(4).toElement().text();
                        tList.append(total);
                    }
 
 
                }
                else
                {
                    cout << "没有当天日期";
                    return;
                }
 
 
            }
            else
            {
                cout << "没有子节点";
                return;
            }
 
 
        }
        else
        {
            cout << "setContent error";
            return;
        }
    }
    else
    {
        cout << "ReadOnly error";
        return;
    }
 
}

示例3

下面一个 books.xml 片段:

<doc>
    <quote>Scio me nihil scire</quote>
    <translation>I know that I know nothing</translation>
</doc>

我们可以认为是如下一棵 DOM 树:

Document
  |--Element(doc)
       |--Element(quote)
       |    |--Text("Scio me nihil scire")
       |--Element(translation)
            |--Text("I know that I know nothing")

面所示的 DOM 树包含了不同类型的节点。例如,Element 类型的节点有一个开始标签和对应的一个结束标签。在开始标签和结束标签之间的内容作为这个 Element 节点的子节点。在 Qt 中,所有 DOM 节点的类型名字都以 QDom 开头,因此,QDomElement就是 Element 节点,QDomText就是 Text 节点。不同类型的节点则有不同类型的子节点。例如,Element 节点允许包含其它 Element 节点,也可以是其它类型,比如 EntityReference,Text,CDATASection,ProcessingInstruction 和 Comment。按照 W3C 的规定,我们有如下的包含规则:

[Document]
  <- [Element]
  <- DocumentType
  <- ProcessingInstrument
  <- Comment
[Attr]
  <- [EntityReference]
  <- Text
[DocumentFragment] | [Element] | [EntityReference] | [Entity]
  <- [Element]
  <- [EntityReference]
  <- Text
  <- CDATASection
  <- ProcessingInstrument
  <- Comment

上面表格中,带有 [] 的可以带有子节点,反之则不能。

程序的目的还是一样的:用QTreeWidget 来显示这个文件的结构。需要注意的是,由于我们选用 DOM 方式处理 XML,无论是 Qt4 还是 Qt5 都需要在 .pro 文件中添加这么一句:
QT += xml

头文件也是类似的

class MainWindow : public QMainWindow
{
    Q_OBJECT
 
public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();
    bool readFile(const QString &fileName);
private:
    void parseBookindexElement(const QDomElement &element);
    void parseEntryElement(const QDomElement &element, QTreeWidgetItem *parent);
    void parsePageElement(const QDomElement &element, QTreeWidgetItem *parent);
    QTreeWidget *treeWidget;
private:
    Ui::MainWindow *ui;
};

构造函数与上面类似

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
 
    setWindowTitle(tr("XML DOM Reader"));
 
    treeWidget = new QTreeWidget(this);
    QStringList headers;
    headers << "Items" << "Pages";
    treeWidget->setHeaderLabels(headers);
    setCentralWidget(treeWidget);
}

readFile文件发生率变化

bool MainWindow::readFile(const QString &fileName)
{
    //打开文件
    QFile file(fileName);
    if (!file.open(QFile::ReadOnly | QFile::Text))
    {
        QMessageBox::critical(this, tr("Error"),
                              tr("Cannot read file %1").arg(fileName));
        return false;
    }
 
    QString errorStr;
    int errorLine;
    int errorColumn;
    //创建一个QDomDocument对象,代表整个文档
    QDomDocument doc;
    //填充dom树
    if (!doc.setContent(&file, false, &errorStr, &errorLine,
                        &errorColumn))//形参2,是否创建命名空间
    {
        QMessageBox::critical(this, tr("Error"),
                              tr("Parse error at line %1, column %2: %3")
                                .arg(errorLine).arg(errorColumn).arg(errorStr));
        return false;
    }
 
    QDomElement root = doc.documentElement();//获取dom树的根标签
    if (root.tagName() != "bookindex")
    {
        QMessageBox::critical(this, tr("Error"),
                              tr("Not a bookindex file"));
        return false;
    }
    parseBookindexElement(root);
      return true;
 }

readFile()函数显然更长更复杂。首先需要使用QFile打开一个文件,这点没有区别。然后我们创建一个QDomDocument对象,代表整个文档。注意看我们上面介绍的结构图,Document 是 DOM 树的根节点,也就是这里的QDomDocument;使用其setContent()函数填充 DOM 树。setContent()有八个重载,我们使用了其中一个:

bool QDomDocument::setContent ( QIODevice * dev,
                                bool namespaceProcessing,
                                QString * errorMsg = 0,
                                int * errorLine = 0,
                                int * errorColumn = 0 )

不过,这几个重载形式都调用同一实现

bool QDomDocument::setContent ( const QByteArray & data,
                                bool namespaceProcessing,
                                QString * errorMsg = 0,
                                int * errorLine = 0,
                                int * errorColumn = 0 )

两个函数的参数基本类似。第二个函数有五个参数,第一个是QByteArray,也就是所读取的真实数据,由QIODevice即可获得这个数据,而QFile就是QIODevice的子类;第二个参数确定是否处理命名空间,如果设置为 true,处理器会自动设置标签的前缀之类,因为我们的 XML 文档没有命名空间,所以直接设置为 false;剩下的三个参数都是关于错误处理。后三个参数都是输出参数,我们传入一个指针,函数会设置指针的实际值,以便我们在外面获取并进行进一步处理。

当QDomDocument::setContent()函数调用完毕并且没有错误后,我们调用QDomDocument::documentElement()函数获得一个 Document 元素。如果这个 Document 元素标签是 bookindex,则继续向下处理,否则则报错。

void MainWindow::parseBookindexElement(const QDomElement &element)
{
    QDomNode child = element.firstChild();//根标签下的子标签
    while (!child.isNull())
    {
        if (child.toElement().tagName() == "entry")//qdomnode ————》qdomelement的转换基类到子类的转换
        {
            parseEntryElement(child.toElement(),
                              treeWidget->invisibleRootItem());
        }
        child = child.nextSibling();
    }
 }

如果根标签正确,我们取第一个子标签,判断子标签不为空,也就是存在子标签,然后再判断其名字是不是 entry。如果是,说明我们正在处理 entry 标签,则调用其自己的处理函数;否则则取下一个标签(也就是nextSibling()的返回值)继续判断。注意我们使用这个 if 只选择 entry 标签进行处理,其它标签直接忽略掉。另外,firstChild()和nextSibling()两个函数的返回值都是QDomNode。这是所有节点类的基类。当我们需要对节点进行操作时,我们必须将其转换成正确的子类。这个例子中我们使用toElement()函数将QDomNode转换成QDomElement。如果转换失败,返回值将是空的QDomElement类型,其tagName()返回空字符串,if 判断失败,其实也是符合我们的要求的。

void MainWindow::parseEntryElement(const QDomElement &element,
                                   QTreeWidgetItem *parent)
{
    QTreeWidgetItem *item = new QTreeWidgetItem(parent);
    item->setText(0, element.attribute("term"));
 
    QDomNode child = element.firstChild();
    while (!child.isNull())//遍历标签的子标签
    {
        if (child.toElement().tagName() == "entry")
        {
            parseEntryElement(child.toElement(), item);//递归调用本身
        }
        else if (child.toElement().tagName() == "page")
        {
            parsePageElement(child.toElement(), item);
        }
        child = child.nextSibling();//指针移动一个标签
    }
}

在parseEntryElement()函数中,我们创建了一个树组件的节点,其父节点是根节点或另外一个 entry 节点。接着我们又开始遍历这个 entry 标签的子标签。如果是 entry 标签,则递归调用自身,并且把当前节点作为父节点;否则则调用parsePageElement()函数。

void MainWindow::parsePageElement(const QDomElement &element,
                                  QTreeWidgetItem *parent)
{
    QString page = element.text();
    QString allPages = parent->text(1);//最开始的一次为空
    qDebug()<<"allPages "<<allPages;
    if (!allPages.isEmpty())
    {
         allPages += ", ";
    }
    allPages += page;
    parent->setText(1, allPages);
}

parsePageElement()则比较简单,我们还是通过字符串拼接设置叶子节点的文本。
在这里插入图片描述
通过这个例子我们可以看到,使用 DOM 当时处理 XML 文档,除了一开始的setContent()函数,其余部分已经与原始文档没有关系了,也就是说,setContent()函数的调用之后,已经在内存中构建好了一个完整的 DOM 树,我们可以在这棵树上面进行移动,比如取相邻节点(nextSibling())。对比上一章流的方式,虽然我们早早关闭文件,但是我们始终使用的是readNext()向下移动,同时也不存在readPrevious()这样的函数。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阳光开朗男孩

你的鼓励是我最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值