Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Programming / C++Programming / Qt / Qt-doc.ru / Работа с XML

.pdf
Скачиваний:
75
Добавлен:
12.02.2016
Размер:
309.02 Кб
Скачать

В настоящее время формат XML (Extensible Markup Language, расширяемый язык разметки) — одна из самых активно используемых технологий. Зайдя в книжный магазин, вы, наверное, поразитесь количеству книг, посвященных XML. С распространением Интернета обмен данными между разными платформами стал необходимостью для программ, работающих с данными. Это и послужило поводом для создания XML.

Разработка XML началась в 1996 г. и была стандартизирована организацией W3C (World Wide Web Consortium). На самом деле, XML не представляет собой ничего нового и просто является упрощенной версией языка SGML, разработанного в начале 80-х. Разработчики XML переняли из него все самое лучшее, с учетом опыта языка HTML, и создали нечто, по своей мощности не уступающее SGML, но, вместе с тем, гораздо удобнее и проще как для понимания, так и для использования.

XML не имеет лицензии, что позволяет бесплатно использовать этот формат в своих программах. Всеобщая поддержка XML исключает зависимость от отдельной фирмы или платформы, оказывающей поддержку для XML. Так как XML-документ представляет собой текст в формате ASCII или Unicode, то он является читабельным и для человека. XML-документ может быть изменен при помощи простых текстовых редакторов, например, программой Notepad (Блокнот) из стандартного набора ОС Windows.

Основные понятия и структура XML-документа

XML — это средство хранения структурных данных в текстовом файле. Примером структурированных данных являются: адресная книга, генеалогическое дерево, информация о продуктах и т. д. XML очень похож на HTML. Язык XML описывает структуру самого документа без детализации его отображения. Также следует заметить, что XML гораздо строже, чем HTML, например, при ошибке спецификация XML запрещает приложению, работающему с XML-документом, предпринимать попытку корректировки. В подобных случаях приложение должно прекратить считывание дефектного файла и сообщить об ошибке.

Например, упрощенный вариант XML-документа, который представляет собой структуру адресной книги, предназначенную для хранения имен, телефонов и адресов электронной почты, мог бы выглядеть следующим образом:

<?xml version = "1.0"?> <!-- My Address Book --> <addressbook>

<contact number = "1"> <name>Piggy</name> <phone>+49 631322187</phone> <email>piggy@mega.de</email>

</contact>

<contact number = "2"> <name>Kermit</name> <phone>+49 631322181</phone> <email>kermit@mega.de</email>

</contact>

</addressbook>

Любой XML-документ начинается с заголовка, говорящего о принадлежности к этому формату и указывающего номер версии. Сам XML-документ состоит из множества элементов (elements). Имена элементов заключаются между символами < и >, образуя теги. Теги нужны для обозначения границ элемента. Например, начало элемента обозначается открывающим тегом <name>, а конец — закрывающим </name>. Между этими тегами может находиться содержание, например <name>Piggy</name>, но это необязательно.

Иногда встречаются элементы, не имеющие содержания, например — <empty></empty>. Для подобных случаев спецификация XML предусматривает сокращенную форму — например, предыдущую надпись можно заменить

на <empty/>. Может показаться, что подобные теги не могут содержать информацию, но это не так — информацию можно сохранить при помощи атрибутов, следующих за именем элемента. Например, <empty number = "1"></empty> или <empty number = "1"/>. Комментарии представляют собой от одной до нескольких строк текста, ограниченных тегами <!-- и -->.

Основные отличия тегов HTML от тегов XML заключаются в следующем:

теги используются только для сохранения информации, а интерпретация этих данных целиком лежит на приложении, которое их считывает. Например, в XML тег <р> необязательно является тегом параграфа, как у HTML, а может обозначать, например, properties (свойства);

для описания документов можно использовать теги с любыми подходящими названиями. Следует учитывать, что строчные и прописные буквы в именах различаются, так, например, <Tag></Tag> и <tag></tag> являются разными тегами.

На рисунке показана часть графического эквивалента структуры. Обратите внимание, что элементы XML-документа задают иерархическую структуру в виде дерева.

Библиотека Qt очень активно использует формат XML. Например, Ot Designer сохраняет файлы пользовательского интерфейса именно в этом формате. Также он используется утилитами Qt, предназначенными для интернационализации приложений.

Поддержка XML в Qt — это отдельный модуль QtXml, для использования которого необходимо указать его имя в проектном файле. Просто добавьте следующую строку:

QT += xml

А для того чтобы работать с классами этого модуля, необходимо включить заголовочный метафайл QtXml.

#include <QtXml>

Qt предоставляет две возможности использования XML. Первая называется DOM, вторая — SAX. Нельзя однозначно сказать, что одна из них лучше другой. Каждая имеет свои преимущества.

DOM (Document Object Model, объектная модель документа) — это стандартное API для анализа XML-документов, разработанное W3C. Qt поддерживает второй уровень реализации, следующий рекомендациям W3C и включающий в себя поддержку пространства имен (name spaces). Самое большое преимущество DOM состоит в возможности представления XML-документа в виде древовидной структуры, в памяти компьютера. Цена этого удобства очевидна — большой расход памяти. Но если на том компьютере, где запускается ваша программа, нет недостатка в оперативной памяти, то использование DOM будет наиболее подходящим решением. На рисунке отображена иерархия классовQDomNode, предоставляемого Qt для работы с DOM. Доступ ко всем классам DOMможно получить включением заголовочного файла QtXml. Самые используемые из этих классов —

это QDomNode, QDomElement, QDomAttr и QDomText.

Класс QDomElement создан для представления элементов. Иерархия DOM содержит узлы различного типа. Например, узел элемента соответствует открытому и закрытому тегу. Данные, находящиеся между этими тегами, представляют собой узлы потомков типа "элемент". Все узлы иерархии DOM являются объектами класса QDomNode, которые способны содержать в себе любые типы узлов. Для проведения операций над узлом его, прежде всего, необходимо преобразовать к нужному типу. Для преобразования объектов QDomNode вQDomElement следует воспользоваться методомQDomNode::toElement(). Нужно всегда проверять возвращаемое этим методом значение, ведь в случае ошибки будет возвращено нулевое значение, которое можно проверить методомisNull().

{рисунок}

Пример программы осуществляет чтение XML-документа и выводит его данные на экран в следующем виде:

TEMPLATE

= app

QT

+= xml

SOURCES

= main.cpp

 

 

win32:CONFIG += console

win32:TARGET = ../XmlDOMRead

Для поддержки XML Qt предоставляет отдельный модуль, для включения которого его нужно указать в секции qt pro-файла. Обратите внимание на секцию config, в которой добавилось значение console. Это нужно для того, чтобы приложение могло осуществлять вывод на консоль в ОС Windows.

#include <QtXml>

void traverseNode(const QDomNode& node)

{

QDomNode domNode = node.firstChild(); while(!domNode.isNull()) {

if(domNode.isElement()) {

QDomElement domElement = domNode.toElement(); if(!domElement.isNull()) {

if(domElement.tagName() == "contact") { qDebug() << "Attr: "

<< domElement.attribute("number", "");

}

else {

qDebug() << "TagName: " << domElement.tagName() << "\tText: " << domElement.text();

}

}

}

traverseNode(domNode);

domNode = domNode.nextSibling();

}

}

int main()

{

QDomDocument domDoc;

QFile file("addressbook.xml");

if(file.open(QIODevice::ReadOnly)) { if(domDoc.setContent(&file)) {

QDomElement domElement= domDoc.documentElement();

traverseNode(domElement);

}

file.close();

}

return 0;

}

В функции main() создаются объекты классов QDomDocument и QFile. Объект классаQDomDocument представляет собой XML-документ. Для считывания XMLдокумента в метод setContext() объекта класса QDomDocument необходимо передать объект, произведенный от класса, унаследованного от QIODevice. В данном случае это классQFile.

Далее, для получения объекта класса QDomElement производится вызов методаdocumentElement() объекта класса QDomDocument. Полученный объект передается в функцию traverseNode(), которая обеспечивает прохождение по всем элементам XML-документа.

Для прохождения по иерархии DOM удобно применить рекурсию. Из рекурсивной функции traverseNode() вызываются методы firstChild() и nextSibling() объекта классаQDomNode. Ели метод nextSibling() возвращает нулевое значение, то это значит, что узлов больше нет. Все получаемые узлы проверяются в цикле с помощью методаisElement(). В том случае, если полученный узел является элементом, производится преобразование его к типу QDomElement, с помощью метода toElement(), с последующим присвоением результата объекту

класса QDomEiement. При обнаружении элемента с именем "contact", с помощью метода attribute() производится отображение значения его атрибута number. В остальных случаях вызываются методыtagName() и text() для отображения имени и данных элемента.

При создании XML-документа необходимо иметь в своем распоряжении механизм создания элементов. Для этого класс QDomDocument содержит серию методов:

например, createElement(),createTextNode(), createAttribute().

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

int main()

{

QDomDocument doc("addressbook");

QDomElement domElement = doc.createElement("adressbook"); doc.appendChild(domElement);

QDomElement contact1 =

contact(doc, "Piggy", "+49 631322187", "piggy@mega.de");

QDomElement contact2 =

contact(doc, "Kermit", "+49 631322181", "kermit@mega.de");

QDomElement contact3 =

contact(doc, "Gonzo", "+49 631322186", "gonzo@mega.de");

domElement.appendChild(contact1);

domElement.appendChild(contact2);

domElement.appendChild(contact3);

QFile file("adressbook.xml"); if(file.open(QIODevice::WriteOnly)) {

QTextStream(&file) << doc.toString(); file.close();

}

return 0;

}

После создания объекта класса QDomDocument необходимо создать начальный элемент, который представляет собой начальный узел создаваемой иерархии. В нашем случае этот элемент имеет имя addressbook. Вызов метода appendChild() добавляет созданный элемент. Вызов функции contact() создает элемент, содержащий контактную информацию. Эти элементы добавляются

методом appendChild() в начальный элемент документа domElement. Для записи XML-документа в файл необходимо вызвать метод toString(), который возвратит текстовое представление XML-документа, и после этого произвести запись в файл.

QDomElement contact(

QDomDocument& domDoc,

 

const QString&

strName,

 

const QString&

strPhone,

 

 

 

 

const QString&

strEmail

)

 

 

 

 

 

{

static int nNumber = 1;

QDomElement domElement = makeElement(domDoc, "contact",

QString().setNum(nNumber) );

domElement.appendChild(makeElement(domDoc, "name", "", strName)); domElement.appendChild(makeElement(domDoc, "phone", "", strPhone)); domElement.appendChild(makeElement(domDoc, "email", "", strEmail));

nNumber++;

return domElement;

}

Функция contact() содержит статическую переменную, увеличивающую свое значение после каждого вызова функции. Это необходимо для присвоения контактным адресам определенного номера. Из этой функции производится вызов функции makeElement()для создания элементов, представляющих собой составные части контактного адреса.

QDomElement makeElement(

QDomDocument& domDoc,

 

 

 

 

const QString&

strName,

 

const QString&

strAttr = QString::null,

 

const QString&

strText = QString::null

)

 

 

{

 

 

 

 

 

QDomElement domElement = domDoc.createElement(strName);

if (!strAttr.isEmpty()) {

QDomAttr domAttr = domDoc.createAttribute("number"); domAttr.setValue(strAttr); domElement.setAttributeNode(domAttr);

}

if (!strText.isEmpty()) {

QDomText domText = domDoc.createTextNode(strText); domElement.appendChild(domText);

}

return domElement;

}

В функции makeElement() производится создание элемента с помощью методаcreateElement() объекта класса QDomDocument. Если третьим параметром было передано значение атрибута, то к элементу будет добавлен атрибут number. Его создание производится вызовом метода createAttribute() объекта классаQDomDocument. Вызов метода setValue() присвоит этому атрибуту переданное в метод значение. Если в четвертом параметре функции была передана строка текста, то методом createTextNode() объекта класса QDomNode будет создан текстовый узел. Вызов метода appendChild() внесет текстовую информацию в объект элемента.

Ввиду большого расхода памяти, работа с моделью DOM не всегда желательна или возможна. Существует принципиально другой способ для анализа XML-документов

— это SAX.

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

Чтение XML-документа

Класс QXmlSimpleReader представляет собой XML-анализатор, базирующийся на SAX. Он читает XML-документ блоками и сообщает о том, что было найдено, с помощью соответствующих методов.

{рисунок}

В этом и состоит его основное преимущество: в память помещаются только фрагменты, а не весь XML-документ. Но это и недостаток, так как информация не считывается вся сразу и невозможно получить иерархию XML-документа. КлассQXmlContentHandler должен использоваться для соединения с объектом классаQXmlSimpleReader. Другие классы, такие

как QXmlEntityResolver,QXmlDTDHandler, QXmlErrorHandler, QXmlDeclHandl er и QXmlLexicalHandler, просто содержат определения виртуальных методов, соответствующих различным событиям анализа XML-документа.

В большинстве случаев, для считывания XML-документа можно прекрасно обойтись двумя классами: QXmlContentHandier и QXmlErrorHandler. Интерфейс классаQContentHandler содержит методы, связанные с отслеживанием структуры документа, вызов которых происходит в следующем порядке:

1.метод startDocument() вызывается при начале чтения XML-документа;

2.метод startElement() вызывается при начале чтения элемента;

3.метод characters () вызывается при чтении данных элемента;

4.метод endElement() вызывается при завершении обработки элемента;

5.метод endDocument() вызывается при завершении обработки документа.

Для удобства существует класс QXmlDefaultHandler, который унаследован от всех шести классов. Он содержит пустые реализации виртуальных методов этих классов. Для чтения XML-документа нужно унаследовать его и перезаписать следующие

методы: startDocument(), startElement(), characters(), endElement(), endDocument()

иfatalError(). Определение последнего метода унаследовано от классаQXmlErrorHandler. Если эти методы возвращают значение true, то это говорит объекту класса QXmlSimpleReader о том, что он должен продолжить анализ файла. Значение false говорит об ошибке, а чтобы отобразить соответствующее сообщение о ней — необходимо перезаписать метод errorString().

Программа, приведенная в листингах, производит чтение XML-документа.

int main()

{

AddressBookParser handler;

QFile file("addressbook.xml");

QXmlInputSource source(&file);

QXmlSimpleReader reader;

reader.setContentHandler(&handler);

reader.parse(source);

return 0;

}

В основной программе создаются объекты классов AddressBookParser, QFile (для чтения файла) и QXmlSimpleReader (для анализа файла). Чтобы поместить XMLдокумент в SAX-анализатор, нужно создать объект класса QXmlInputSource, передав ему указатель на QIODevice. Созданный объект нужно передать в

метод parse()объекта класса QXmlSimpleReader. Этот метод запустит процесс анализа XML-документа. До его вызова необходимо установить в

методе setContenHandler() объект класса AddressBookparser, который производит отображение XML-документа.

class AddressBookParser : public QXmlDefaultHandler { private:

QString m_strText;

public:

bool startElement(const QString&, const QString&, const QString&,

const QXmlAttributes& attrs

)

{

for(int i = 0; i < attrs.count(); i++) { if(attrs.localName(i) == "number") {

qDebug() << "Attr:" << attrs.value(i);

}

}

return true;

}

bool characters(const QString& strText)

{

m_strText = strText; return true;

}

bool endElement(const QString&, const QString&, const QString& str)

{

if (str != "contact" && str != "addressbook") { qDebug() << "TagName:" << str

<< "\tText:" << m_strText;

}

return true;

}

bool fatalError(const QXmlParseException& exception)

{

qDebug() << "Line:"

<< exception.lineNumber()

 

 

<<", Column:" << exception.columnNumber()

<<", Message:" << exception.message(); return false;

}

};

Класс AddressBookParser реализует виртуальные методы, которые вызываются при прохождении по XML-документу. При нахождении тегов вызываются соответствующие методы startElement() и endElement(), которые должны быть перезаписаны для того, чтобы реагировать надлежащим образом. Метод startElement() вызывается тогда, когда при считывании встречается открытие тега. Третий параметр, передаваемый в этот метод, — это имя тега. Четвертый — это список атрибутов. В нашем случае, если название атрибута совпадает со

строкой number, то производится вывод его значения.

Метод characters() производит запись содержимого текущего элемента в переменную m_strText, а это необходимо для того, чтобы можно было получить доступ к этому содержимому из любого метода класса AddressBookParser. МетодendElement() вызывается всегда, когда при чтении встречается закрытие тега. Третий параметр, передаваемый в этот метод,— имя тега. В случае несовпадения его со строками contact и addressbook, производится отображение данных элемента. Метод fatalError() вызывается в том случае, если не удается проанализировать XML-документ. В этом случае метод производит отображение предупреждающего сообщения с указанием номера строки и столбца XMLдокумента, в котором произошел сбой и текст ошибки анализатора.

Qt предоставляет два способа работы с XML-документам и — DOM и SAX. Первый представляет данные XML-документа в виде иерархии (древовидной структуры), что очень удобно для работы. Второй способ считывает данные из XML-документа блоками и сообщает о результатах в определенные виртуальные методы.

При выборе следует учитывать, что SAX — это низкоуровневый способ и поэтому более быстрый. Его лучше всего применять в тех случаях, когда не требуется проведение очень сложных операций. Также следует применять его для