一、DOM是什么
DOM是一种解析XML文档的标准应用程序编程接口。Qt提供一套用于读取,操作和编写XML文档的非验证型二级DOM实现。
DOM把XML文件表示成内存中的一棵树。我们可以按需要遍历这个DOM树,也可以修改这个树并把它作为XML文件保存到磁盘中。
让我们考虑如下这个XML文档:
<doc>
<quote>Scio me nihil scire</quote>
<translation>I know that I know nothing</translation>
</doc>
它对应如下所示的DOM树:
这个DOM树包含不同类型的节点。例如,Element节点对应打开标签以及与它匹配的关闭标签。在这两个标签之间的内容则作为这个Element节点的子节点出现。在Qt中,节点类型(和其他所有与DOM有关的类一样)具有一个QDom前缀。因此,QDomElement就代表一个Element节点,而QDomText就代表一个Text节点。
不同类型的节点可以具有不同种类的子节点。例如,一个Element节点可以包含其他Element节点,也可以包含EntityReference、Text、CDATASection,Processinglnstruction以及Comment节点。下图给出了节点可以包含在的子节点的种类。图中显示为灰色的节点则不能拥有它自己的子节点。
二、DOM读取XML实例
为了演示如何使用DOM读取XML文件,我们将为前一篇文章(使用QXmlStreamReader读取XML)中提到的报刊索引文件格式编写一个解析器。界面运行如下图所示。
1. DomParser类定义
定义了一个称为DomParser的类,它将会解析XML书刊索引文档并且在QTreeWidget中显示结果。
class DomParser
{
public:
DomParser(QTreeWidget *tree);
bool readFile(const QString &fileName);
private:
void parseBookindexElement(const QDomElement &element);
void parseEntryElement(const QDomElement &element,
QTreeWidgetItem *parent);
void parsePageElement(const QDomElement &element,
QTreeWidgetItem *parent);
QTreeWidget *treeWidget;
};
2. DomParser类实现
DomParser::DomParser(QTreeWidget *tree)
{
treeWidget=tree;
}
在构造函数中,我们仅将给定的树形窗口部件赋给成员变量。所有的解析都在readFile()函数的内部完成。
bool DomParser::readFile(const QString &fileName)
{
QFile file(fileName);
if (! file.open(QFile::ReadOnly | QFile::Text)){
std::cerr<<"Error: Cannot read file "<<qPrintable(fileName)
<<": "<<qPrintable(file.errorString())
<<std::endl;
return false;
}
QString errorStr;
int errorLine;
int errorColumn;
QDomDocument doc;
if (!doc.setContent(&file, false, &errorStr, &errorLine,&errorColumn)) {
std::cerr<<"Error: Parse error at line "<<errorLine<<", "
<<"column "<<errorColumn<<": "
<<qPrintable(errorStr)<<std::endl;
return false;
}
QDomElement root = doc.documentElement();
if (root.tagName() != "bookindex") {
std::cerr<<"Error: Not a bookindex file"<<std::endl;
return false;
}
parseBookindexElement(root);
return true;
}
在readFile()中,首先尝试打开那些文件名已经被传递进来的文件。如果有错误发生,就输出一个出错信息,并返回false值表示文件打开失败。否则,就设置一些变量用以保存那些需要的解析出错信息,然后创建一个QDomDocument。当对DOM文档调用setContent()函数时,由QIODevice提供的整个XML文档将被读取并解析。如果该文挡还未打开,函数将自动打开设备。setContent()的false参数将禁用命名空间的处理。关于命名空间及其如何在Qt中处理,可以査阅QtXml的参考文档。
如果有错误发生,就输出一个出错信息并返回false值表示解析失败。如果解析成功就对QDomDocument调用documentElement()以获得它唯一的QDomElement子对象,同时检查它是否为<bookindex>索引。如果已经有<bookindex>元素了,就调用parseBookindexElement()来解析它。与前一篇文章中介绍的内容相似,解析过程是使用向下递归方法来实现的。
void DomParser::parseBookindexElement(const QDomElement &element)
{
QDomNode child = element.firstChild();
while (!child.isNull()) {
if(child.toElement().tagName()=="entry"){
parseEntryElement(child.toElement(),treeWidget->invisibleRootItem());
}
child=child.nextSibling();
}
}
在parseBookindexElement()中,遍历所存的子节点。我们希望每一个节点都是一个<entry>元素,那样的话,就可以调用parseEntryElement()来解析每一个节点。我们忽略那些未知的节点以便书刊索引格式在不阻止旧的解析器工作的情况下今后也能被扩展。所有的<entry>节点都是<bookindex>节点的直接子对象,在组装的用于反映DOM树的窗口部件中它还是顶级节点。所以,当我们想逐一解析这些节点时,将传递节点元素和树的不可见根项作为窗口部件树项的父对象。
QDomNode类可以存储任何类型的节点。如果想进一步处理一个节点,首先必须把它转换为正确的数据类型。在这个实例中,我们仅仅关心Element节点,所以对QDomNode调用toElement()以把它转换成QDomElement,然后调用tagName()来取得元素的标签名称。如果节点不是Element类型,那么toElement()函数就会返回一个空QDomElement对象和一个空的标签。
void DomParser::parseEntryElement(const QDomElement &element, QTreeWidgetItem *parent)
{
QTreeWidgetItem *item=new QTreeWidgetItem(parent);
item->setText(0,element.attribute("term"));
QDomNode child=element.firstChild();
while (!child.isNull()) {
if(child.toElement().tagName()=="entry"){
parseEntryElement(child.toElement(),item);
}else if(child.toElement().tagName()=="page"){
parsePageElement(child.toElement(),item);
}
child=child.nextSibling();
}
}
在parseEntryElement()中,我们创建一个树形窗口部件项。传入的父对象项既可以是树的不可见根项(如果这是一个顶级条目的话),也可以是其他的条目(如果它只是一个子条目的话)。我们调用setText(),将显示在项的第一列中的文本设置为<entry>标签的term属性值。
一旦已经初始化了QTreeWidgetltem,就遍历对应于当前<entry>标签的QDomElement节点下的所有子节点。对于每一个<entry>标签的子元素,我们利用当前项作为第二个参数来递归调用parseEntryElement()然后,利用当前条目作为父对象,创建每一子节点的QTreeWidgetItem。如果子元素为<page>,就调用parsePageElement()。
void DomParser::parsePageElement(const QDomElement &element, QTreeWidgetItem *parent)
{
QString page=element.text();
QString allPages=parent->text(1);
if(!allPages.isEmpty())
allPages+=", ";
allPages+=page;
parent->setText(1,allPages);
}
在parsePageElement()中,对元素调用text()以获得<page>和</page>标签之间文本;然后,将文本添加到QTreeWidgetItem的第二列,这是一列以逗号分隔的页码列表。QDomElement::text()函数遍历元素的所有子节点并连接存储在Text和CDATA节点中的所有文本。
3. DomParser的调用
现在看看如何使用DomParser类来解析文件:
Widget::Widget(QWidget *parent)
: QWidget(parent), ui(new Ui::Widget)
{
ui->setupUi(this);
ui->treeWidget->setColumnCount(2);
QStringList header;
header<<"Terms"<<"Pages";
ui->treeWidget->setHeaderLabels(header);
DomParser parser(ui->treeWidget);
parser.readFile("bookindex.xml");
}
首先在界面上添加了一个QTreeWidget,然后再创建一个DomParser,最后调用readFile()函数还解析指定的文件。
就像这个实例所描绘的,尽管并不如使用XmlStreamReader那么方便,遍历一个DOM树也还是相当简单和直接的。频繁使用DOM的程序员会经常编写他们自己的高级封装函数来简化那些常规需求的操作。
——————————————————
对于本文实例完整代码有需要的朋友,可关注并在评论区留言!
本文暂时没有评论,来添加一个吧(●'◡'●)