Парсер SAX2 вызывает метод endElement()
при выходе из каждого элемента. Его аргументы имеют тот же смысл, который имеют первые три аргумента метода startElement()
. В реализации endElement()
, приведенной в примере 14.6, я игнорирую все элементы, отличные от name
, species
и dateOfBirth
. Когда происходит обратный вызов, соответствующий одному из этих элементов, сигнализирующий о сделанном только что выходе парсера из элемента, я использую символьные данные, сохраненные в currentText_
для установки клички, вида и даты рождения текущего объекта Animal
.
Несколько важных особенностей SAX2 не проиллюстрировано в примерах 14.6, 14.7 и 14.8. Например, класс SAX2XMLReader
содержит перегрузку метода parse()
, которая принимает в качестве аргумента экземпляр xercesc::InputSource
вместо строки в С-стиле. InputSource
является абстрактным классом, инкапсулирующим источник символьных данных; конкретные его подклассы, в том числе xercesc::MemBufInputSource
и xercesc::URLInputSource
, позволяют парсеру SAX2 анализировать документ XML, который находится не в локальной файловой системе.
Более того, интерфейс ContentHandler
содержит много дополнительных методов, например startDocument()
и endDocument()
, которые сигнализируют о начале и конце документа XML, и setLocator()
, который позволяет задать объект Locator
, отслеживающий текущую позицию анализируемого файла. Существуют также другие интерфейсы обработчиков, включая DTDHandler
и EntityResolver
(соответствующие базовой спецификации SAX 2.0), а также DeclarationHandler
и LexicalHandler
(соответствующие стандартизованным расширениям SAX 2.0).
Кроме того, можно в одном классе реализовать несколько интерфейсов обработчиков. Это можно легко сделать в классе xercesc::DefaultHandler
, потому что он является производным от всех интерфейсов обработчиков и содержит реализации своих виртуальных функций, в которых не выполняется никаких действий. Следовательно, я мог бы добавить методы из CircusErrorHandler
в CircusContentHandler
и следующим образом модифицировать пример 14.8.
// Зарегистрировать обработчики
CircusContentHandler handler(animalList);
parser->setContentHandler(&handler);
parser->setErrorHandler(&handler);
Пример 14.8 имеет еще одну, последнюю особенность, которую вы должны были заметить: обработчик CircusContentHandler
не проверяет корректность структуры экземпляра анализируемого документа, т.е. не убеждается в том, что корневым является элемент animalList
или что все дочерние элементы корня являются элементами animal
. Это сильно отличается от примера 14.3. Например, функция main()
из примера 14.3 проверяет то, что элементом верхнего уровня является animalList
, а функция nodeToAnimal()
проверяет то, что ее аргументы представляют элемент animal
, содержащий точно пять дочерних элементов типа name
, species
, dateOfBirth
, veterinarian
и trainer
.
Пример 14.6 можно модифицировать, чтобы он выполнял подобного рода проверки. Например, обработчик ContentHandler
в примере 14.9 удостоверяется в том, что корневым элементом документа является animalList
и что его дочерние элементы имеют тип animal
, а дочерние элементы элемента animal
не содержат других элементов. Это можно сделать с помощью трех флагов типа boolean
, parsingAnimalList_
, parsingAnimal_
и parsingAnimalChild_
, которые регистрируют анализируемую в данный момент область документа. Методы startElement()
и endElement()
просто обновляют эти флаги и проверяют их согласованность, делегируя задачу обновления текущего объекта Animal вспомогательным методам startAnimalChild()
и endElementChild()
, реализация которых очень напоминает реализацию методов startElement()
и endElement()
из примера 14.6.
// Реализует функции обратного вызова, которые получают символьные данные и
// уведомляют о начале и конце элементов
class CircusContentHandler : public DefaultHandler {
public:
CircusContentHandler(vector