Выбрать главу

   } 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.

Давайте теперь посмотрим, как класс CircusContentHandler из примера 14.6 реализует интерфейс SAX2 ContentHandler. Парсер SAX 2 вызывает метод startElement() при каждой встрече открывающего тега элемента. Если элементу приписано пространство имен, первый аргумент, uri, будет содержать URI пространства имен элемента, а второй аргумент, localname, будет содержать ту часть имени тега элемента, которая идет за префиксом пространства имен. Если элемент не имеет пространства имен, эти два аргумента будут иметь пустые строки. Третий аргумент содержит имя тега элемента, если с элементом не связывается пространство имен; в противном случае этот аргумент может содержать либо имя тега элемента в том виде, в каком оно встречается в анализируемом документе, либо пустую строку. Четвертым аргументом является экземпляр класса Attributes, представляющего набор атрибутов элемента.