Чтение документов 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