Qt实现SAX2方式解析XML文件(简单有效、老少皆宜,200MB大小4-7层的XML解析只需要30s)

18 篇文章 2 订阅

我这个案例可以说非常实用了,而且解说相当于是手把手教了,不管是老手还是新手都能轻易掌握的。

1、准备工作

需要定义好用于表示一个XML节点的结构体,我把这个结构体命名为XMLNode,每个节点包含若干个属性(attributes),若干个子节点(children), 节点的名字(name),节点的内容文本(characters)和一个父节点(parent)。这样一来可以得到一个结构体:

typedef struct xml_node_t{
    XMLAttributes attributes;
    QVector<xml_node_t*> children;
    QString name;
    QString characters;
    xml_node_t* parent;
}XMLNode;

这个结构体即为解析XML所需的节点结构体(XMLNode)。

XMLNode中的属性(XMLAttributes)这样定义:

typedef struct xml_attribute_t{
    QString name;
    QString value;
}XMLAttribute;

typedef QVector<XMLAttribute> XMLAttributes;

一个属性由一个名字(name)和一个值(value)组成。

PS: 为了方便使用,我往XMLNode这个结构体中加入了若干个成员函数,这几个函数分别是

获取某一个属性的值:

QString getAttribute(const QString& name){
        QString value = "";
        for(int i = 0; i < attributes.count(); i++){
            if(attributes[i].name == name){
                value = attributes[i].value;
                break;
            }
        }
        return value;
}

设置某一个属性的值:

void setAttribute(const QString& name, const QString& value){
        for(int i = 0; i < attributes.count(); i++){
            if(attributes[i].name == name){
                attributes[i].value = value;
                return;
            }
        }
        XMLAttribute attr;
        attr.name = name;
        attr.value = value;
        attributes << attr;
}

通过节点名称获取XMLNode的某个子节点:

QVector<xml_node_t*> getChildrenByName(QString name){
        QVector<xml_node_t*> result;
        for(int i = 0; i < children.count(); i++){
            if(children[i]->name == name){
                result << children[i];
            }
        }
        return result;
}

为了方便清理节点,在析构的时候将其子节点也一并析构:

~xml_node_t(){
        xml_node_t* p = this->parent;
        if(p)
        {
            for(int i = p->children.count() - 1; i >=0; i-- )
            {
                if(p->children[i] == this)
                    p->children.remove(i);
            }
        }
		for(int i = 0; i < this->children.count(); i++){
			xml_node_t* child = this->children[i];
			if(child)
            {
                child->parent = 0;
				delete child;
            }
		}
}

将节点列表定义成XMLNodeVector

typedef QVector<XMLNode*> XMLNodeVector;

(以上就是我为了方便使用所添加的功能)

2、实现Sax2解析处理对象

我把这个对象命名为EO_XmlSax2Parser,这个对象就是用来当系统在扫描XML文件时所发生的各种事件的。

 [注]:这里仅实现了比较常用的事件,小伙伴们如果有特别需要可以研究一下所需要的其他事件。

1)首先,EO_XmlSax2Parse需要一个保存当前解析到的节点数量的变量(currentNodeCount),需要一个XML文件的根节点(root,类型为XMLNode),以及一个用于辅助解析的栈(rootStack,这个栈的最顶层一定是下一层的子节点,最底层一定是root)。

2)得定义Sax2解析的事件处理函数

 [注]: 我没有描述到的参数是我这个示例中没有用到的(实际上也不常用),如果有需要的小朋友可以自行去查阅。

 ① 信号处理函数: bool startElement(namespaceUrl, localName, qName, atts)

这个函数为了处理当系统扫描到一个新节点的事件,其中localName为节点名称(即为XML标签名),atts即为属性列表

(节点中的属性)。返回值即为是否处理正常,如果返回false则系统会报错(这时候会调用错误处理函数)返回true则表示

这个事件处理成功。

② 信号处理函数:bool endElement(namespaceUrl, localName, qName)

  这是处理当系统扫描到一个节点尾部(探测到节点结尾)的事件,其中localName为该节点名称 。返回值表示是否处理成功。

③ 信号处理函数:bool characters(ch)

这个信号处理函数是用来处理扫描到节点内容的(内容不包括子节点,比如'<a>123<d /></a>'的a节点中会扫描到‘123’作为ch传入,而d节点中则会是空''或是不发送信号。返回值表示是否处理成功。

④ 处理处理函数 bool fatalError(e)、bool error(e)、bool warning(e)

  这几个处理函数是用来处理错误的,我没有仔细研究(实际上如果进来这几个函数一般就是xml文件格式有误,你要决定是否退出解析)如果返回false则正常退出,如果返回true则会尝试继续解析(但可能导致程序崩溃),一般来讲直接return false就好了,如果有特别需要的小朋友可以自行去研究一下。

3)为了方便使用加入一些实用函数

1)bool parseFromFile(filename),通过文件名开始解析,返回是否解析成功。

2)bool parseFromData(data),通过传入XML数据开始解析,返回是否解析成功。

3)XMLNode* root(),返回解析好的根节点root,如果解析失败则返回的是NULL。

4)具体实现

 可能有的小朋友到这还是没有头绪这是个很不好的感觉,好在Qt有帮忙实现所需的Sax2事件处理函数,这个实现在Qt类 'QXmlDefaultHandler'及其派生类中(使用这个类得包含模块xml, QT+= xml)。

 有了Qt接下来只需要将EO_XmlSax2Parser继承QXmlDefaultHandler就好了,以下是它的声明:

class EO_XmlSax2Parser : public QXmlDefaultHandler
{
private:
    quint64 mCurrentNodeCount;
    QStack<XMLNode*> mRootStack;
    XMLNode* mRoot;
public:
    explicit EO_XmlSax2Parser();
	~EO_XmlSax2Parser();
    bool parseFromFile(QString filename);
bool parseFromData(QString data);
    XMLNode* root();
    bool startElement(const QString &namespaceURI, const QString &localName, const QString &qName, const QXmlAttributes &atts);
    bool endElement(const QString &namespaceURI, const QString &localName, const QString &qName);
    bool characters(const QString &ch);
    bool fatalError(const QXmlParseException &exception);
    bool error(const QXmlParseException &exception);
    bool warning(const QXmlParseException &exception);
};

其中的成员和函数我在之前已经详细介绍过了,就不再阐述了。

现在到了最有趣的一步——实现!

这是具体的实现:

#include <QXmlSimpleReader>
#include <QDebug>
EO_XmlSax2Parser::EO_XmlSax2Parser()
    :
      mRoot(NULL),
      mCurrentNodeCount(0)
{
}
EO_XmlSax2Parser::~EO_XmlSax2Parser()
{
	if(mRoot != NULL)
	{
		delete mRoot;
		mRoot = NULL;
	}
}
bool EO_XmlSax2Parser::parseFromFile(QString filename)
{
    if(mRoot != NULL)
    {
        delete mRoot;
        mRoot = NULL;
    }
    QFile file(filename);
    if(!file.open(QIODevice::ReadOnly))
    {
        qDebug() << "xml parser " << filename << " can not open";
        return false;
    }
    QXmlInputSource source(&file);
    QXmlSimpleReader reader;
    reader.setContentHandler(this);
    bool isOk = reader.parse(source);
    file.close();
    qDebug() << "xml parser " << filename << " parse Status:" << isOk;
    return isOk;
}
bool EO_XmlSax2Parser::parseFromData(QString data)
{
	if(mRoot != NULL)
	{
		delete mRoot;
		mRoot = NULL;
	}
	QXmlInputSource source;
	source.setData(data);
	QXmlSimpleReader reader;
	reader.setContentHandler(this);
	bool isOk = reader.parse(source);
	qDebug() << "xml parser parse Status:" << isOk;
	return isOk;
}
XMLNode *EO_XmlSax2Parser::root()
{
    return mRoot;
}
bool EO_XmlSax2Parser::startElement(const QString &namespaceURI, const QString &localName, const QString &qName, const QXmlAttributes &atts)
{
//    qDebug() << "start element:" << localName;
    XMLNode* newNode = new XMLNode;
    newNode->name = localName;
    XMLAttributes newAttributes;
    for(int i = 0; i < atts.count(); i++)
    {
        XMLAttribute newAttribute;
        newAttribute.name = atts.localName(i);
        newAttribute.value = atts.value(i);
		newAttributes << newAttribute;
    }
    newNode->attributes = newAttributes;
    if(mRoot == NULL)
    {
        mRoot = newNode;
    }
    else
    {
        newNode->parent = mRootStack.top();
        mRootStack.top()->children << newNode;
    }
    mRootStack.push(newNode);
    return true;
}
bool EO_XmlSax2Parser::endElement(const QString &namespaceURI, const QString &localName, const QString &qName)
{
//    qDebug() << "end element:" << localName;
    if(mRootStack.count() <= 0)
    {
        return false;
    }
    if(mRootStack.top()->name != localName)
    {
        return false;
    }
    mRootStack.pop();
	return true;
}
bool EO_XmlSax2Parser::characters(const QString &ch)
{
//    qDebug() << "characters:" << ch;
    if(mRootStack.count() <= 0)
    {
        return false;
    }
    mRootStack.top()->characters = ch;
	return true;
}
bool EO_XmlSax2Parser::fatalError(const QXmlParseException &exception)
{
    qDebug() << exception.message();
    return false;
}
bool EO_XmlSax2Parser::error(const QXmlParseException &exception)
{
    qDebug() << exception.message();
    return false;
}
bool EO_XmlSax2Parser::warning(const QXmlParseException &exception)
{
    qDebug() << exception.message();
    return false;
}

可能不少小伙伴在看到这个实现的时候一时想不通是怎么回事,我这里就简单介绍下最关键的几点:

① startElement的处理,这个事件一出发就代表扫描到了一个新节点,这个时候立马创建一个新节点XMLNode* newNode = new XMLNode 并对其赋值(节点名字、属性),接下来要看这个节点是不是XML文件的根节点(root == NULL,如果为true则newNode为根节点,否则肯定是子节点)。如果是XML根节点则root == newNode,如果是子节点那么肯定是上一个根节点的子节点,这时候直接放入上一个根节点的子节点队列中:rootStack.top().children.push(newNode)。

 由于不能确定在这个节点结束之前是否会包含子节点,所以将这个newNode作为临时的根节点放到栈中: rootStack.push(newNode)。然后结束处理return true;

②characters的处理,当进入这个事件后先判断当前rootStack当中是否已经有临时根节点,如果没有则返回false(说明XML解析出了问题,starElement当中没有rootStack.push(newNode)怎么可能会到characters呢?明显有问题,直接返回false);如果rootStack顶层有有效节点,则ch为这个节点的文本,所以rootStack.top().characters = ch,处理完毕之后返回true。

③endElement的处理,到了这一步就意味着当前正在处理的节点已经完毕了,所以直接判断一下当前正在处理的节点和处理完毕的节点是不是同一个,如果不是,则说明解析过程出了问题或则是XML不对。如果比较节点名不一致,直接返回false。如果一致则rootStack.pop()弹出已经处理完毕的临时根节点。

(说明:所有的XML处理步骤大致就是扫描到头之后提取属性和名字并放入临时根节点栈顶,扫描到文本的时候赋值到对应节点,当结束一个节点的时候则将临时根节点从栈顶弹出。)

3、结果

在顺利的实现了相关函数之后试着解析一个XML文件吧:

放到D:/test.xml中

<target name="os-detect">
        <condition property="os.family" value="mac">
            <os family="mac"/>
        </condition>
        <condition property="os.family" value="windows">
            <os family="windows"/>
        </condition>
        <property name="os.family" value="unix"/>
        <condition property="is-mac" value="1">
            <equals arg1="${os.family}" arg2="mac"/>
        </condition>
        <condition property="is-windows" value="1">
            <equals arg1="${os.family}" arg2="windows"/>
        </condition>
        <condition property="is-unix" value="1">
            <or>
                <equals arg1="${os.family}" arg2="unix"/>
                <equals arg1="${os.family}" arg2="mac"/>
                <equals arg1="${os.family}" arg2="cygwin"/>
            </or>
        </condition>
</target>

执行如下代码进行解析:

EO_XmlSax2Parser parser;
parser.parseFromFile("D:/test.xml");
qDebug()<< "root node :" << parser.root()->name;
qDebug()<< "child count for root:" << parser.root()->children.count();

得到结果:

至此我们的SAX2解析XML的神器EO_XmlSax2Parser就大功告成啦!

(创作不易,未经允许请勿转载。欢迎交流,谢谢!)

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值