// на основе заданного набора атрибутов
Contact contactFromAttributes(const Attributes &attrs) {
// Для повышения эффективности хранить часто используемые строки
// в статических переменных
static XercesString name = fromNative("name");
static XercesString phone = fromNative("phone");
Contact result; // Возвращаемый объект Contact.
const XMLCh* val; // Значение атрибута name или phone.
// Установить имя объекта Contact.
if ((val = attrs.getValue(name.c_str())) != 0) {
result.setName(toNative(val));
} else {
throw runtime_error("contact missing name attribute");
}
// Установить номер телефона для объекта Contact.
if ((val = attrs.getValue(phone.c_str())) != 0) {
result.setPhone(toNative(val));
} else {
throw runtime_error("contact missing phone attribute");
}
return result;
}
// Реализует обратные вызовы, которые получают символьные данные и
// уведомления о начале и конце элементов
class CircusContentHandler : public DefaultHandler {
public:
CircusContentHandler(vector<Animal>& animalList) :
animalList_(animalList) {}
// Если текущий элемент представляет ветеринара или дрессировщика
// используйте attrs для конструирования объекта Contact для текущего
// Animal; в противном случае очистите currentText_, подготавливая
// обратный вызов characters()
void startElement(
const XMLCh *const uri, // URI пространства имен
const XMLCh *const localname, // имя тега без префикса NS
const XMLCh *const qname, // имя тега + префикс NS
const Attributes &attrs) // атрибуты элементов
{
static XercesString animalList = fromNative("animalList");
static XercesString animal = fromNative("animal");
static XercesString vet = fromNative("veterinarian");
static XercesString trainer = fromNative("trainer");
static XercesString xmlns =
fromNative("http://www.feldman-family-circus.com");
// проверить URI пространства имен
if (uri != xmlns)
throw runtime_error(
string("wrong namespace uri ") + toNative(uri)
);
if (localname == animal) {
// Добавить в список объект Animal; это будет
// "текущий объект Animal"
animalList_.push_back(Animal());
} else if (localname != animalList) {
Animal& animal = animalList_.back();
if (localname == vet) {
// Мы встретили элемент "ветеринар".
animal.setVeterinarian(contactFromAttributes(attrs));
} else if (localname == trainer) {
// Мы встретили элемент "дрессировщик".
animal.setTrainer(contactFromAttributes(attrs));
} else {
// Мы встретили элемент "кличка", "вид животного" или
// "дата рождения". Их содержимое будет получено
// при обратном вызове функции characters().
currentText_.clear();
}
}
}
// Если текущий элемент представляет кличку, вид животного или дату
// рождения, используйте хранимый в currentText_ текст для установки
// соответствующего свойства текущего объекта Animal.
void endElement(
const XMLCh *const uri, // URI пространства имен
const XMLCh *const localname, // имя тега без префикса NS
const XMLCh *const qname) // имя тега + префикс NS
{
static XercesString animalList = fromNative("animal-list");
static XercesString animal = fromNative("animal");
static XercesString name = fromNative("name");
static XercesString species = fromNative("species");
static XercesString dob = fromNative("dateOfBirth");
if (localname!= animal && localname!= animalList) {
// currentText_ содержит текст элемента, который был
// добавлен. Используйте его для установки свойств текущего
// объекта Animal.
Animal& animal = animalList_.back();
if (localname == name) {
animal setName(toNative(currentText_));
} else if (localname == species) {
animal.setSpecies(toNative(currentText_));
} else if (localname == dob) {
animal.setDateOfBirth(toNative(currentText_));
}
}
}
// Получает уведомления, когда встречаются символьные данные
void characters(const XMLCh* const chars,
const unsigned int length) {
// Добавить символы в конец currentText_ для обработки методом
// endElement()
currentText_.append(chars, length);
}
private:
vector<Animal>& animalList_;
XercesString currentText_;
};
Пример 14.7. SAX2 ErrorHandler
#include <stdexcept> // runtime_error
#include <xercesc/sax2/DefaultHandler.hpp>
// Получает уведомления об ошибках.
class CircusErrorHandler : public DefaultHandler {
public:
void warning(const SAXParseException& e) {
/* нет действий */
}
void error(const SAXParseExceptionf& e) {
throw runtime_error(toNative(e.getMessage()));
}
void fatalError(const SAXParseException& e) { error(e); }
};
Пример 14.8. Синтаксический анализ документа animals.xml при помощи программного интерфейса SAX2
#include <exception>
#include <iostream> // cout
#include <memory> // auto_ptr
#include <vector>
#include <xercesc/sax2/SAX2XMLReader.hpp>
#include <xercesc/sax2/XMLReaderFactory.hpp>
#include <xercesc/util/PlatformUtils.hpp>
#include "animal.hpp"
#include "xerces_strings.hpp" // Пример 14.4
using namespace std;
using namespace xercesc;
// Утилита RAII инициализирует парсер и освобождает ресурсы
// при выходе из области видимости
class XercesInitializer {
public:
XercesInitializer() { XMLPlatformUtils::Initialize(); }
~XercesInitializer() { XMLPlatformUtils::Terminate(); }
private:
// Запретить копирование и присваивание
XercesInitializer(const XercesInitializer&);
XercesInitializer& operator=(const XercesInitializer&);
};
int main() {
try {
vector<Animal> animalList;
// Инициализировать Xerces и получить парсер
XercesInitializer init;
auto_ptr<SAX2XMLReader>
parser(XMLReaderFactory::createXMLReader());
// Зарегистрировать обработчики
CircusContentHandler content(animalList);
CircusErrorHandler error;
parser->setContentHandler(&content);
parser->setErrorHandler(&error);
// Выполнить синтаксический анализ документа XML
parser->parse("animals.xml");
// Напечатать клички животных
for (vector<Animal>::size_type i = 0;
n = animalList.size(); i < n; ++i) {
cout << animalList[i] << "n";
}
} catch (const SAXException& e) {
cout << "xml error: " << toNative(e.getMessage()) << "n";
return EXIT_FAILURE;
} catch (const XMLException& e) {
cout << "xml error: " << toNative(e.getMessage()) << "n";
return EXIT_FAILURE;
} catch (const exception& e) {
cout << e.what() << "n";
return EXIT_FAILURE;
}
}
Обсуждение
Некоторые парсеры XML выполняют синтаксический анализ документа XML и возвращают его пользователю в виде сложного объекта С++. Именно это делает парсер TinyXml и парсер W3C DOM, который будет рассмотрен в следующем рецепте. В отличие от них парсер SAX2 использует ряд функций обратного вызова для передачи пользователю информации о документе XML по ходу его анализа. Функции обратного вызова сгруппированы в несколько интерфейсов обработчиков: ContentHandler получает уведомления об элементах, атрибутах и о тексте документа XML, ErrorHandler получает предупреждения и сообщения об ошибках, a DTDHandler получает уведомления о DTD документа XML.
Проектирование парсера, использующего функции обратного вызова, имеет несколько важных преимуществ. Например, можно выполнять синтаксический анализ очень больших документов, которые не помещаются в памяти. Кроме того, это может сэкономить процессорное время, потому что не надо выполнять многочисленные операции динамического выделения памяти, необходимые для конструирования узлов внутреннего представления документа XML, и потому что пользователь может создавать свое представление данных документа непосредственно, а не во время прохождения дерева документа, как я это делал в примере 14.3.
Пример 14.8 достаточно простой: я получаю парсер SAX2, регистрирую ContentHandler и ErrorHandler, анализирую документ animals.xml и печатаю список объектов Animal, заполненный обработчиком ContentHandler. Следует отметить два интересных момента: во-первых, функция XMLReaderFactory::createXMLReader() возвращает экземпляр SAX2XMLReader, память под который выделяется динамически и должна освобождаться пользователем в явной форме; для этой цели я использую std::auto_ptr, чтобы обеспечить удаление парсера даже в случае возникновения исключения. Во-вторых, среда Xerces должна быть инициализирована, используя xercesc::XMLPlatformUtils::Initialize(), и очищена при помощи xercesc::XMLPlatformUtils::Terminate(). Я инкапсулирую эту инициализацию и очистку в классе XercesInitializer, который вызывает XMLPlatformUtils::Initialize() в своем конструкторе и XMLPlatformUtils::Terminate() в своем деструкторе. Это гарантирует вызов Terminate(), даже если выбрасывается исключение. Это пример метода захвата ресурса при инициализации (Resource Acquisition Is Initialization — RAII), который был продемонстрирован в примере 8.3.