Qt XML DOM方式读写

目录

最近将程序的配置文件格式从ini改为xml,使用的Qt模块也从QSettings改为Qt XML模块。Qt提供多种操纵XML的选择,我使用DOM方式。

1. 载入XML文件

QDomDocument表示一个XML文档,将整个XML文档的DOM树放置在内存中,所以DOM方式适用小型XML文件。利用QDomDocument::setContent设置并解析XML文档。
[cpp]
bool QDomDocument::setContent ( const QByteArray & data, bool namespaceProcessing, QString * errorMsg = 0, int * errorLine = 0, int * errorColumn = 0 )
[/cpp]
示例:
[cpp]
/*
QString xml_file_name;
QFile* xml_file;
QDomDocument dom_doc;
*/
bool XmlAppConfig::load(const QString &file_name)
{
xml_file = new QFile(file_name);
if(!xml_file->open(QIODevice::ReadWrite))
{
qDebug()«“Can’t load config file:"«file_name; delete xml_file; return false; } else { xml_file_name = file_name; dom_doc.setContent(xml_file); xml_file->close();
}
return true;
}
[/cpp]

2. 保存XML文件

可以手动保存XML文本,利用QDomDocument::toString得到字符串,再写入到文件中。不过QDomDocument::save方法(继承自QDomNode,说明可以保存任意节点)封装了该功能,直接将DOM树保存到文件中。
[cpp]
void QDomNode::save ( QTextStream & str, int indent ) const;
void QDomNode::save ( QTextStream & str, int indent, EncodingPolicy encodingPolicy ) const;
[/cpp]
例子
[cpp]
bool XmlAppConfig::write(QString &file_name) const
{
if(file_name==QString())
{
file_name = xml_file_name;
}
const int XML_SPACE_NUM = 4;
QFile file(file_name);
if(!file.open(QIODevice::WriteOnly))
{
qDebug()«“Can’t open file to write:"«file_name; return false; } QTextStream out(&file); dom_doc.save(out,XML_SPACE_NUM); file.close(); return true; } [/cpp]

3. 遍历XML

XML文档的根结点元素由QDomElement QDomDocument::documentElement()得到。
QDomElement有一个tagName()和零至多个属性。标签名可用setTagName()修改。属性由QDomAttr对象表示,可用attribute()、attributeNode()访问,并由setAttribute()和setAttributeNode()设置,用removeAttribute()移除。元素的文本由QDomElement::text()获得。
QDomNode是DOM树中的所有节点的基类,可以通过以下函数判断节点的类型 isAttr(), isCDATASection(), isDocumentFragment(), isDocument(), isDocumentType(), isElement(), isEntityReference(), isText(), isEntity(), isNotation(), isProcessingInstruction(), isCharacterData() 和 isComment(),并可以转换成具体类型的节点。需要注意的是,QDomNode的拷贝共享数据,修改任意一个拷贝都会影响所有其它拷贝,但cloneNode()返回一个独立的备份。
以上QDomElement和QDomNode都有遍历XML需要的方法,大致就是firstChild()/firstChildElement()、nextSibling()/nextSiblingElement()、lastChild()、previousSibling()/previousSiblingElement()、parentNode()、childNodes()等方法。QDomElement还有根据标签名查找的方法elementsByTagName()。利用以上函数,就可以遍历XML的DOM树,查找到需要的节点。
例:
获取xml文件某节点的文本值。我还没理解XML XPath和XQuery,也不会用Qt的XmlPattern库,就参照XPath写了个简单的XML节点访问函数,路径可以带属性值,格式如下
[bash]
/node_name/node_name[@proerpty_name=peroperty_value]/…
[/bash]
则读取文本值的函数如下:
[cpp]
QVariant XmlAppConfig::value(const QString &key, const QVariant &defaultValue) const
{
QDomElement dom_element = findDomElement(key,dom_doc);
if(dom_element.isNull())
return defaultValue;
return QVariant(dom_element.text());
}
[/cpp]
其中findDomElement()返回路径对应的元素节点。我的配置文件以/configuration开头,所以findDomElement()实现如下:
[cpp]
QDomElement XmlAppConfig::findDomElement(const QString &key, const QDomDocument &dom) const
{
[/cpp]
拆分key值
[cpp]
QStringList key_list = key.split(“/”);
[/cpp]
应保证至少有两段,只有一段的情况返回空元素
[cpp]
int levels = key_list.size()-1;
if(levels==0)
{
qDebug()«“key must begin with "/"!”; return QDomElement(); } [/cpp] 只有两段,则只返回根元素 [cpp] dom_element = dom.documentElement(); if(levels==1) { // root return dom_element; } [/cpp] 下面循环解析剩下的每段路径 [cpp] int i=2; while(i<=levels) { [/cpp] 首先分析路径的格式,是否包含属性 [cpp] QString section_str=key_list[i]; QString node_name; QString property_name; QString property_value; QRegExp p_pattern("^(.*)\[@(.*)=(.*)\]$”); // one pattern // future pattern if(p_pattern.indexIn(section_str)==-1) { node_name=section_str; } else { node_name=p_pattern.cap(1); property_name=p_pattern.cap(2); property_value=p_pattern.cap(3); } [/cpp] 寻找是否有满足条件的子节点,利用firstChildElement()和nextSiblingElement()方法 [cpp] QDomElement e=dom_element.firstChildElement(node_name); if(!property_name.isEmpty()) { while(!e.isNull()) { if(e.attribute(property_name)==property_value) break; e=e.nextSiblingElement(node_name); } } [/cpp] 没找到,则返回空节点,找到则进入下一段路径 [cpp] if(e.isNull()) { qDebug()«“Given key ("+key+”) canot be found in level “+i+”!”; return QDomElement(); } dom_element = e; i++; } [/cpp] 判断是否全部解析,并最终返回元素节点。 [cpp] if(i!=levels+1) { qDebug()«“Given key ("+key+”) has level error!"; return QDomElement(); } return dom_element; } [/cpp] 例2:我还写了一个函数(findDomNodeList)返回具有路径最后一段标签的节点列表。与上一个函数相同,只不过只解析到倒数第二段路径,最后一段路径看做标签,最后返回 [cpp] return dom_element.elementsByTagName(key_list[i]); [/cpp]

4. 创建节点、修改文本

XML中每个节点都属于文档,所以要用QDomDocument创建属于自身的节点。例如 createElement(), createTextNode(), createComment(), createCDATASection(), createProcessingInstruction(), createAttribute()和createEntityReference()等方法
。并通过QDomNode或QDomElement将节点放入到合适位置,例如insertBefore(), insertAfter() 或 appendChild(),还可以用replaceChild()替换,用removeChild()删除。
例:根据路径设置参数值,如果某段路径不存在,则创建该路径。
[cpp]
bool XmlAppConfig::setValue(const QString &key, const QVariant &value)
{
QDomElement dom_element = makeDomElement(key,dom_doc);
if(dom_element.isNull())
return false;
QDomNode text_node= dom_element.firstChild();
if(!text_node.isNull() && text_node.isText())
{
text_node.setNodeValue(value.toString());
return true;
}
return false;
}
[/cpp]
makeDomElement返回路径对应的元素,如果不存在则创建。该函数与findDomElement相似,仅在对无效路径的处理上有区别,makeDomElement发现无效路径时直接创建新的元素,并将该元素添加到相应位置。
[cpp]
if(e.isNull())
{
e = dom.createElement(node_name);
if(!property_name.isEmpty())
{
e.setAttribute(property_name,property_value);
}
dom_element.appendChild(e);
}
[/cpp]
修改文本只需使用QDomElement::setNodeValue()函数即可。
注:以上函数还有不少问题,有待进一步修改。