The OpenNET Project / Index page

[ новости /+++ | форум | wiki | теги | ]

Каталог документации / Раздел "Программирование, языки" / Оглавление документа

Глава 14. XML

XML (от англ. Extensible Markup Language -- Расширяемый Язык Разметки) -- популярный формат файлов, используемый для обмена и хранения данных в текстовом виде.

Для работы с XML документами, Qt поддерживает два различных API:

В каждом конкретном случае, при выборе того или иного API, необходимо учитывать множество факторов. SAX -- более быстрый, он больше подходит для выполнения простых задач (например, чтобы найти все вхождения заданного тега в документе), и для работы с XML-файлами огромного размера, которые могут не уместиться в памяти целиком. DOM -- более удобен, в большинстве приложений, фактор удобства перевешивает быстроту и нетребовательность SAX.

В этой главе мы покажем, как работать с XML-файлами посредством обоих API.

14.1. Чтение XML-документов с помощью SAX.

SAX -- это (де-факто) Java API стандарт для чтения XML-документов. Классы SAX, в библиотеке Qt, моделируют реализацию SAX2 Java, с небольшими отличиями в именованиях. Дополнительную информацию о SAX вы найдете по адресу: http://www.saxproject.org/.

Qt предоставляет SAX-парсер QXmlSimpleReader. Он распознает правильно оформленные XML-документы и поддерживает пространства имен XML. Во время анализа документа вызываются виртуальные функции классов-обработчиков событий разбора. (В данном случае, понятие "событие разбора" никак не пересекается с понятием событий в Qt.) Например, предположим, что парсер анализирует XML-документ со следующим содержимым:

<doc> 
  <quote>Errare humanum est</quote> 
</doc>  
      
В этом случае парсер мог бы вызвать следующие обработчики событий разбора:
startDocument() 
startElement("doc") 
startElement("quote") 
characters("Errare humanum est") 
endElement("quote") 
endElement("doc") 
endDocument()
      
Все вышеприведенные функции определены в классе QXmlContentHandler. С целью упрощения примера мы не приводим некоторые аргументы в функциях startElement() и endElement().

Класс QXmlContentHandler -- лишь один из многих, которые могут работать совместно с QXmlSimpleReader. Среди других классов можно назвать: QXmlEntityResolver, QXmlDTDHandler, QXmlErrorHandler, QXmlDeclHandler и QXmlLexicalHandler. Они реализуют исключительно виртуальные функции и предоставляют сведения о различного типа событиях разбора. В большинстве приложений используются только два класса: QXmlContentHandler и QXmlErrorHandler.

Для большего удобства, Qt так же предоставляет класс QXmlDefaultHandler, который наследует (через множественное наследование) и реализует все виртуальные функции других классов-обработчиков. Такая архитектура, со множеством абстрактных классов и единственным классом-наследником, довольно необычна для Qt, однако, она была принята в соответствии с моделью реализации, принятой в Java.

Рассмотрим на примере, как можно использовать классы QXmlSimpleReader и QXmlDefaultHandler для разбора XML-файла и отображения его содержимого в QListView. Наш класс, производный от класса QXmlDefaultHandler, будет называться SaxHandler. В его задачи будет входить разбор XML-документа, представляющего собой список терминов, использовавшихся в книге.

Рисунок 14.1. Дерево наследования класса SaxHandler.


Ниже приведен XML-файл, содержимое которого отображается в QListView, на рисунке 14.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>
      

Рисунок 14.2. Файл со списком терминов, использованных в книге, загруженный в QListView.


Сначала создадим определение класса-обработчика:
class SaxHandler : public QXmlDefaultHandler 
{ 
public: 
  SaxHandler(QListView *view); 
  
  bool startElement(const QString &namespaceURI, 
                    const QString &localName, 
                    const QString &qName, 
                    const QXmlAttributes &attribs); 
  bool endElement(const QString &namespaceURI, 
                  const QString &localName, 
                  const QString &qName); 
  bool characters(const QString &str); 
  bool fatalError(const QXmlParseException &exception); 
  
private: 
  QListView *listView; 
  QListViewItem *currentItem; 
  QString currentText; 
};
      
Класс SaxHandler порожден от класса QXmlDefaultHandler и перекрывает четыре метода родителя: startElement(), endElement(), characters() и fatalError(). Первые три функции объявлены в классе QXmlContentHandler, последняя функция -- в QXmlErrorHandler.
SaxHandler::SaxHandler(QListView *view) 
{ 
  listView = view; 
  currentItem = 0; 
}
      
Конструктор получает указатель на QListView, который будет заполняться информацией из XML-файла.
bool SaxHandler::startElement(const QString &, const QString &, 
                              const QString &qName, 
                              const QXmlAttributes &attribs) 
{ 
  if (qName == "entry") { 
    if (currentItem) { 
      currentItem = new QListViewItem(currentItem); 
    } else { 
      currentItem = new QListViewItem(listView); 
    } 
    currentItem->setOpen(true); 
    currentItem->setText(0, attribs.value("term")); 
  } else if (qName == "page") { 
    currentText = ""; 
  } 
  return true; 
}
      
Функция startElement() вызывается, когда парсер встречает новый открывающий тег. Третий аргумент -- это имя тега. Четвертый -- список атрибутов. В данном примере мы будем игнорировать первый и второй аргументы. Они предназначены для работы с XML-файлами, которые используют механизм пространств имен.

Если это тег <entry>, создается новый элемент списка QListView. Если анализируемый тег вложен в другой тег <entry>, создается вложенный подэлемент списка -- QListViewItem. В противном случае создается элемент списка верхнего уровня. Функция setOpen(true) вызывается для того, чтобы открыть вложенные подэлементы данного элемента. Функция setText() записывает текст (значение атрибута term), который будет отображаться на экране в первой колонке списка.

Если это тег <page>, то в currentText записывается пустая строка. Переменная currentText служит своего рода аккумулятором для текста, размещаемого между тегами <page> и </page>.

В заключение, в вызывающую программу возвращается true, чтобы сообщить парсеру SAX о том, что он может продолжить разбор файла. В случае неопознанного тега, можно вернуть false, чтобы известить парсер об ошибке. В этом случае необходимо тогда перекрыть метод errorString(), унаследованный от QXmlDefaultHandler, чтобы вернуть соответствующее сообщение об ошибке.

bool SaxHandler::characters(const QString &str) 
{ 
  currentText += str; 
  return true; 
}
      
Функция characters() вызывается для передачи символьных данных из XML-файла. В нашем случае мы просто добавляем их в конец переменной currentText.
bool SaxHandler::endElement(const QString &, const QString &, 
                            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>, то текущим назначается элемент более высокого уровня. Таким образом восстанавливается значение переменной, которое предшествовало открывающему тегу <entry>.

Если это тег </page>, производится добавление номеров в список страниц, которые отображаются во второй колонке списка.

bool SaxHandler::fatalError(const QXmlParseException &exception) { 
  qWarning("Line %d, column %d: %s", exception.lineNumber(), 
            exception.columnNumber(), exception.message().ascii()); 
  return false; 
}
      
Функция fatalError() вызывается, когда парсер не может продолжить разбор XML-файла. Тогда мы просто выводим сообщение, с указанием номера строки и позиции в строке, где была обнаружена ошибка.

На этом мы завершаем обзор реализации класса SaxHandler и переходим к демонстрации практического его применения:

bool parseFile(const QString &fileName) 
{ 
  QListView *listView = new QListView(0); 
  listView->setCaption(QObject::tr("SAX Handler")); 
  listView->setRootIsDecorated(true); 
  listView->setResizeMode(QListView::AllColumns); 
  listView->addColumn(QObject::tr("Terms")); 
  listView->addColumn(QObject::tr("Pages"));
  listView->show(); 
  
  QFile file(fileName); 
  QXmlSimpleReader reader; 
  
  SaxHandler handler(listView); 
  reader.setContentHandler(&handler); 
  reader.setErrorHandler(&handler); 
  return reader.parse(&file); 
}
      
Сначала создается виджет QListView с двумя колонками. Затем создаются объект QFile, посредством которого будет выполняться работа с файлом XML-документа, и QXmlSimpleReader -- сам парсер. У нас нет необходимости открывать файл -- за нас это сделает сама библиотека Qt.

В заключение создается объект SaxHandler. Мы передаем его парсеру, как обработчик событий разбора и как обработчик ошибок. И наконец запускаем процесс разбора, вызовом parse().




Спонсоры:
Inferno Solutions
Hosting by Hoster.ru
Хостинг:

Закладки на сайте
Проследить за страницей
Created 1996-2021 by Maxim Chirkov
Добавить, Поддержать, Вебмастеру