用Qt中的QDomDocument类处理XML文件
XML,全称为扩展标记语言(extensible markup language).是一种非常方便的数据交换工具。现在Blog的订阅系统输出格式就是满足XML规范的RSS格式,还有SVG矢量图形也使用了XML格式。
我们在取得一个XML格式的文件后,需要作句法分析去提取发布方提供的信息。而Qt为XML提供了很好的支持,包括DOM的实现和SAX的实现。
DOM 是Document Object Model的简称,其实现方式是将整个文档当作一个对象来装入内存进行处理,然后开发者可以访问这个对象中的每一个节点,每一个节点对应XML文件里的一 个标记。这种方式的优点是操作非常简单,缺点是需要将整个文件放入内存,不适合处理过大文件。这种方式在Qt中的实现类是QDomDocument,也是 本文将要讲解的。以Blog为代表的RSS文件都不会很大,适合用这种方式来处理。
SAX是Simple API for XML的简称,其实现方式是按阶段将文档读取到内存中,在碰到标签或者其它阶段的时候,调用开发者预先设计好的回调函数去处理。这种方式的缺点是需要开发 者写回调函数去处理不同标签,代码复杂一些,优点是能处理很大的XML文件。这种方式在Qt中的实现类叫QXmlStreamReader。
——————分割线以下,代码讲解———-
1)如果需要使用QDomDocument,那么请在你的项目.pro文件里加上一句”QT += xml”,不然无法编译通过。
QDomDocument doc;QFile file(“rss.xml”);QString errorStr;int errorLine; int errorCol;//setContent是将指定的内容指定给QDomDocument解析,//第一参数可以是QByteArray或者是文件名等 if(!doc.setContent(&file,true,&errorStr,&errorLine,&errorCol)){ //如果出错,则会进入这里。errorStr得到的是出错说明 //errorLine和errorCol则是出错的行和列}
如果上面的步骤没出错,那么恭喜你,你已经得到了一个完整的QDomDocument对象doc,你只要访问doc的子节点或子子节点就可以取得XML中所有标记上的内容了。
3)
可以通过doc.childNodes()获得doc的所有的子节点列表QDomNodeList。比如
QDomNodeList list=doc.childNodes();
for(int i=0;i<list.count();i++)
{
QDomNode node=list.at(i);
qDebug()<<"node name is "<<node.nodeName();
qDebug()<<"node type is "<<.nodeType();
}
通过上面的方法你就能知道每个节点的名字和类型了,节点名字就是标记的名字。打印出来 对照着xml文件看就很容易明白了。QDomNode类是一个父类,QDomDocument是QDomNode的一个子类,鉴于大部分QDomNode 的类型都是QDomDocument.那么你可以使用toDocument()函数将QDomNode类型转换成QDomDocument.
QString text=node.toElement().text();
上面的语句可以将一个节点里的文本取出,也就是标记内的文本部分。使用该函数获得的编码就已经是unicode格式了,不需要再做转换。
使用Qt中的QDomDocument读取XML文件
通过QDomDocument的方法可以容易解析一个XML文件,QDomDocument的每一个子对象和XML的每一个子节点对应,保留了相同的层次结构,能够做到随机访问XML中的节点。但它的缺点是需要将整个XML文件加载到内存中,不适合处理较大的文件。
今天要介绍的是QXmlStreamReader,就是按顺序读取XML的方式,每读取一个节点,应用就对当前节点做相应的处理(忽略或保存)。那么这种方式是SAX么?不是!
SAX 的处理方式虽然也是按节点顺序读取,但是对读取节点的处理方法不一样。SAX在碰到一个节点后,会去调用用户已经写好的回调函数,该回调函数是基类实现的 虚函数,不能改变它的名字,只能根据传入的参数实现不同处理,代码会比较复杂。但StreamReader的实现方式是由开发者通过readNext() 读入一个节点,然后判断节点内容,由开发者决定用什么函数去处理,对应不同节点层次实现不同函数,可维护性比较好。
具体流程是:
下面,来看看我处理rss2代码的例子,通过该类我读取了rss2里每个文件的标题。
因为需要实现自己的处理功能,我们用一个类RssRader继承自QXmlStreamReader
class RssReader : public QXmlStreamReader{…}
下面就可以使用该类了
RssReader reader;
reader.read(&file); //参数是QIODevice指针,file是QFile类型,
read是我在子类里实现的函数名,可以是其它名字,看看read()的实现
bool RssReader::read(QIODevice *device)
{
setDevice(device);//这是基类的函数,用于将XML传入解析器
while (!atEnd()){
readNext(); //这个是关键函数,挨个读取节点
if (isStartElement()) {
if (name() == “rss”){
//我的实现,对rss作了处理,其它节点忽略
//read_rss()管理剩下的节点,碰到rss节点结束时返回
read_rss();
}
}
}
return !error();
}
//read_rss的实现,主要关心名为channel的节点,其他节点忽略
//XML的节点是对称的,有开始必有结束,判断的标志就是
//isStartElement()和isEndElement()
void RssReader::read_rss(){
qDebug()<<”read rss info”;
while (!atEnd()) {
readNext();
if (isStartElement()) {
if (name() == “channel”){
read_channel();
}
}
if (isEndElement()) {
if (name() == “rss”){
qDebug()<<”end read rss info”;
break;
}
}
}
}
//读取节点内容的函数是readElementText(),由基类实现
void RssReader::read_item(){
while (!atEnd()) {
readNext();
if (isStartElement()) {
if (name() == “title”){
qDebug()<<”Title:”<<readElementText();
}
}
if (isEndElement()) {
if (name() == “item”){
break;
}
}
在“QQ便签”项目组中我负责有关后台XML操作的实现部分,现在对在项目中用到的XML的相关操作进行简单的小结。
我 们知道对XML的操作有两种方法,即DOM方式和SAX方式。二者主要区别是:DOM实现方式操作非常简单,但不适合处理过大文件;而SAX实现方式是能 处理很大的XML文件,但是需要开发者写一些复杂的代码。Qt提供了对应于这两种用于读取、操作和编写XML的实现类,分别是QDomDocument类 和QXmlStreamReader类,由于在项目中涉及的文件不大,因此我们选用QDomDocument类来处理。
项目中涉及便签的增删改查,对应于XML文件中相应标记的读、写和修改,下面分别介绍:
1).创建根节点:QDomElement root = doc.documentElement("rootName " );
2).创建元素节点:QDomElement element = doc.createElement(“nodeName”);
3).添加元素节点到根节点:root. appendChild(element);
4).创建元素文本:QDomText nodeText=doc.createTextNode(“text”);
5).添加元素文本到元素节点:element. appendChild(nodeText);
在本项目中,假设便签的属性有序号、名字、内容、字体、字号、颜色、粗细、斜体、下划线这几项,则在文件中添加一个便签节点的操作如下:
QDomDocument doc;
instruction = doc.createProcessingInstruction("xml","version=\"1.0\" encoding=\"UTF-8\"");
doc.appendChild(instruction);
QDomElement root = doc.createElement("Notes");
doc.appendChild(root);
QDomElement note = doc.createElement("note");
root.appendChild(note);
QDomElement no = doc.createElement("no");
note.appendChild(no);
QDomText no_text = doc.createTextNode("001");
则得到一个便签节点,将其保存到test.xml文件中,代码如下:
```cpp
```cpp
QFile file("test.xml");
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate |QIODevice::Text))
return ;
QTextStream out(&file);
out.setCodec("UTF-8");
doc.save(out,4,QDomNode::EncodingFromTextStream);
file.close();
则test.xml文件:
001
2010-05-10(13:53:24)
A meeting!
Script MT Bold
16
#00ffff
0
true
true
上面是创建一个便签节点,若要继续添加便签节点,则需要在已有节点后增加一个新节点,并重写入XML文件。
2. 加载、查找便签时要读取XML文档中的节点信息,DOM实现方式是将整个文档当作一个对象来装入内存进行处理,然后开发者可以访问这个对象中的每一个节点,每一个节点对应XML文件里的一个标记。
主要操作包括:
1).读取根节点:QDomElement root = doc.documentElement();
2).读取第一个子节点:QDomNode node = root.firstChild();
3).读取下一个子节点:node = node.nextSibling();
4).匹配结点标记:node.toElement().tagName() == "note"
5).读取节点文本:no = childNode.toText().data();
以下是项目中读取便签属性的函数实现代码:
```cpp
void MainWindow::parseAttr(const QDomElement &element)
{
QString no,name,content,font,fontSize,color;
QDomNode node = element.firstChild();
while (!node.isNull())
{
if (node.toElement().tagName() == "note")
{
//匹配note节点
parseAttr(node.toElement());
}
else if (node.toElement().tagName() == "no")
{//匹配属性no
QDomNode childNode = node.firstChild();
if (childNode.nodeType() == QDomNode::TextNode)
{
no = childNode.toText().data();
}
}
else if (node.toElement().tagName() == "name") //匹配属性name
node = node.nextSibling();//读取兄弟节点
}
}
以上对XML文件的重写操作是必须的,因此在文件的打开方式中要加上QIODevice::Truncate,表示覆盖重写。目前还没有找到可以直接修改文件的方法,但若文件很大的情况下,必须考虑相应的效率问题。
由 于本项目在启动时需要将所有便签加载到内存,因此选用DOM方式比较合适,但如果处理的XML文件很大,而且不需要全部读到内存时,可以用SAX实现方 式,它按阶段将文档读取到内存中,在碰到标签或者其它阶段的时候,可以调用开发者预先设计好的回调函数去处理,这样效率比DOM方式更高