</entry>
</bookindex>
Рис. 15.2. Файл предметного указателя книги, загруженный в виджет QTreeWidget.
Первый этап в реализации парсера заключается в создании подкласса QXmlDefaultHandler:
01 class SaxHandler : public QXmlDefaultHandler
02 {
03 public:
04 SaxHandler(QTreeWidget *tree);
05 bool startElement(const QString &namespaceURI,
06 const QString &localName,
07 const QString &qName,
08 const QXmlAttributes &attributes);
09 bool endElement(const QString &namespaceURI,
10 const QString &localName,
11 const QString &qName);
12 bool characters(const QString &str);
13 bool fatalError(const QXmlParseException &exception);
14 private:
15 QTreeWidget *treeWidget;
16 QTreeWidgetItem *currentItem;
17 QString currentText;
18 };
Класс SaxHandler наследует QXmlDefaultHandler и переопределяет четыре функции: startElement(), endElement(), characters() и fatalError(). Первые четыре функции объявлены в QXmlContentHandler; последняя функция объявлена в QXmlErrorHandler.
01 SaxHandler::SaxHandler(QTreeWidget *tree)
02 {
03 treeWidget = tree;
04 currentItem = 0;
05 }
Конструктор SaxHandler принимает объект типа QTreeWidget, который мы собираемся заполнять информацией, содержащейся в файле XML.
01 bool SaxHandler::startElement(const QString & /* namespaceURI */,
02 const QString & /* localName */,
03 const QString &qName,
04 const QXmlAttributes &attributes)
05 {
06 if (qName == "entry") {
07 if (currentItem) {
08 currentItem = new QTreeWidgetItem(currentItem);
09 } else {
10 currentItem = new QTreeWidgetItem(treeWidget);
11 }
12 currentItem->setText(0, attributes.value("term"));
13 } else if (qName == "page") {
14 currentText.clear();
15 }
16 return true;
17 }
Функция startElement() вызывается, когда обнаруживается новый открывающий тег. Третий параметр представляет собой имя тега (или точнее — «подходящее имя»). В четвертом параметре задается список атрибутов. В этом примере мы игнорируем первый и второй параметры. Они полезны для тех файлов XML, которые используют механизм пространств имен, подробно описанный в справочной документации.
Если обнаружен тег <entry>, мы создаем новый элемент списка QTreeWidget. Если данный тег является вложенным в другой тег <entry>, новый тег определяет подэлемент предметного указателя, и новый элемент QTreeWidgetItem создается как дочерний по отношению к внешнему элементу QTreeWidgetItem. В противном случае мы создаем элемент QTreeWidgetItem, используя в качестве родительского элемента объект treeWidget, делая его элементом верхнего уровня. Мы вызываем функцию setText() для отображения в столбце 0 текста со значением атрибута term тега <entry>.
Если обнаружен тег <page>, мы устанавливаем значение переменной currentText на пустую строку. В переменной currentText накапливается текст, расположенный между тегами <page> и </page>.
В конце мы возвращаем true, указывая SAX на необходимость продолжения синтаксического анализа файла. Если бы нам нужно было сообщить об ошибке из-за обнаружения неизвестного тега, мы возвращали бы в этих случаях false. Нам также потребовалось бы переопределить функцию errorString() класса QXmlDefaultHandler для возврата соответствующего сообщения об ошибке.
01 bool SaxHandler::characters(const QString &str)
02 {
03 currentText += str;
04 return true;
05 }
Функция characters() используется для извлечения символьных данных из документа XML. Мы просто добавляем символы в конец переменной currentText.
01 bool SaxHandler::endElement(const QString & /* namespaceURI */,
02 const QString & /* localName */, const QString &qName)
03 {
04 if (qName == "entry") {
05 currentItem = currentItem->parent();
06 } else if (qName == "page") {
07 if (currentItem) {
08 QString allPages = currentItem->text(1);
09 if (!allPages.isEmpty())
10 allPages += ", ";
11 allPages += currentText;
12 currentItem->setText(1, allPages);
13 }
14 }
15 return true;
16 }
Функция endElement() вызывается при обнаружении закрывающего тега. Так же как и для функции startElement(), ее третий параметр содержит имя тега.