和HTML的语法很相似,但不同之处在于: HTML 被设计用来显示数据,其关注的是数据的外观,XML 被设计用来传输和存储数据,其关注的是数据的内容,因此,XML主要用来作为数据的存储和共享。
XML文档是一种树的结构,从根部扩展到枝叶。以下是一个XML示例
<root>
<class name="Rect">
<object name="obj1">
<x1>10x1>
<y1>10y1>
<x2>50x2>
<y1>50y1>
<linewidth>2linewidth>
<scale>0scale>
<rotate>0rotate>
object>
class>
root>
其中第一行 是XML 声明。它定义 XML 的版本和所使用的编码格式,
为根节点的起始(在XML中可以自定义节点名称),
为子元素,其中name
为其属性,值为Rect
;每一个子元素都可以拥有子元素,故class
的子元素为object
,依次类推; 所有的元素都可以有文本内容和属性,如x1的文本为10,x2的文本为50。
Classes | 说明 |
---|---|
QDomAttr | 表示 QDomElement 的一个属性 |
QDomCDATASection | 表示 XML CDATA 部分 |
QDomCharacterData | 表示 DOM 中的通用字符串 |
QDomComment | 表示 XML 注释 |
QDomDocument | 表示一个 XML 文档 |
QDomDocumentFragment | QDomNodes 树,不是完整的QDomDocument |
QDomDocumentType | DTD 在文档树中的表示 |
QDomElement | 表示 DOM 树中的一个元素 |
QDomEntity | 表示一个 XML 实体 |
QDomEntityReference | 表示 XML 实体引用 |
QDomImplementation | 有关 DOM 实现的功能的信息 |
QDomNamedNodeMap | 包含可以按名称访问的节点集合 |
QDomNode | DOM 树中所有节点的基类 |
QDomNodeList | QDomNode 对象列表 |
QDomNotation | 表示 XML 表示法 |
QDomProcessingInstruction | 表示 XML 处理指令 |
QDomText | 表示解析的 XML 文档中的文本数据 |
其相关继承关系如下:
一个XML文档如果只做保存数据使用,那么以下XML的构成就足够使用了
在QDomNode中,对XML中各种参数的区分是通过NodeType枚举实现的
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,// this is not in the standard
CharacterDataNode = 22 // this is not in the standard
};
可以通过以下函数对节点的类型进行判断
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如下
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);
创建节点有如下工厂函数可供使用
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
创建一个带有命名空间的元素即可。
QDomNodeList elementsByTagName(const QString& tagname) const;
QDomNodeList elementsByTagNameNS(const QString& nsURI, const QString& localName);
QDomElement elementById(const QString& elementId);
节点的复制,节点类通过隐式共享来共享数据,这样获取到节点指针后可以很方便的对数据进行修改。同时也可以对节点进行深拷贝。
QDomNode QDomNode::cloneNode(bool deep = true) const
节点的遍历,从前往后遍历可以通过
QDomNode firstChild() const;
QDomNode nextSibling() const;
从后往前遍历可以通过
QDomNode lastChild() const;
QDomNode previousSibling() const;
QDomNode parentNode() const;
配合函数namedItem()
可以进行更精确的遍历
节点的转化。将一个节点类型转化为另一种节点类型
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;
节点的清除
void clear();
节点的非空判断
bool isNull() const;
元素中可以有属性和文本,有关属性的操作主要有获取属性值,添加属性,移除属性和判断是否含有某个属性等,如下所示
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文档保存通过函数,可以指定文本流,缩进以及编码方式
void QDomNode::save(QTextStream &stream, int indent, QDomNode::EncodingPolicy encodingPolicy = QDomNode::EncodingFromDocument) const
使用DOM解析XML文档时,需要在.pro
文件中添加
QT += xml
代码示例:
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; /* 根节点下的class属性 */
QDomElement object; /* class下的object属性 */
QDomElement x1; /* object下的x1属性 */
QDomElement y1; /* object下的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); /* 将元素追加在class元素后面 */
object.setAttribute("name", "obj1"); /* 为元素添加一个属性 */
/* 创建一个元素 */
x1 = doc.createElement("x1");
object.appendChild(x1); /* 将元素追加在object元素后面 */
/* 创建一个文本 */
text = doc.createTextNode("10");
x1.appendChild(text); /* 将文本追加在x1元素后面 */
/* 创建一个元素 */
object = doc.createElement("object");
_class.appendChild(object); /* 将元素追加在class元素后面 */
object.setAttribute("name", "obj2"); /* 为元素添加一个属性 */
/* 创建一个元素 */
x1 = doc.createElement("x1");
object.appendChild(x1); /* 将元素追加在object元素后面 */
/* 创建一个文本 */
text = doc.createTextNode("20");
x1.appendChild(text); /* 将文本追加在x1元素后面 */
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文档:
<root>
<class xmlns="rect" name="Rect">
<object name="obj1">
<x1>10x1>
object>
<object name="obj2">
<x1>20x1>
object>
class>
<class xmlns="line" name="Line">没有线图元class>
root>
示例:
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); /* 设置doc的内容来自文件,并且支持命名空间 */
/* 查找命名空间是rect且名字为class的节点 */
QDomNodeList nodeList = doc.elementsByTagNameNS("rect", "class");
qDebug() << nodeList.size();
QDomNode node = nodeList.at(0); /* 因为实际只有一个,这里直接取第0个元素即可 */
QDomElement domElement;
if(node.isElement()) /* 判断是否是元素,然后转化为元素节点 */
domElement = node.toElement();
/* 获取obj1和obj2中的x1的值 */
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"); // obj1,obj2
for(QDomNode cnode2 = dElement.firstChild(); cnode2.isNull()==false; cnode2 = cnode2.nextSibling())
{
QDomText tDom = cnode2.firstChild().toText(); /* 把元素x1,y1的第一个子节点转化为文本类节点 */
qDebug() << cnode2.nodeName() << ":" << tDom.data();
}
}
}
}
打印输出:
1
"obj1"
"x1" : "10"
"obj2"
"x1" : "20"
示例代码:
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); /* 设置doc的内容来自文件,并且支持命名空间 */
file.close();
/* 查找命名空间是rect且名字为class的节点 */
QDomNodeList nodeList = doc.elementsByTagNameNS("rect", "class");
qDebug() << nodeList.size();
QDomNode node = nodeList.at(0); /* 因为实际只有一个,这里直接取第0个元素即可 */
QDomElement domElement;
if(node.isElement()) /* 判断是否是元素,然后转化为元素节点 */
domElement = node.toElement();
QDomNode last_node = domElement.lastChild(); /* 获取class的子节点中的最后一个节点 */
/* 创建一个新的元素 obj3 */
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.appendChild(new_object);
domElement.insertAfter(new_object, last_node);
/* 删除 class Line元素 */
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
节点后的结果为:
<root>
<class xmlns="rect" name="Rect">
<object xmlns="rect" name="obj1">
<x1 xmlns="rect">10x1>
object>
<object xmlns="rect" name="obj2">
<x1 xmlns="rect">20x1>
object>
<object name="obj3">
<x1>50x1>
<y1>80y1>
object>
class>
root>
需要注意的是再次写入的时候,程序自动在旧的元素上增添了命名空间。
示例代码:以修改元素obj1
的x1
的文本为例,假如修改为100
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)) /* 设置doc的内容来自文件,并且支持命名空间 */
{
file.close();
return;
}
file.close();
/* 查找命名空间是rect且名字为object的节点 */
QDomNodeList nodeList = doc.elementsByTagNameNS("rect", "object");
qDebug() << nodeList.size(); /* 会获取到3个 */
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)
{
/* 找到x1节点下的节点,因为只有一个,直接用firstChild,转化为文本节点类型,之后设置参数 */
objDom.elementsByTagName("x1").at(0).firstChild().toText().setData("100");
}
}
QDomNode node = nodeList.at(0); /* 因为实际只有一个,这里直接取第0个元素即可 */
QDomElement domElement;
if(node.isElement()) /* 判断是否是元素,然后转化为元素节点 */
domElement = node.toElement(); /* 获取obj1的节点 */
QDomNode node1 = domElement.elementsByTagName("x1").at(0);
QDomText domText;
if(node.isText()) /* 判断是否是元素,然后转化为元素节点 */
domText = node.toText(); /* 获取obj1的节点 */
domText.setData("100");
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate))
{
qDebug() << "Save failed";
return;
}
QTextStream stream(&file);
doc.save(stream, 4);
file.close();
}
依然以写入的XML文档作为示例,修改后的文件为:
<root>
<class xmlns="rect" name="Rect">
<object xmlns="rect" name="obj1">
<x1 xmlns="rect">100x1>
object>
<object xmlns="rect" name="obj2">
<x1 xmlns="rect">20x1>
object>
class>
<class xmlns="line" name="Line">没有线图元class>
root>
以DOM方式对xml文件进行解析,可以进行对文件进行修改,这是SAX做不到的,但是其读写并没有SAX方便,两者结合,以SAX方式进行一次性读取,以DOM方式进行写入和修改应该是个不错的方式。