Чтение онлайн

ЖАНРЫ

C++. Сборник рецептов

Когсуэлл Джефф

Шрифт:

Давайте теперь посмотрим, как класс

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
.

Парсер 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.

Пример 14.9. Обработчик SAX2 ContentHandler документа animals.xml, который проверяет структуру документа

// Реализует функции обратного вызова, которые получают символьные данные и

// уведомляют о начале и конце элементов

class CircusContentHandler : public DefaultHandler {

public:

 CircusContentHandler(vector<Animal>& animalList)

: animalList_(animalList), // заполняемый список

parsingAnimalList_(false), // состояние анализа

parsingAnimal_(false), // состояние анализа

parsingAnimalChild_(false) // состояние анализа

{}

 // Получает уведомления от парсера при каждой встрече начала

 // какого-нибудь элемента

 void startElement(

const XMLCh *const uri, // uri пространства имен

const XMLCh *const localname, // простое имя тега

Поделиться с друзьями: