Qt之XML文件解析(DOM) XML简介 和HTML 的语法很相似,但不同之处在于: HTML 被设计用来显示数据,其关注的是数据的外观,XML 被设计用来传输和存储数据,其关注的是数据的内容,因此,XML主要用来作为数据的存储和共享。
XML文档是一种树的结构,从根部扩展到枝叶。以下是一个XML示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?xml version="1.0" encoding="UTF-8" ?> <root > <class name ="Rect" > <object name ="obj1" > <x1 > 10</x1 > <y1 > 10</y1 > <x2 > 50</x2 > <y1 > 50</y1 > <linewidth > 2</linewidth > <scale > 0</scale > <rotate > 0</rotate > </object > </class > </root >
其中第一行 是XML 声明。它定义 XML 的版本和所使用的编码格式,<root> </root>
为根节点的起始(在XML中可以自定义节点名称),<class> </class>
为子元素,其中name
为其属性,值为Rect
;每一个子元素都可以拥有子元素,故class
的子元素为object
,依次类推; 所有的元素都可以有文本内容和属性,如x1的文本为10,x2的文本为50。
DOM类介绍 其相关继承关系如下:
一个XML文档如果只做保存数据使用,那么以下XML的构成就足够使用了
节点类型 在QDomNode中,对XML中各种参数的区分是通过NodeType枚举实现的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 enum NodeType { ElementNode = 1 , AttributeNode = 2 , TextNode = 3 , CDATASectionNode = 4 , EntityReferenceNode = 5 , EntityNode = 6 , ProcessingInstructionNode = 7 , CommentNode = 8 , DocumentNode = 9 , DocumentTypeNode = 10 , DocumentFragmentNode = 11 , NotationNode = 12 , BaseNode = 21 , CharacterDataNode = 22 };
节点类型判断 可以通过以下函数对节点的类型 进行判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 bool isAttr () const { return nodeType () == QDomNode::AttributeNode; }bool isCDATASection () const { return nodeType () == QDomNode::CDATASectionNode; }bool isDocumentFragment () const { return nodeType () == QDomNode::DocumentFragmentNode; }bool isDocument () const { return nodeType () == QDomNode::DocumentNode; }bool isDocumentType () const { return nodeType () == QDomNode::DocumentTypeNode; }bool isElement () const { return nodeType () == QDomNode::ElementNode; }bool isEntityReference () const { return nodeType () == QDomNode::EntityReferenceNode; }bool isText () const { const QDomNode::NodeType nt = nodeType ();return (nt == QDomNode::TextNode)|| (nt == QDomNode::CDATASectionNode); }bool isEntity () const { return nodeType () == QDomNode::EntityNode; }bool isNotation () const { return nodeType () == QDomNode::NotationNode; }bool isProcessingInstruction () const { return nodeType () == QDomNode::ProcessingInstructionNode; }bool isCharacterData () const { const QDomNode::NodeType nt = nodeType ();return (nt == QDomNode::CharacterDataNode)|| (nt == QDomNode::TextNode)|| (nt == QDomNode::CommentNode); } bool isComment () const { return nodeType () == QDomNode::CommentNode; }
节点增删改 对于节点的操作主要有插入 、替换 、移除 、追加 等,相关API如下
1 2 3 4 5 QDomNodePrivate* insertBefore (QDomNodePrivate* newChild, QDomNodePrivate* refChild) ;QDomNodePrivate* insertAfter (QDomNodePrivate* newChild, QDomNodePrivate* refChild) ;QDomNodePrivate* replaceChild (QDomNodePrivate* newChild, QDomNodePrivate* oldChild) ;QDomNodePrivate* removeChild (QDomNodePrivate* oldChild) ;QDomNodePrivate* appendChild (QDomNodePrivate* newChild) ;
节点创建 创建 节点有如下工厂函数可供使用
1 2 3 4 5 6 7 8 9 10 11 12 QDomElement createElement (const QString& tagName) ;QDomDocumentFragment createDocumentFragment () ;QDomText createTextNode (const QString& data) ;QDomComment createComment (const QString& data) ;QDomCDATASection createCDATASection (const QString& data) ;QDomProcessingInstruction createProcessingInstruction (const QString& target, const QString& data) ;QDomAttr createAttribute (const QString& name) ;QDomEntityReference createEntityReference (const QString& name) ;QDomNodeList elementsByTagName (const QString& tagname) const ;QDomNode importNode (const QDomNode& importedNode, bool deep) ;QDomElement createElementNS (const QString& nsURI, const QString& qName) ;QDomAttr createAttributeNS (const QString& nsURI, const QString& qName) ;
节点查找 节点的查找 。需要注意的是,虽然Qt提供了elementById
这个方法,但是并没有去实现,所以是不可使用的。而通过elementsByTagName
方法可以通过name
返回所有名为name
的节点,通过elementsByTagNameNS
可以通过命名空间和名字一起使用得到指定的节点,同时在创建元素时使用createElementNS
创建一个带有命名空间的元素即可。
1 2 3 QDomNodeList elementsByTagName (const QString& tagname) const ;QDomNodeList elementsByTagNameNS (const QString& nsURI, const QString& localName) ;QDomElement elementById (const QString& elementId) ;
节点复制 节点的复制 ,节点类通过隐式共享来共享数据,这样获取到节点指针后可以很方便的对数据进行修改。同时也可以对节点进行深拷贝。
1 QDomNode QDomNode::cloneNode (bool deep = true ) const
节点遍历 节点的遍历 ,从前往后遍历可以通过
1 2 QDomNode firstChild () const ;QDomNode nextSibling () const ;
从后往前遍历可以通过
1 2 3 QDomNode lastChild () const ;QDomNode previousSibling () const ;QDomNode parentNode () const ;
配合函数namedItem()
可以进行更精确的遍历
节点转化 节点的转化 。将一个节点类型转化为另一种节点类型
1 2 3 4 5 6 7 8 9 10 11 12 13 QDomAttr toAttr () const ;QDomCDATASection toCDATASection () const ;QDomDocumentFragment toDocumentFragment () const ;QDomDocument toDocument () const ;QDomDocumentType toDocumentType () const ;QDomElement toElement () const ;QDomEntityReference toEntityReference () const ;QDomText toText () const ;QDomEntity toEntity () const ;QDomNotation toNotation () const ;QDomProcessingInstruction toProcessingInstruction () const ;QDomCharacterData toCharacterData () const ;QDomComment toComment () const ;
节点清除 节点的清除
节点非空判断 节点的非空判断
元素属性与文本 元素中可以有属性和文本,有关属性 的操作主要有获取属性值,添加属性,移除属性和判断是否含有某个属性等,如下所示
1 2 3 4 QString attribute (const QString& name, const QString& defValue) const ;void setAttribute (const QString& name, const QString& value) ;void removeAttribute (const QString& name) ;bool hasAttribute (const QString& name) ;
文本 似乎没有提供属性这样方便的方法,但是文本作为文本节点,其具有节点的操作方法也同样可以使用,属性也是如此。
其它 要找出一个节点是否有子节点,可以使用hasChildNodes()
或者通过childNodes()
获取一个节点所有子节点的列表。
节点的名称和值(其含义取决于它的节点类型)分别由nodeName()
和nodeValue()
返回。节点的类型由nodeType()
返回。Node的值可以通过setNodeValue()
来设置。
通过ownerDocument()
返回节点所属的文档
通过normalize()
合并两个相邻的QDomText节点
通过lineNumber()
和columnNumber()
获取节点的行数和列数
XML文档保存通过函数,可以指定文本流,缩进以及编码方式
1 void QDomNode::save (QTextStream &stream, int indent, QDomNode::EncodingPolicy encodingPolicy = QDomNode::EncodingFromDocument) const
创建XML示例 使用DOM解析XML文档时,需要在.pro
文件中添加
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 void MainWindow::on_btnWrite_clicked () { QString fileName = QFileDialog::getSaveFileName (this , "Save File" , "./untitled.xml" ,"Xml(*.xml)" ); qDebug () << fileName; QFile file (fileName) ; if (!file.open (QIODevice::ReadWrite | QIODevice::Truncate)) { qDebug () << "Save failed" ; return ; } QDomDocument doc; QDomProcessingInstruction docStatement = doc.createProcessingInstruction ("xml" , "version=\"1.0\" encoding=\"UTF-8\"" ); doc.appendChild (docStatement); QDomElement root; QDomElement _class; QDomElement object; QDomElement x1; QDomElement y1; QDomComment comment; QDomText text; root = doc.createElement ("root" ); doc.appendChild (root); comment = doc.createComment ("以下是矩形图元" ); root.appendChild (comment); _class = doc.createElementNS ("rect" , "class" ); root.appendChild (_class); _class.setAttribute ("name" , "Rect" ); object = doc.createElement ("object" ); _class.appendChild (object); object.setAttribute ("name" , "obj1" ); x1 = doc.createElement ("x1" ); object.appendChild (x1); text = doc.createTextNode ("10" ); x1.appendChild (text); object = doc.createElement ("object" ); _class.appendChild (object); object.setAttribute ("name" , "obj2" ); x1 = doc.createElement ("x1" ); object.appendChild (x1); text = doc.createTextNode ("20" ); x1.appendChild (text); comment = doc.createComment ("以下是线图元" ); root.appendChild (comment); _class = doc.createElementNS ("line" , "class" ); root.appendChild (_class); _class.setAttribute ("name" , "Line" ); text = doc.createTextNode ("没有线图元" ); _class.appendChild (text); QTextStream stream (&file) ; doc.save (stream, 4 ); }
所产生的xml文档:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?xml version="1.0" encoding="UTF-8" ?> <root > <class xmlns ="rect" name ="Rect" > <object name ="obj1" > <x1 > 10</x1 > </object > <object name ="obj2" > <x1 > 20</x1 > </object > </class > <class xmlns ="line" name ="Line" > 没有线图元</class > </root >
读取XML示例 示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 void MainWindow::on_btnRead_clicked () { QString fileName = QFileDialog::getOpenFileName (this , "Save this file As" , "./" , "Xml(*.xml)" ); qDebug () << fileName; QFile file (fileName) ; if (!file.open (QIODevice::ReadOnly)) { qDebug () << "Save failed" ; return ; } QDomDocument doc; doc.setContent (&file, true ); QDomNodeList nodeList = doc.elementsByTagNameNS ("rect" , "class" ); qDebug () << nodeList.size (); QDomNode node = nodeList.at (0 ); QDomElement domElement; if (node.isElement ()) domElement = node.toElement (); QDomNode cnode; cnode = domElement.firstChild (); for (cnode = domElement.firstChild (); cnode.isNull ()==false ; cnode = cnode.nextSibling ()) { if (cnode.isElement ()) { QDomElement dElement = cnode.toElement (); qDebug () << dElement.attribute ("name" ); for (QDomNode cnode2 = dElement.firstChild (); cnode2.isNull ()==false ; cnode2 = cnode2.nextSibling ()) { QDomText tDom = cnode2.firstChild ().toText (); qDebug () << cnode2.nodeName () << ":" << tDom.data (); } } } }
打印输出:
1 2 3 4 5 1 "obj1" "x1" : "10" "obj2" "x1" : "20"
增删XML示例 示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 void MainWindow::on_btnAddAndRemove_clicked () { QString fileName = QFileDialog::getOpenFileName (this , "Save this file As" , "./" , "Xml(*.xml)" ); qDebug () << fileName; QFile file (fileName) ; if (!file.open (QIODevice::ReadOnly)) { qDebug () << "Save failed" ; return ; } QDomDocument doc; doc.setContent (&file, true ); file.close (); QDomNodeList nodeList = doc.elementsByTagNameNS ("rect" , "class" ); qDebug () << nodeList.size (); QDomNode node = nodeList.at (0 ); QDomElement domElement; if (node.isElement ()) domElement = node.toElement (); QDomNode last_node = domElement.lastChild (); QDomElement new_object = doc.createElement ("object" ); new_object.setAttribute ("name" , "obj3" ); QDomElement new_x1 = doc.createElement ("x1" ); new_object.appendChild (new_x1); QDomText new_text = doc.createTextNode ("50" ); new_x1.appendChild (new_text); QDomElement new_y1 = doc.createElement ("y1" ); new_object.appendChild (new_y1); new_text = doc.createTextNode ("80" ); new_y1.appendChild (new_text); domElement.insertAfter (new_object, last_node); QDomNode root = doc.documentElement (); QDomNode oldNode = doc.elementsByTagNameNS ("line" , "class" ).at (0 ); root.removeChild (oldNode); if (!file.open (QIODevice::WriteOnly | QIODevice::Truncate)) { qDebug () << "Save failed" ; return ; } QTextStream stream (&file) ; doc.save (stream, 4 ); file.close (); }
依然以写入的文档作为示例,经过增加一个子节点obj3
和删除一个命名空间为line
的class
节点后的结果为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?xml version='1.0' encoding='UTF-8'?> <root > <class xmlns ="rect" name ="Rect" > <object xmlns ="rect" name ="obj1" > <x1 xmlns ="rect" > 10</x1 > </object > <object xmlns ="rect" name ="obj2" > <x1 xmlns ="rect" > 20</x1 > </object > <object name ="obj3" > <x1 > 50</x1 > <y1 > 80</y1 > </object > </class > </root >
需要注意的是再次写入的时候,程序自动在旧的元素上增添了命名空间。
修改XML示例 示例代码:以修改元素obj1
的x1
的文本为例,假如修改为100
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 void MainWindow::on_btnModify_clicked () { QString fileName = QFileDialog::getOpenFileName (this , "Save this file As" , "./" , "Xml(*.xml)" ); qDebug () << fileName; QFile file (fileName) ; if (!file.open (QIODevice::ReadOnly)) { qDebug () << "Save failed" ; return ; } QDomDocument doc; if (!doc.setContent (&file, true )) { file.close (); return ; } file.close (); QDomNodeList nodeList = doc.elementsByTagNameNS ("rect" , "object" ); qDebug () << nodeList.size (); for (int i = 0 ; i < nodeList.size (); i++) { QDomElement objDom; if (nodeList.at (i).isElement ()) objDom = nodeList.at (i).toElement (); if (QString::compare (objDom.attribute ("name" ), "obj1" ) == 0 ) { objDom.elementsByTagName ("x1" ).at (0 ).firstChild ().toText ().setData ("100" ); } } QDomNode node = nodeList.at (0 ); QDomElement domElement; if (node.isElement ()) domElement = node.toElement (); QDomNode node1 = domElement.elementsByTagName ("x1" ).at (0 ); QDomText domText; if (node.isText ()) domText = node.toText (); domText.setData ("100" ); if (!file.open (QIODevice::WriteOnly | QIODevice::Truncate)) { qDebug () << "Save failed" ; return ; } QTextStream stream (&file) ; doc.save (stream, 4 ); file.close (); }
依然以写入的XML文档作为示例,修改后的文件为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?xml version='1.0' encoding='UTF-8'?> <root > <class xmlns ="rect" name ="Rect" > <object xmlns ="rect" name ="obj1" > <x1 xmlns ="rect" > 100</x1 > </object > <object xmlns ="rect" name ="obj2" > <x1 xmlns ="rect" > 20</x1 > </object > </class > <class xmlns ="line" name ="Line" > 没有线图元</class > </root >
总结 以DOM方式对xml文件进行解析,可以进行对文件进行修改,这是SAX做不到的,但是其读写并没有SAX方便,两者结合,以SAX方式进行一次性读取,以DOM方式进行写入和修改应该是个不错的方式。
参考 XML文件详解 Qt XML C++ Classes