专业的JAVA编程教程与资源

网站首页 > java教程 正文

Qt编程进阶(97):使用SAX读取XML(qt编程实现写入文件)

temp10 2024-10-17 16:25:20 java教程 10 ℃ 0 评论

一、SAX是什么?

SAX事实上是公共领域中一种用于读取XML文档的标准应用程序编程接口。Qt的SAX类是对基于SAX2的Java实现的模拟,只是在命名上有些不太符合Qt的惯例。与DOM相比,SAX更加底层但通常也更加快速。然而,由于在前面文章曾介绍过的QXmlSimpleReader类提供了一个更接近Qt风格的应用程序编程接口,且比SAX解析器更加快速,因此SAX解析器的主要用途就是将使用SAX应用程序编程接口的代码导入Qt中。

Qt编程进阶(97):使用SAX读取XML(qt编程实现写入文件)

Qt提供了一个名为QXmlSimpleReader的基于SAX的非验证型XML解析器。这个解析器能够识别具有良好格式的XML文档并且支持XML文档的命名空间。当这个解析器遍历文档时,它调用注册的处理函数中的虚拟函数来表明解析事件。(这些“解析事件”和Qt事件并无联系,就像按键取件和鼠标事件一样)。我们假设这个解析器正在解析如下的文档:

<doc>
<quote>Gnothi seauton</quote>
</doc>

解析器将会调用如下这些解析事件处理函数:

startDocument()
startElement("doc")
startElement("quote")
characters("Gnothi seauton")
endElement(''quote”)
endElement("doc")
endDocument()

上述的这些函数都是在QXmlContentHandler中声明过的。为了简单,我们省略了startElement()和endElement()中的一些参数。

QXmlContentHandler只是可以和QXmlSimpleReader协作使用的众多处理程序类之一。其他的还有QXmlEntityResolver、QXmlDTDHandler、QXmlErrorHandler、QXmlDeclhandler和QXmlLexicalHandler。这些类仅仅声明纯虚函数并且给出不同类型的解析事件的相关信息。对于绝大多数应用程序来说,只有QXmlContentHandler和QXmlErrorHandler是必要的。这些类的层级关系如下图所示。

为了方便,Qt还提供了QXmlDefaultHandler,这是一个派生自所有处理程序类并且为所有函数都提供具体实现的类。含有很多抽象处理程序类和一个具体子类的这种设计构思,在Qt中并不常用,这里它被用来密切关注模型的java实现。

与使用QXmlStreamReader或者DOM应用程序接口相比,使用SAX最显著的区别在于:SAX需要我们利用成员变量手动追踪解析器的状态,而其他两种采用考虑向下递归的方法则不需要。

二、SAX读取XML文件实例

为了阐明如何使用SAX读取XML文件,我们将为前面文章提到的书刊索引文件格式编写一个解析器。这里将使用一个QXmlSimpleReader和一个名为SaxHandler的QXmlDefaultHandler子类来实现。程序运行界面如下:

1. SaxHandler类的定义

实现解析器的第一步是定义QXmlDefaultHandler的子类:

class SaxHandler : public QXmlDefaultHandler
{
public:
  SaxHandler(QTreeWidget *tree);
  bool readFile(const QString &fileName);
protected:
  bool startElement(const QString &namespaceURI,
  const QString &localName,
  const QString &qName,
  const QXmlAttributes &atts);
  bool endElement(const QString &namespaceURI,
  const QString &localName,
  const QString &qName);
  bool characters(const QString &ch);
  bool fatalError(const QXmlParseException &exception);
private:
  QTreeWidget *treeWidget;
  QTreeWidgetItem *currentItem;
  QString currentText;
};

SaxHandler类派生自QXmlDefaultHandler,并且重新实现了4个函数:startElement()、endElement()、characters()和fatalError()。前面二个函数都是在QXmlContentHandler中声明的,最后一个函数则是在QXmlErrorHandler中声明的。

2. SaxHandler类的实现

SaxHandler::SaxHandler(QTreeWidget *tree)
{
	treeWidget=tree;
}

SaxHandler构造函数接受用存储在XML文件中的信息进行组装的QTreeWidget。

bool SaxHandler::readFile(const QString &fileName)
{
  currentItem=0;
  QFile file(fileName);
  QXmlInputSource inputSource(&file);
  QXmlSimpleReader reader;
  reader.setContentHandler(this);
  reader.setErrorHandler(this);
  return reader.parse(inputSource);
}

当获得要解析文件的文件名时,就会调用这个函数。我们为文件创建一个QFile对象,同时创建一个QXmllnputSource以读取文件的内容。然后,创建一个QXmlSimpleReader来解析这个文件。我们设置该类(SaxHandler)的阅读器内容和错误处理程序,接着对阅读器调用parse()来执行解析。在SaxHandler中,仅从新实现了来自于QXmlContentHandler和QXmlErrorHandler类的函数。如果已经重新实现了来自于其他处理程序类的函数,则还需要调用相应的setXxxHandler()函数。

我们传递一个QXmlInputSource,而不是传递一个简单的QFile对象给parse()函数。这个类将打开并读取给定的文件(考虑了<?xml?>声明中指定的任意字符编码),同时它还提供了一个解析器读取文件的接口。

bool SaxHandler::startElement(const QString &namespaceURI,
const QString &localName, const QString &qName, const QXmlAttributes &atts)
{
  if(qName=="entry"){
    currentItem=new QTreeWidgetItem(currentItem ?
    	currentItem:treeWidget->invisibleRootItem());
    currentItem->setText(0,atts.value("term"));
  }else if (qName=="page") {
  	currentText.clear();
  }
  return true;
}

当阅读器遇到一个新的打开标签时,就会调用startElement()函数。第三个参数是标签的名称。第四个参数是属性列表。在这个实例中,忽略了第一和第二个参数。对于使用XML命名空间机制的XML文件,它们非常有用。

如果标签是<entry>,就创建一个新的QTreeWidgetItem项。如果标签嵌套在另一个<entry>中,则新的标签将在这个索引中定义一个子条目,并且这个新的QTreeWidgetItem会作为代表包含条目的QTreeWidgetItem的子对象而创建。不然,就创建QTreeWidgetItem作为顶级项,使用树形窗口的不可见根项作为它的父对象。我们调用setText(),将第0列中显示的文本值设置为这个<entry>标签的term属性。

如果标签是<page>,就将currentText变量设置为一个空字符串。该变量作为一个累加器,用于<page>和</page>标签之间的文本。

最后,我们返回true值让SAX继续解析这个文件。如果想把那些未知的标签也作为错误报告,这时就需要返回false值。然后,还可以在QXmlDefaultHandler中重新实现errorString(),以返回一个适当的出错信息。

bool SaxHandler::characters(const QString &ch)
{
  currentText+=ch;
  return true;
}

可以调用函数characters()报告XML文档中的字符数据。我们只把这些字符串添加到currentText变量中。

bool SaxHandler::endElement(const QString &namespaceURI,
	const QString &localName, const QString &qName)
{
  if(qName=="entry"){
  	currentItem=currentItem->parent();
  }else if(qName=="page"){
    if(currentItem){
      QString allPages=currentItem->text(1);
      if(!allPages.isEmpty())
      	allPages+=", ";
      allPages+=currentText;
      currentItem->setText(1,allPages);
    }
  }
  return true;
}

当阅读器遇到一个关闭标签时,就会调用endElement()函数。就像startElement()一样,第三个参数是标签的名称。

如果标签是</entry>,就更新currentItem私有变量,使他指向当前QTreeWidgetItem的父对象。这样可以确保currentItem变量能恢复为相应的<entry>标签被读取之前的值。

如果标签为</page>,则把指定的页码或者页码范围添加到第一列当前项的文本中,以逗号分隔。

bool SaxHandler::fatalError(const QXmlParseException &exception)
{
  std::cerr<<"Parse error at line "<<exception.lineNumber()
  	<<", "<<"column "<<exception.columnNumber()<<": "
  	<<qPrintable(exception.message())<<std::endl;
  return false;
}

当阅读器解析XML文件失败吋,就会调用fatalError()函数。如果这种情况发生,我们仅向控制台输出一条出错信息,给出行号、列号以及这个解析器的错误文本。

这样就完成了SaxHandler类的实现。它的使用与前面的DomParser几乎一样,不在复述。

——————————————————

对于本文实例完整代码有需要的朋友,可关注并在评论区留言!

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表