The OpenNET Project / Index page

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

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

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

DOM -- это стандарт API, для разбора XML-документов, разработанный в недрах World Wide Web Consortium (W3C). Qt предоставляет реализацию DOM Level 2 для чтения, изменения и записи XML-документов.

DOM представляет XML-файл в памяти, в виде древовидной структуры. У приложения имеется возможность перемещаться по этой структуре, как ему заблагорассудится. Программа может изменить содержимое дерева и сохранить его обратно в файл.

Рассмотрим следующий XML-документ:

<doc> 
  <quote>Errare humanum est</quote> 
  <translation>To err is human</translation> 
</doc>
      
Соответствующее ему дерево DOM:

Дерево состоит из узлов различного типа. Например, узел Element соответствует открывающему и парному закрывающему тегам. Все, что находится между ними, отображается в виде дочерних узлов.

В Qt, имена классов узлов начинаются с префикса QDom. Таким образом, класс QDomElement представляет узел Element, а QDomText -- узел Text.

Различные типы узлов могут включать в себя различные типы дочерних узлов. Например, узел Element может содержать другие узлы типа Element, а так же Entity Reference, Text, CDATA Section, Processing Instruction и Comment. На рисунке 14.3 показано, какие типы узлов, в состав каких типов могут входить.

Рисунок 14.3. Взаимоотношения между типами узлов в DOM.


Для демонстрации работы с XML-документами через API DOM, мы напишем парсер, который будет анализировать XML-файл, представленный выше.
class DomParser 
{ 
public: 
  DomParser(QIODevice *device, QListView *view); 
  
private: 
  void parseEntry(const QDomElement &element, 
                  QListViewItem *parent); 
  
  QListView *listView; 
};
      
Наш класс DomParser будет производить анализ XML-файла и выводить его содержимое в QListView. Этот класс не имеет предка.
DomParser::DomParser(QIODevice *device, QListView *view) 
{ 
  listView = view;
  
  QString errorStr; 
  int errorLine; 
  int errorColumn; 
  QDomDocument doc; 
  if (!doc.setContent(device, true, &errorStr, &errorLine, 
                      &errorColumn)) { 
    qWarning("Line %d, column %d: %s", errorLine, errorColumn, 
             errorStr.ascii()); 
    return; 
  } 
  
  QDomElement root = doc.documentElement(); 
  if (root.tagName() != "bookindex") { 
    qWarning("The file is not a bookindex file"); 
    return; 
  } 
  
  QDomNode node = root.firstChild(); 
  while (!node.isNull()) { 
    if (node.toElement().tagName() == "entry") 
      parseEntry(node.toElement(), 0); 
    node = node.nextSibling(); 
  } 
}
      
В конструкторе создается объект QDomDocument и вызывается его метод setContent(), чтобы прочитать XML-документ из QIODevice. Она автоматически открывает устройство. Затем вызывается documentElement(), чтобы получить корневой узел (со всеми дочерними узлами), и проверяется -- является ли этот элемент тегом <bookindex>. После этого выполняются итерации по всем дочерним узлам и если встречен тег <entry>, вызывается parseEntry() для его анализа.

Класс QDomNode может хранить узлы любого типа. Если вы собираетесь обрабатывать узлы какого-то конкретного типа, нужно сначала выполнить соответствующее преобразование. В этом примере нас интересуют только узлы типа Element, поэтому мы выполняем преобразование вызовом метода toElement(), чтобы получить узел типа QDomElement, и затем вызываем tagName(), чтобы прочитать имя тега. Если узел относится к другому типу, то функция toElement() вернет пустой объект QDomElement, с пустым именем тега.

void DomParser::parseEntry(const QDomElement &element, 
                           QListViewItem *parent) 
{ 
  QListViewItem *item; 
  if (parent) { 
    item = new QListViewItem(parent); 
  } else { 
    item = new QListViewItem(listView); 
  } 
  item->setOpen(true); 
  item->setText(0, element.attribute("term")); 
  
  QDomNode node = element.firstChild(); 
  while (!node.isNull()) { 
    if (node.toElement().tagName() == "entry") {
      parseEntry(node.toElement(), item); 
    } else if (node.toElement().tagName() == "page") { 
      QDomNode childNode = node.firstChild(); 
      while (!childNode.isNull()) { 
        if (childNode.nodeType() == QDomNode::TextNode) { 
          QString page = childNode.toText().data(); 
          QString allPages = item->text(1); 
          if (!allPages.isEmpty()) 
            allPages += ", "; 
          allPages += page; 
          item->setText(1, allPages); 
          break; 
        } 
        childNode = childNode.nextSibling(); 
      } 
    } 
    node = node.nextSibling(); 
  } 
}
      
В функции parseEntry() создается элемент списка QListView. Если тег вложен в другой тег <entry>, то создается вложенный элемент списка. В противном случае создается элемент списка верхнего уровня. Чтобы открыть элемент списка, вызывается setOpen(true) и затем в него записывается текст, отображаемый в первой колонке списка, вызовом функции setText() (содержимое атрибута term).

После инициализации QListViewItem, выполняются итерации по всем вложенным узлам, соответствующим данному тегу <entry>.

Если встречен тег <entry>, вызывается функция parseEntry(), которой, в качестве второго аргумента передается, текущий элемент списка. В результате будет создан новый элемент списка, вложенный в текущий.

Если встречен тег <page>, выполняется поиск узла Text. После того как он будет найден, выполняется преобразование узла, функцией toText(), в QDomText и из него извлекается текст в виде QString. Полученный таким образом текст добавляется в список номеров страниц, который отображается во второй колонке QListViewItem.

Теперь покажем, как можно использовать полученный класс DomParser:

void parseFile(const QString &fileName) { 
  QListView *listView = new QListView(0); 
  listView->setCaption(QObject::tr("DOM Parser")); 
  listView->setRootIsDecorated(true); 
  listView->setResizeMode(QListView::AllColumns); 
  listView->addColumn(QObject::tr("Terms")); 
  listView->addColumn(QObject::tr("Pages")); 
  listView->show(); 
  
  QFile file(fileName); 
  DomParser(&file, listView); 
}
      
Сначала создается и настраивается QListView. Затем создаются QFile и DomParser. Во время создания, DomParser выполняет разбор XML-документа и заполняет список.

Как показывает пример, навигация по DOM-дереву может оказаться весьма громоздкой. Простое извлечение текста, заключенного между тегами <page> и </page> потребовало от нас выполнения итераций по всему списку дочерних узлов, с помощью функций firstChild() и nextSibling(). Программисты, которые часто сталкиваются с необходимостью выполнения синтаксического анализа XML-документов, нередко пишут свои высокоуровневые классы-обертки, упрощающие выполнение наиболее часто используемых операций, таких как извлечение текста, заключенного между тегами.




Партнёры:
PostgresPro
Inferno Solutions
Hosting by Hoster.ru
Хостинг:

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