程序运行效果图
负责处理xml文件的是XbelWriter,XbelReader,界面的实现是由MainWindow完成的
保存后的xbel文件前几行是这样的
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xbel>
<xbel version="1.0">
<folder folded="yes">
<title>Literate Programming</title>
<bookmark href="http://www.vivtek.com/litprog.html">
<title>Synopsis of Literate Programming</title>
</bookmark>
如果不断用readNext(),即以下几行代码读取文件的话
while (!xml.atEnd()) {
qDebug() << "name " << xml.name() << "tokentype " << xml.tokenType();
if (xml.isCharacters()) {
qDebug() << xml.text();
}
xml.readNext();
}
前几行输出:
name "" tokentype 0
name "" tokentype 2
name "" tokentype 8
name "xbel" tokentype 4
name "" tokentype 6
"
"
name "folder" tokentype 4
name "" tokentype 6
"
"
name "title" tokentype 4
name "" tokentype 6
"Literate Programming"
name "title" tokentype 5
name "" tokentype 6
"
"
name "bookmark" tokentype 4
name "" tokentype 6
"
"
name "title" tokentype 4
name "" tokentype 6
"Synopsis of Literate Programming"
name "title" tokentype 5
结合源代码里关于Tokentype的enum定义:
enum TokenType {
NoToken = 0,
Invalid,
StartDocument,
EndDocument,
StartElement,
EndElement,
Characters,
Comment,
DTD,
EntityReference,
ProcessingInstruction
};
可以看出:
1.readNext就是一个一个的来读,碰到<xxx>,</xxx>,以及...>xxx<...的文字
理一下读取的流程:
1.没有读取任何东西,NoToken
2.读取<?xml version="1.0" encoding="UTF-8"?>,StartDocument
3.读取<!DOCTYPE xbel>,DTD
4.读取<xbel version="1.0">,StartElement
5.读取<xbel version="1.0">与<folder folded="yes">之间的characters,如果打印相应的unicode出来的话,是10 32 32 32 32
即1个换行+4个空格,直接打印就是
"
"
这个了,可见...>xxx<...的文字都会读取,即使是换行/空格之类的
6.读取<folder folded="yes">,StartElement
。。。。。。
2.
遇到这种<separator/>则类似于<separator></separator>
name "separator" type 4
name "separator" type 5
利用void QXmlStreamWriter::writeEmptyElement("separator")可以写入成这样
1.XbelWriter Class
写入的过程其实是很简单的,具体看代码:
bool XbelWriter::writeFile(QIODevice *device)
{
xml.setDevice(device);
xml.writeStartDocument();//生成L1
xml.writeDTD("<!DOCTYPE xbel>");//生成L2
xml.writeStartElement("xbel");//与下一行共同生成L3
xml.writeAttribute("version", "1.0");
for (int i = 0; i < treeWidget->topLevelItemCount(); ++i)
writeItem(treeWidget->topLevelItem(i));
xml.writeEndDocument();
return true;
}
2.XbelReader Class
首先读取到合适的Element
bool XbelReader::read(QIODevice *device)
{
xml.setDevice(device);
if (xml.readNextStartElement()) {
if (xml.name() == "xbel" && xml.attributes().value("version") == "1.0")//即读到name为xbel,属性version为1.0的Element时
//The actual process of reading only takes place if the file is a valid XBEL 1.0 file.
readXBEL();
else
xml.raiseError(QObject::tr("The file is not an XBEL version 1.0 file."));
}
return !xml.error();
}
由注释可以看到工作函数是readXBEL(),整个流程由这几行代码完成(里面函数用到递归)
void XbelReader::readXBEL()
{
Q_ASSERT(xml.isStartElement() && xml.name() == "xbel");
while (xml.readNextStartElement()) {
if (xml.name() == "folder")
readFolder(0);
else if (xml.name() == "bookmark")
readBookmark(0);
else if (xml.name() == "separator")
readSeparator(0);
else
xml.skipCurrentElement();
}
}
看下skipCurrentElement()的源代码:
void QXmlStreamReader::skipCurrentElement()
{
int depth = 1;
while (depth && readNext() != Invalid) {
if (isEndElement())
--depth;
else if (isStartElement())
++depth;
}
},可以看到变量depth在进入一个新的element则+1,出来则-1
因此直到现在的这个element结束(遇到相应的</xxx>),函数才结束退出
Qt Assistant里的注释:
The readXBEL() function reads the name of a startElement and calls the appropriate function to read it, depending on whether if its a "folder", "bookmark" or "separator". Otherwise, it calls skipCurrentElement().
即如果Element的name为folder,bookmark,separator时,读入内容,否则skip
具体会遇到的有这么几种:
<folder folded="yes">
<title>Literate Programming</title>
<bookmark href="http://www.vivtek.com/litprog.html">
<separator/>
需要不同的函数来处理
例子里分别用了
readTitle(),readSeparator(),readFolder(),readBookmark()
我主要学习了下readFolder()
fold有两种情况
<folder folded="yes">
<folder folded="no">
读取时差别的处理代码
QTreeWidgetItem *folder = createChildItem(item);
bool folded = (xml.attributes().value("folded") != "no");
treeWidget->setItemExpanded(folder, !folded);
第三行注释掉后对比下图片
可见默认是不expand的
Sets the item referred to by item to either closed or opened, depending on the value of expand.
This function is deprecated. Use QTreeWidgetItem::setExpanded() instead.
void QTreeWidgetItem::setExpanded ( bool expand )
Expands the item if expand is true, otherwise collapses the item
主要是展开还是摺叠项。
folder下面的element可能name为title,bookmark,separator,还可能再由folder,像Useful C++ Links下面还有STL Qt等,需要递归处理,因此还需要以下代码
while (xml.readNextStartElement()) {
if (xml.name() == "title")
readTitle(folder);
else if (xml.name() == "folder")
readFolder(folder);
else if (xml.name() == "bookmark")
readBookmark(folder);
else if (xml.name() == "separator")
readSeparator(folder);
else
xml.skipCurrentElement();
}
只不过跟readXBEL里参数不同
再看下while-loop condition
bool QXmlStreamReader::readNextStartElement ()
Reads until the next start element within the current element. Returns true when a start element was reached. When the end element was reached, or when an error occurred, false is returned.
The current element is the element matching the most recently parsed start element of which a matching end element has not yet been reached. When the parser has reached the end element, the current element becomes the parent element.
This is a convenience function for when you're only concerned with parsing XML elements.
看下该函数源代码:
bool QXmlStreamReader::readNextStartElement()
{
while (readNext() != Invalid) {
if (isEndElement())
return false;
else if (isStartElement())
return true;
}
return false;
}
可以看到是读取下一个记号,如果是endElement,则返回false,否则true
再对比下xml文件看下该函数是如何用来在这个大的element内部遍历的
<folder folded="yes">
<title>Literate Programming</title>
<bookmark href="http://www.vivtek.com/litprog.html">
<title>Synopsis of Literate Programming</title>
</bookmark>
<bookmark href="http://vasc.ri.cmu.edu/old_help/Programming/Literate/literate.html">
<title>Literate Programming: Propaganda and Tools</title>
</bookmark>
1.读到记号为<title>,进入readTitle()
2.读取内容,记号移动到</title>
3.进入while条件,即readNextStartElement(),此时虽然TokenType是EndElement,但是readNext后,变为startElement,即bookmark,进入readBookmark()
4.同folder,bookmark也一直要读到</bookmark>
再看下
QTreeWidgetItem *XbelReader::createChildItem(QTreeWidgetItem *item)
{
QTreeWidgetItem *childItem;
if (item) {
childItem = new QTreeWidgetItem(item);
} else {
childItem = new QTreeWidgetItem(treeWidget);
}
childItem->setData(0, Qt::UserRole, xml.name().toString());
return childItem;
}
setData参数可以指定不同的role
Qt::UserRole 32 The first role that can be used for application-specific purposes.
通过相应的data函数可以取出该QString
通过给
QTreeWidgetItem的构造函数传入不同的参数,可以传入QTreeWidget,也可以是QTreeWidgetItem
QTreeWidget会根据各个item的关系而排列好缩进?关于QTreeWidget还没用过,有空要学习一下。