Проектирование парсера, использующего функции обратного вызова, имеет несколько важных преимуществ. Например, можно выполнять синтаксический анализ очень больших документов, которые не помещаются в памяти. Кроме того, это может сэкономить процессорное время, потому что не надо выполнять многочисленные операции динамического выделения памяти, необходимые для конструирования узлов внутреннего представления документа 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()
, даже если выбрасывается исключение. Это пример метода
Давайте теперь посмотрим, как класс CircusContentHandler
из примера 14.6 реализует интерфейс SAX2 ContentHandler
. Парсер SAX 2 вызывает метод startElement()
при каждой встрече открывающего тега элемента. Если элементу приписано пространство имен, первый аргумент, uri
, будет содержать URI пространства имен элемента, а второй аргумент, localname
, будет содержать ту часть имени тега элемента, которая идет за префиксом пространства имен. Если элемент не имеет пространства имен, эти два аргумента будут иметь пустые строки. Третий аргумент содержит имя тега элемента, если с элементом не связывается пространство имен; в противном случае этот аргумент может содержать либо имя тега элемента в том виде, в каком оно встречается в анализируемом документе, либо пустую строку. Четвертым аргументом является экземпляр класса Attributes
, представляющего набор атрибутов элемента.
В приведенной в примере 14.6 реализации startElement()
я игнорирую элемент animalList
. Когда я встречаю элемент animal
, я добавляю новый объект Animal
в список животных; назовем его Animal
и предоставим право установки свойств этого Animal
обработчикам других элементов. Когда я встречаю элемент veterinarian
или trainer
, я вызываю функцию contactFromAttributes
для конструирования экземпляра Contact
из набора атрибутов элемента и затем использую этот объект Contact
для установки свойств ветеринара и дрессировщика в текущем элементе Animal
. Когда я встречаю элемент name, species
или dateOfBirth
, я очищаю переменную-член currentText_
, которая будет использоваться для хранения текстового содержимого этого элемента.
Парсер SAX2 вызывает метод characters()
для передачи символьных данных, содержащихся в элементе. Этот парсер может передавать символы элемента с помощью нескольких вызовов метода characters()
; пока не встретится закрывающий тег, нельзя быть уверенным в передаче всех символьных данных. Поэтому в реализации characters()
я просто добавляю полученные символы в конец переменной-члена currentText_
, которую я использую для установки клички, вида и даты рождения Animal
сразу после встречи закрывающего тега для элемента name
, species
или dateOfBirth
.