SAX-велосипед: (дисковые тормоза, рама -- карбон...)
По мотивам
сканера c-smile'аАвтор: c-smile
Дата: 10.09.04
.
Строго говоря, "SAX-парсер" это весьма громкое название для этой библиотеки, но она позволяет решить, наверное, все задачи которые решает настоящий SAX-парсер, а всё остальное может выполнить вызывающая сторона.
Это не валидирующий парсер, т.е. правильный документ он поймёт правильно, неправильный он как-то поймёт. Проверка на корректность имён не производится. Единственным строгим требованием к обработке некорректного документа было: "не подвесить на нём программу".
Основные отличия от парсера c-smile'а:
Поддержка многосимвольных сущностей, в том числе содержащих разметку (с проверкой на рекурсию).
Сам парсер поддерживает числовые сущности в десятичном и шестнадцатеричном формате (} .
Попытка отлавливать некоторые, наиболее распостранённые ошибки и механизм их разрешения (т.е., решение парсить дальше или остановить работу остаётся за вызывающей стороной).
Нулевой символ в потоке допустим.
Имёна элементов и атрибутов хранятся в юникоде.
Работа постороена на callback методах. Не то чтобы это лучше или хуже чем метод который использовал Андрей, но писать парсер таким образом оказалось удобнее, по крайней мере мне.
Парсер не разбивает текстовый узел на фрагменты из "слов" и "пробелов", учитывая, что в общем виде задача довольно не простая*, я оставил её на совесть вызывающей стороны. *(В некоторых языках слова не разделяются пробелами).
Объём увеличился в 3 -- 4 раза, т.е. составляет около 40K. Отчасти это вызвано новыми возможностями, отчасти моей привычкой форматировать код и оставлять подробные комментарии.
Фича: может воспринимать содержание PI как набор атрибутов. Это не по стандарту, но часто востребовано. Решение как воспринимать содержание PI принимает вызывающая сторона.
Известное ограничение: не парсится !DOCTYPE и вложенные в него секции, такие как !ENTITY, !ELEMENT и т.д. В настоящий момент, на ветках, которые должны обрабатывать эти потоки стоят заглушки. Мне пока не нужны, а их поддержка раздует код ещё больше.
Пример использования:
#include "../xml/sax/sax.h"
namespace xml
{
namespace sax
{
// Поток ввода
class sax_stream: public entity_stream
{
size_t length( const xml::char_t* p )
{
for( size_t i = 0; ; i++ ) if( 0 == p[i] ) return i;
return -1;
}
const xml::char_t* m_p;
const xml::char_t* m_end;
public:
sax_stream( const xml::char_t* src ): m_p(src), m_end(src + length(src)) {}
virtual xml::char_t pop_char() { return *m_p++: }
virtual bool has_data() const { return m_p < m_end; }
virtual entity_type type() const { return ET_XML; }
virtual int id() const { return 0; }
};
class sax_callback : public callback
{
public:
sax_callback(){}
virtual void document_start() { wprintf(L"\nDOC START ------\n\n"); }
virtual void document_end() { wprintf(L"\nDOC END ------"); }
virtual void element_start( const scanner& scan ) { wprintf(L"\nEL START: <%s", scan.el_name().p() ); }
virtual void element_end( const scanner& scan ) { wprintf(L"\nEL END: </%s", scan.el_name().p() ); }
virtual void pi_start( const scanner& scan ) { wprintf( L"\n<?%s:", scan.el_name().p() ); }
virtual void pi_end( const scanner& scan ) { wprintf( L"?>" ); }
// ...
virtual void character( char_t c ) { wprintf(L"%c",c); }
virtual bool resolve_entity( const str_t& name, str_t& result, entity_type& type, int& id )
{
if( name.length() == 4 )
{
if( str_eq( name, L"wow1", 4 ) )
{
result.set( L"-&wow2;-", 8 );
type = ET_XML;
id = 1;
return true;
}
if( str_eq( name, L"wow2", 4 ) )
{
result.set( L"<win>", 5 );
type = ET_TXT; // Будет вставлено как текст
id = 0; // text stream id
return true;
}
}
return false;
}
};
}
}
int _tmain(int argc, _TCHAR* argv[])
{
xml::sax::sax_stream my_stream( L"<?xml version='1.0' encoding='utf-8'?>"
L"W&amp &wow1; &wow2; <preved>Medved</preved>" );
xml::sax::scanner scan( xml::sax::sax_callback() );
scan.parse( my_stream );
return 0;
}
Исходники и демонстрационный проект:
http://yarrr.ru/dl/xml_sax.zip (29K)