Чтение документов XML при помощи интерфейса SAX
Чтение документов XML при помощи интерфейса SAX
SAX является фактическим стандартом программного интерфейса с открытым исходным кодом, который обеспечивает чтение документов XML.
Классы Qt для интерфейса SAX моделируют реализацию SAX2 Java с некоторыми отличиями в названиях для обеспечения принятых в Qt правил обозначений названий классов и их членов. Более подробную информацию относительно SAX можно получить в сети Интернет по адресу http://www.saxproject.org/.
Qt обеспечивает построенный на основе интерфейса SAX парсер документов XML, не предусматривающий проверку их достоверности под названием QXmlSimpleReader. Этот парсер распознает хорошо сформированные документы XML и поддерживает пространства имен XML. Когда парсер обрабатывает документ, он вызывает виртуальные функции в зарегистрированных классах—обработчиках, уведомляющих о возникновении соответствующих событий в ходе синтаксического анализа документа. (Эти события никак не связаны с такими событиями Qt, как события клавиатуры и события мышки.) Например, пусть парсер выполняет анализ следующего документа XML:
<doc>
<quote> Ars longa vita brevis</quote>
</doc>
В этом случае парсер вызовет следующие обработчики событий синтаксического анализа:
startDocument()
startElement("doc")
startElement("quote")
characters("Ars longa vita brevis")
endElement("quote")
endElement("doc")
endDocument()
Все приведенные выше функции объявлены в классе QXmlContentHandler. Для простоты мы не стали указывать некоторые аргументы функций startElement() и endElement().
QXmlContentHandler — это всего лишь один из многих классов—обработчиков, которые могут использоваться совместно с классом QXmlSimpleReader. Другими такими классами являются QXmlEntityResolver, QXmlDTDHandler, QXmlErrorHandler, QXmlDeclHandler и QXmlLexicalHandler. Эти классы только объявляют чистые виртуальные функции и предоставляют информацию о различных событиях синтаксического анализа. Для большинства приложений вполне достаточно использовать лишь классы QXmlContentHandler и QXmlErrorHandler.
Для удобства Qt также предоставляет класс QXmlDefaultHandler, который наследует все классы—обработчики и обеспечивает очень простую реализацию всех функций. Такая конструкция со множеством абстрактных классов—обработчиков и одним подклассом с тривиальной реализацией функций необычна для Qt; она принята для максимального соответствия модели Java—реализации.
Теперь мы рассмотрим пример, который показывает способы применения QXmlSimpleReader и QXmlDefaultHandler для синтаксического анализа файла XML заранее известного формата и для отображения его содержимого в виджете QTreeWidget. Подкласс QXmlDefaultHandler имеет название SaxHandler, и он используется для обработки предметного указателя книги, который содержит элементы и подэлементы.
Рис. 15.1. Дерево наследования для SaxHandler.
Ниже приводится файл предметного указателя книги, который отображается в виджете QTreeWidget и показан на рис. 15.2:
<?xml version="1.0"?>
<bookindex>
<entry term="sidebearings">
<page>10</page>
<page>34-35</page>
<page>307-308</page>
</entry>
<entry term="subtraction">
<entry term="of pictures">
<page>115</page>
<page>244</page>
</entry>
<entry term="of vectors">
<page>9</page>
</entry>
</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(), ее третий параметр содержит имя тега.
Если обнаружен тег </entry>, мы устанавливаем закрытую переменную currentItem на родительский элемент текущего элемента QTreeWidgetItem. Это обеспечивает восстановление переменной currentItem на значение, которое она имела перед чтением соответствующего тега <entry>.
Если обнаружен тег </page>, мы добавляем указанный номер страницы или диапазон страниц в разделяемый запятыми список в столбце 1 текущего элемента.
01 bool SaxHandler::fatalError(const QXmlParseException &exception)
02 {
03 QMessageBox::warning(0, QObject::tr("SAX Handler"),
04 QObject::tr("Parse error at line %1, column %2: %3.")
05 .arg(exception.lineNumber())
06 .arg(exception.columnNumber())
07 .arg(exception.message()));
08 return false;
09 }
Функция fatalError() вызывается, когда синтаксический анализ файла XML завершается неудачей. В этом случае мы просто выводим на экран сообщение, указывая номер строки, номер столбца и текст об ошибке синтаксического анализа.
Этим мы завершаем реализацию класса SaxHandler. Теперь давайте посмотрим, как можно использовать этот класс:
01 bool parseFile(const QString &fileName)
02 {
03 QStringList labels;
04 labels << QObject::tr("Terms") << QObject::tr("Pages");
05 QTreeWidget *treeWidget = new QTreeWidget;
06 treeWidget->setHeaderLabels(labels);
07 treeWidget->setWindowTitle(QObject::tr("SAX Handler"));
08 treeWidget->show();
09 QFile file(fileName);
10 QXmlInputSource inputSource(&file);
11 QXmlSimpleReader reader;
12 SaxHandler handler(treeWidget);
13 reader.setContentHandler(&handler);
14 reader.setErrorHandler(&handler);
15 return reader.parse(inputSource);
16 }
Мы задаем два столбца в виджете QTreeWidget. Затем мы создаем объект типа QFile для считываемого файла и объект типа QXmlSimpleReader для синтаксического анализа файла. Нам не требуется самим открывать QFile; QXmlInputSource делает это автоматически.
Наконец, мы создаем объект типа SaxHandler, который используется для объекта reader одновременно в качестве обработчика содержимого файла и обработчика ошибок, и мы вызываем функцию parse() для выполнения синтаксического анализа.
Вместо простого объекта файла мы передаем функции parse() объект QXmlInputSource. Этот класс открывает заданный файл, читает его (учитывая кодировку символов в объявлении <?xml?>) и предоставляет интерфейс для чтения файла парсером.
В классе SaxHandler мы всего лишь переопределили функции, унаследованные от классов QXmlContentHandler и QXmlErrorHandler. Если бы мы стали переопределять функции других классов—обработчиков, нам пришлось бы вызывать соответствующие функции—установщики для объекта reader.
Для сборки приложения с библиотекой QtXml в файл .pro необходимо добавить следующую строку:
QT += xml