markup::scanner - XML и HTML токенайзер
Вот предлагается SGML токенайзер — markup::scanner — понимает контрукции и XML и HTML.
Для чего он может быть использован:
Преобразование HTML -> plain text, например для поиска вхождения какого-нибудь текста в HTML файле.
При использовании XML как persitent data format — быстрый парсер больших объемов данных.
Парсер HTML
и т.д.
а) очень быстрый (наверное самый быстрый из всех возможных) и б) вообще не аллоцирует память в процессе работы.
Предлагаемый класс не включает в себя распознавние кодировок — это нужно делать на уровне istream.
Также markup::scanner не проверяет ни наименование тэгов ни атрибутов на соответсвие nmtoken правил.
Основной метод markup::scanner'а — token_type get_token() который возвращает
следующие значения:
enum token_type
{
TT_ERROR = -1,
TT_EOF = 0,
TT_TAG_START, // <tag ...
// ^-- happens here
TT_TAG_END, // </tag>
// ^-- happens here
// <tag ... />
// ^-- or here
TT_ATTR, // <tag attr="value" >
// ^-- happens here
TT_WORD, // word
TT_SPACE // whitespace
};
Пример использования:
(исходный текст в следующих сообщениях)
#include "string.h"
#include "ml_scanner.h"
struct str_istream: public markup::istream
{
const char * p;
const char * end;
str_istream(const char * src): p(src), end(src + strlen(src)) {}
virtual wchar_t get_char() { return p < end? *p++: 0; }
};
int main(int argc, char * argv[])
{
str_istream si("<html one='two' three='four' five>hello world</html>" );
markup::scanner sc(si);
bool in_text = false ;
while (true )
{
int t = sc.get_token();
switch (t)
{
case markup::scanner::TT_ERROR:
printf("ERROR\n" );
break ;
case markup::scanner::TT_EOF:
printf("EOF\n" );
goto FINISH;
case markup::scanner::TT_TAG_START:
printf("TAG START:%s\n" , sc.get_tag_name());
break ;
case markup::scanner::TT_TAG_END:
printf("TAG END:%s\n" , sc.get_tag_name());
break ;
case markup::scanner::TT_ATTR:
printf("\tATTR:%s=%S\n" , sc.get_attr_name(), sc.get_value());
break ;
case markup::scanner::TT_WORD:
case markup::scanner::TT_SPACE:
printf("{%S}\n" , sc.get_value());
break ;
}
}
FINISH:
printf("--------------------------\n" );
return 0;
}
//|
//| simple XML/HTML scanner/tokenizer
//|
//| (C) Andrew Fedoniouk @ terrainformatica.com
//|
#include <wchar .h>
namespace markup
{
struct istream
{
virtual wchar_t get_char() = 0;
};
class scanner
{
public :
enum token_type
{
TT_ERROR = -1,
TT_EOF = 0,
TT_TAG_START, // <tag ...
// ^-- happens here
TT_TAG_END, // </tag>
// ^-- happens here
// <tag ... />
// ^-- or here
TT_ATTR, // <tag attr="value" >
// ^-- happens here
TT_WORD,
TT_SPACE
};
enum $ { MAX_TOKEN_SIZE = 1024, MAX_NAME_SIZE = 128 };
public :
scanner(istream& is):
input(is),
input_char(0),
where(TEXT),
value_length(0), tag_name_length(0), attr_name_length(0) {}
// get next token
token_type get_token();
// get value of WORD, SPACE or ATTRibute
const wchar_t * get_value();
// get attribute name
const char * get_attr_name();
// get tag name
const char * get_tag_name();
// should be overrided to resolve entities, e.g.
virtual wchar_t resolve_entity(const char * buf, int buf_size) { return 0; }
private : /* methods */
token_type scan_body();
token_type scan_head();
token_type scan_tag();
wchar_t skip_whitespace();
void push_back(wchar_t c);
wchar_t get_char();
wchar_t scan_entitty();
bool is_whitespace(wchar_t c);
void append_value(wchar_t c);
void append_attr_name(wchar_t c);
void append_tag_name(wchar_t c);
private : /* data */
enum state { TEXT = 0, MARKUP = 1 };
state where;
token_type token;
wchar_t value[MAX_TOKEN_SIZE];
int value_length;
char tag_name[MAX_NAME_SIZE];
int tag_name_length;
char attr_name[MAX_NAME_SIZE];
int attr_name_length;
istream& input;
wchar_t input_char;
};
}
//|
//| simple XML/HTML scanner/tokenizer
//|
//| (C) Andrew Fedoniouk @ terrainformatica.com
//|
#include "ml_scanner.h"
#include <string.h>
namespace markup
{
scanner::token_type scanner::get_token()
{
if (where == TEXT)
return (token = scan_body());
else if ( where == MARKUP)
return (token = scan_head());
return TT_ERROR;
}
const wchar_t * scanner::get_value()
{
value[value_length] = 0;
return value;
}
const char * scanner::get_attr_name()
{
attr_name[attr_name_length] = 0;
return attr_name;
}
const char * scanner::get_tag_name()
{
tag_name[tag_name_length] = 0;
return tag_name;
}
scanner::token_type scanner::scan_body()
{
wchar_t c = get_char();
value_length = 0;
bool ws = is_whitespace(c);
if (c == 0) return TT_EOF;
else if (c == '<') return scan_tag();
else if (c == '&')
c = scan_entitty();
while (true )
{
append_value(c);
c = input.get_char();
if (c == 0) { push_back(c); break ; }
if (c == '<') { push_back(c); break ; }
if (c == '&')
c = scan_entitty();
if (is_whitespace(c) != ws)
{
push_back(c);
break ;
}
}
return ws? TT_SPACE:TT_WORD;
}
scanner::token_type scanner::scan_head()
{
wchar_t c = skip_whitespace();
if (c == '>')
{
where = TEXT;
return scan_body();
}
if (c == '/' )
{
wchar_t t = get_char();
if (t == '>') { where = TEXT; return TT_TAG_END; }
else { push_back(t); return TT_ERROR; } // erroneous situtation - standalone '/'
}
attr_name_length = 0;
// attribute name...
while (c != '=' )
{
if ( c == 0) return TT_ERROR;
if ( c == '>' ) { push_back(c); return TT_ATTR; } // attribute without value (HTML style)
if ( is_whitespace(c) )
{
c = skip_whitespace();
if (c != '=' ) { push_back(c); return TT_ATTR; } // attribute without value (HTML style)
else break ;
}
if ( c == '<') return TT_ERROR;
append_attr_name(c);
c = get_char();
}
c = skip_whitespace();
// attribute value...
value_length = 0;
if (c == '\"' )
while (c = get_char())
{
if (c == '\"' ) return TT_ATTR;
if (c == '&') c = scan_entitty();
append_value(c);
}
else if (c == '\'' ) // allowed in html
while (c = get_char())
{
if (c == '\'' ) return TT_ATTR;
if (c == '&') c = scan_entitty();
append_value(c);
}
else // scan token, allowed in html: e.g. align=center
while (c = get_char())
{
if ( is_whitespace(c) ) return TT_ATTR;
if ( c == '/' || c == '>' ) { push_back(c); return TT_ATTR; }
if ( c == '&' ) c = scan_entitty();
append_value(c);
}
return TT_ERROR;
}
// caller already consumed '<'
// scan header start or tag tail
scanner::token_type scanner::scan_tag()
{
tag_name_length = 0;
wchar_t c = get_char();
bool is_tail = c == '/' ;
if (is_tail) c = get_char();
while (c)
{
if (is_whitespace(c)) { c = skip_whitespace(); break ; }
if (c == '/' || c == '>') break ;
append_tag_name(c);
c = get_char();
}
if (c == 0) return TT_ERROR;
if (is_tail)
{
if (c == '>') return TT_TAG_END;
return TT_ERROR;
}
else
push_back(c);
where = MARKUP;
return TT_TAG_START;
}
// skip whitespaces.
// returns first non-whitespace char
wchar_t scanner::skip_whitespace()
{
while (wchar_t c = get_char())
{
if (!is_whitespace(c)) return c;
}
return 0;
}
void scanner::push_back(wchar_t c) { input_char = c; }
wchar_t scanner::get_char()
{
if (input_char) { wchar_t t(input_char); input_char = 0; return t; }
return input.get_char();
}
// case insensitive string equality test
// s_lowcase shall be lowercase string
inline bool equal(const char * s, const char * s1, size_t length)
{
switch (length)
{
case 4: if (s1[3] != s[3]) return false ;
case 3: if (s1[2] != s[2]) return false ;
case 2: if (s1[1] != s[1]) return false ;
case 1: if (s1[0] != s[0]) return false ;
case 0: return true ;
default : return strncmp(s,s1,length) == 0;
}
}
// caller consumed '&'
wchar_t scanner::scan_entitty()
{
char buf[32];
int i = 0;
wchar_t t;
for (; i < 31 ; ++i )
{
buf[i] = char (t = get_char());
if (t == ';' )
break ;
// || t == '<' || t == '>' || t == '\"')
}
buf[i] = 0;
if (i == 2)
{
if (equal(buf,"gt" ,2)) return '>';
if (equal(buf,"lt" ,2)) return '<';
}
else if (i == 3 && equal(buf,"amp" ,3))
return '&';
else if (i == 4)
{
if (equal(buf,"apos" ,4)) return '\'' ;
if (equal(buf,"quot" ,4)) return '\"' ;
}
t = resolve_entity(buf,i);
if (t) return t;
// no luck ...
append_value('&');
for (int n = 0; n < i; ++n)
append_value(buf[n]);
return ';' ;
}
bool scanner::is_whitespace(wchar_t c)
{
return c <= ' '
&& (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' );
}
void scanner::append_value(wchar_t c)
{
if (value_length < (MAX_TOKEN_SIZE - 1))
value[value_length++] = c;
}
void scanner::append_attr_name(wchar_t c)
{
if (attr_name_length < (MAX_NAME_SIZE - 1))
attr_name[attr_name_length++] = char (c);
}
void scanner::append_tag_name(wchar_t c)
{
if (tag_name_length < (MAX_NAME_SIZE - 1))
tag_name[tag_name_length++] = char (c);
}
}
Re: markup::scanner - XML и HTML токенайзер
Здравствуйте, c-smile, Вы писали:
А всякие "специальные" куски? Начинаются с "<!"
<!-- ... -->
<![CDATA[ ... ]]>
Ы?
ЗЫ: Ты письма получаешь?
FAQ — це мiй ай-кью!
Re: markup::scanner - XML и HTML токенайзер
Здравствуйте, c-smile, Вы писали:
версия №2
Добавлена поддержка comment, cdata, pi и entity declaration.
ml_scanner.h:
//|
//| simple XML/HTML scanner/tokenizer
//|
//| (C) Andrew Fedoniouk @ terrainformatica.com
//|
namespace markup
{
struct instream
{
virtual wchar get_char() = 0;
};
typedef unsigned short wchar ;
class scanner
{
public :
enum token_type
{
TT_ERROR = -1,
TT_EOF = 0,
TT_TAG_START, // <tag ...
// ^-- happens here
TT_TAG_END, // </tag>
// ^-- happens here
// <tag ... />
// ^-- or here
TT_ATTR, // <tag attr="value" >
// ^-- happens here
TT_WORD,
TT_SPACE,
TT_DATA, // content of followings:
TT_COMMENT_START, TT_COMMENT_END, // after "<!--" and "-->"
TT_CDATA_START, TT_CDATA_END, // after "<![CDATA[" and "]]>"
TT_PI_START, TT_PI_END, // after "<?" and "?>"
TT_ENTITY_START, TT_ENTITY_END, // after "<!ENTITY" and ">"
};
enum $ { MAX_TOKEN_SIZE = 1024, MAX_NAME_SIZE = 128 };
public :
scanner(instream& is):
input(is),
input_char(0),
c_scan(scan_body),
value_length(0),
tag_name_length(0),
attr_name_length(0),
got_tail(false ) {}
// get next token
token_type get_token() { return (this ->*c_scan)(); }
// get value of TT_WORD, TT_SPACE, TT_ATTR and TT_DATA
const wchar * get_value();
// get attribute name
const char * get_attr_name();
// get tag name
const char * get_tag_name();
// should be overrided to resolve entities, e.g.
virtual wchar resolve_entity(const char * buf, int buf_size) { return 0; }
private : /* methods */
typedef token_type (scanner::*scan)();
scan c_scan; // current 'reader'
// content 'readers'
token_type scan_body();
token_type scan_head();
token_type scan_comment();
token_type scan_cdata();
token_type scan_pi();
token_type scan_tag();
token_type scan_entity_decl();
wchar skip_whitespace();
void push_back(wchar c);
wchar get_char();
wchar scan_entity();
bool is_whitespace(wchar c);
void append_value(wchar c);
void append_attr_name(wchar c);
void append_tag_name(wchar c);
private : /* data */
//enum state { TEXT = 0, MARKUP = 1, COMMENT = 2, CDATA = 3, PI = 4 };
//state where;
token_type token;
wchar value[MAX_TOKEN_SIZE];
int value_length;
char tag_name[MAX_NAME_SIZE];
int tag_name_length;
char attr_name[MAX_NAME_SIZE];
int attr_name_length;
instream& input;
wchar input_char;
bool got_tail; // aux flag used in scan_comment, etc.
};
}
ml_scanner.cpp :
namespace markup
{
// case insensitive string equality test
// s_lowcase shall be lowercase string
inline bool equal(const char * s, const char * s1, size_t length)
{
switch (length)
{
case 8: if (s1[7] != s[7]) return false ;
case 7: if (s1[6] != s[6]) return false ;
case 6: if (s1[5] != s[5]) return false ;
case 5: if (s1[4] != s[4]) return false ;
case 4: if (s1[3] != s[3]) return false ;
case 3: if (s1[2] != s[2]) return false ;
case 2: if (s1[1] != s[1]) return false ;
case 1: if (s1[0] != s[0]) return false ;
case 0: return true ;
default : return strncmp(s,s1,length) == 0;
}
}
const wchar * scanner::get_value()
{
value[value_length] = 0;
return value;
}
const char * scanner::get_attr_name()
{
attr_name[attr_name_length] = 0;
return attr_name;
}
const char * scanner::get_tag_name()
{
tag_name[tag_name_length] = 0;
return tag_name;
}
scanner::token_type scanner::scan_body()
{
wchar c = get_char();
value_length = 0;
bool ws = is_whitespace(c);
if (c == 0) return TT_EOF;
else if (c == '<') return scan_tag();
else if (c == '&')
c = scan_entity();
while (true )
{
append_value(c);
c = input.get_char();
if (c == 0) { push_back(c); break ; }
if (c == '<') { push_back(c); break ; }
if (c == '&')
c = scan_entity();
if (is_whitespace(c) != ws)
{
push_back(c);
break ;
}
}
return ws? TT_SPACE:TT_WORD;
}
scanner::token_type scanner::scan_head()
{
wchar c = skip_whitespace();
if (c == '>') { c_scan = scan_body; return scan_body(); }
if (c == '/' )
{
wchar t = get_char();
if (t == '>') { c_scan = scan_body; return TT_TAG_END; }
else { push_back(t); return TT_ERROR; } // erroneous situtation - standalone '/'
}
attr_name_length = 0;
value_length = 0;
// attribute name...
while (c != '=' )
{
if ( c == 0) return TT_ERROR;
if ( c == '>' ) { push_back(c); return TT_ATTR; } // attribute without value (HTML style)
if ( is_whitespace(c) )
{
c = skip_whitespace();
if (c != '=' ) { push_back(c); return TT_ATTR; } // attribute without value (HTML style)
else break ;
}
if ( c == '<') return TT_ERROR;
append_attr_name(c);
c = get_char();
}
c = skip_whitespace();
// attribute value...
if (c == '\"' )
while (c = get_char())
{
if (c == '\"' ) return TT_ATTR;
if (c == '&') c = scan_entity();
append_value(c);
}
else if (c == '\'' ) // allowed in html
while (c = get_char())
{
if (c == '\'' ) return TT_ATTR;
if (c == '&') c = scan_entity();
append_value(c);
}
else // scan token, allowed in html: e.g. align=center
while (c = get_char())
{
if ( is_whitespace(c) ) return TT_ATTR;
if ( c == '/' || c == '>' ) { push_back(c); return TT_ATTR; }
if ( c == '&' ) c = scan_entity();
append_value(c);
}
return TT_ERROR;
}
// caller already consumed '<'
// scan header start or tag tail
scanner::token_type scanner::scan_tag()
{
tag_name_length = 0;
wchar c = get_char();
bool is_tail = c == '/' ;
if (is_tail) c = get_char();
while (c)
{
if (is_whitespace(c)) { c = skip_whitespace(); break ; }
if (c == '/' || c == '>') break ;
append_tag_name(c);
switch (tag_name_length)
{
case 3:
if (equal(tag_name,"!--" ,3)) { c_scan = scan_comment; return TT_COMMENT_START; }
break ;
case 8:
if ( equal(tag_name,"![CDATA[" ,8) ) { c_scan = scan_cdata; return TT_CDATA_START; }
break ;
case 7:
if ( equal(tag_name,"!ENTITY" ,8) ) { c_scan = scan_entity_decl; return TT_ENTITY_START; }
break ;
}
c = get_char();
}
if (c == 0) return TT_ERROR;
if (is_tail)
{
if (c == '>') return TT_TAG_END;
return TT_ERROR;
}
else
push_back(c);
c_scan = scan_head;
return TT_TAG_START;
}
// skip whitespaces.
// returns first non-whitespace char
wchar scanner::skip_whitespace()
{
while (wchar c = get_char())
{
if (!is_whitespace(c)) return c;
}
return 0;
}
void scanner::push_back(wchar c) { input_char = c; }
wchar scanner::get_char()
{
if (input_char) { wchar t(input_char); input_char = 0; return t; }
return input.get_char();
}
// caller consumed '&'
wchar scanner::scan_entity()
{
char buf[32];
int i = 0;
wchar t;
for (; i < 31 ; ++i )
{
buf[i] = char (t = get_char());
if (t == ';' )
break ;
// || t == '<' || t == '>' || t == '\"')
}
buf[i] = 0;
if (i == 2)
{
if (equal(buf,"gt" ,2)) return '>';
if (equal(buf,"lt" ,2)) return '<';
}
else if (i == 3 && equal(buf,"amp" ,3))
return '&';
else if (i == 4)
{
if (equal(buf,"apos" ,4)) return '\'' ;
if (equal(buf,"quot" ,4)) return '\"' ;
}
t = resolve_entity(buf,i);
if (t) return t;
// no luck ...
append_value('&');
for (int n = 0; n < i; ++n)
append_value(buf[n]);
return ';' ;
}
bool scanner::is_whitespace(wchar c)
{
return c <= ' '
&& (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' );
}
void scanner::append_value(wchar c)
{
if (value_length < (MAX_TOKEN_SIZE - 1))
value[value_length++] = c;
}
void scanner::append_attr_name(wchar c)
{
if (attr_name_length < (MAX_NAME_SIZE - 1))
attr_name[attr_name_length++] = char (c);
}
void scanner::append_tag_name(wchar c)
{
if (tag_name_length < (MAX_NAME_SIZE - 1))
tag_name[tag_name_length++] = char (c);
}
scanner::token_type scanner::scan_comment()
{
if (got_tail)
{
c_scan = scan_body;
got_tail = false ;
return TT_COMMENT_END;
}
for (value_length = 0; value_length < (MAX_TOKEN_SIZE - 1); ++value_length)
{
value[value_length] = get_char();
if (value_length >= 2
&& value[value_length] == '>'
&& value[value_length - 1] == '-'
&& value[value_length - 2] == '-' )
{
got_tail = true ;
value_length -= 2;
break ;
}
}
return TT_DATA;
}
scanner::token_type scanner::scan_cdata()
{
if (got_tail)
{
c_scan = scan_body;
got_tail = false ;
return TT_CDATA_END;
}
for (value_length = 0; value_length < (MAX_TOKEN_SIZE - 1); ++value_length)
{
value[value_length] = get_char();
if (value_length >= 2
&& value[value_length] == '>'
&& value[value_length - 1] == ']'
&& value[value_length - 2] == ']' )
{
got_tail = true ;
value_length -= 2;
break ;
}
}
return TT_DATA;
}
scanner::token_type scanner::scan_pi()
{
if (got_tail)
{
c_scan = scan_body;
got_tail = false ;
return TT_PI_END;
}
for (value_length = 0; value_length < (MAX_TOKEN_SIZE - 1); ++value_length)
{
value[value_length] = get_char();
if (value_length >= 1
&& value[value_length] == '>'
&& value[value_length - 1] == '?' )
{
got_tail = true ;
value_length -= 1;
break ;
}
}
return TT_DATA;
}
scanner::token_type scanner::scan_entity_decl()
{
if (got_tail)
{
c_scan = scan_body;
got_tail = false ;
return TT_ENTITY_END;
}
wchar t;
dword tc = 0;
for (value_length = 0; value_length < (MAX_TOKEN_SIZE - 1); ++value_length)
{
value[value_length] = t = get_char();
if (t == '\"' ) tc++;
else if ( t == '>' && (tc & 1) == 0 )
{
got_tail = true ;
break ;
}
}
return TT_DATA;
}
}
Re[2]: markup::scanner - XML и HTML токенайзер
Здравствуйте, Зверёк Харьковский, Вы писали:
ЗХ>
ЗХ>А всякие "специальные" куски? Начинаются с "<!"
ЗХ><!-- ... -->
ЗХ><![CDATA[ ... ]]>
ЗХ>Ы?
Поправил уже. В стандарте еще много всякой экзотики типа %processing-entity; которые пока не распознаются.
ЗХ>ЗЫ: Ты письма получаешь?
Ага! А что? Ты посылал чего?
Если на старые email's то не дошло очевидно.
Прсьба повторить если можно.
Re[3]: markup::scanner - XML и HTML токенайзер
Здравствуйте, c-smile, Вы писали:
ЗХ>>ЗЫ: Ты письма получаешь?
CS>Ага! А что? Ты посылал чего?
CS>Если на старые email's то не дошло очевидно.
CS>Прсьба повторить если можно.
Ушли два письма с подтверждением доставки на Andrew@ и info@terrainformatica.com
Ни доставки, ни подтверждения
Кстати, Алекс Мова жаловался, что он тоже пытался писать и ничего не дошло.
Может, антиспам?
FAQ — це мiй ай-кью!
Re[3]: markup::scanner - XML и HTML токенайзер
Здравствуйте, c-smile, Вы писали:
CS>Ага! А что? Ты посылал чего?
Что ж такое делается?
Может еще кто посылал чего, а я не получил или не так понял?
(Извиняюсь за частные вопросы на публичном форуме)
Re[4]: markup::scanner - XML и HTML токенайзер
От:
Виталий
Дата: 20.09.04 21:57
Оценка:
Здравствуйте, c-smile, Вы писали:
СS>Что ж такое делается?
CS>Может еще кто посылал чего, а я не получил или не так понял?
Кстати говоря я тоже письма отсылал и без ответа. Правда потом в MSN все обсудили
Re[2]: markup::scanner - XML и HTML токенайзер
От:
Аноним
Дата: 07.05.06 03:24
Оценка:
Здравствуйте, c-smile, Вы писали:
CS>Здравствуйте, c-smile, Вы писали:
CS>версия №2
Вешь замечательная, только от такого
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<HTML>
<body>
<LI> Begin & back </LI>
</body>
</HTML>
крышу сносит.
При очередном парсинге возвращает(markup::scanner::TT_WORD) сначала Begin затем
0x0013d920 "& back </LI>
</body>
</HTML>
;"
Re[3]: markup::scanner - XML и HTML токенайзер
От:
Аноним
Дата: 07.05.06 03:33
Оценка:
Здравствуйте, Аноним, Вы писали:
Вот сам код, для удобства. А вешь действительно полезная
#include <stdio.h>
#include "string.h"
#include <tchar.h>
#include "ml_scanner.h"
struct str_istream: public markup::istream
{
const TCHAR* p;
const TCHAR* end;
str_istream(const TCHAR* src): p(src), end(src + _tcslen(src)) {}
virtual markup::wchar get_char() { return p < end? *p++: 0; }
};
int main(int argc, char * argv[])
{
TCHAR t = _T('а' );
str_istream si(_T("<HTML><body><LI> Begin & back </LI></body></HTML>" ));
markup::scanner sc(si);
bool in_text = false ;
while (true )
{
int t = sc.get_token();
switch (t)
{
case markup::scanner::TT_ERROR:
_tprintf(_T("ERROR\n" ));
break ;
case markup::scanner::TT_EOF:
_tprintf(_T("EOF\n" ));
goto FINISH;
case markup::scanner::TT_TAG_START:
_tprintf(_T("TAG START:%s\n" ), sc.get_tag_name());
break ;
case markup::scanner::TT_TAG_END:
_tprintf(_T("TAG END:%s\n" ), sc.get_tag_name());
break ;
case markup::scanner::TT_ATTR:
_tprintf(_T("\tATTR:%s=%s\n" ), sc.get_attr_name(), sc.get_value());
break ;
case markup::scanner::TT_WORD:
case markup::scanner::TT_SPACE:
_tprintf(_T("{%s}\n" ), sc.get_value());
break ;
}
}
FINISH:
_tprintf(_T("--------------------------\n" ));
return 0;
}
ну и в markup — typedef TCHAR wchar;
Re[4]: markup::scanner - XML и HTML токенайзер
От:
Аноним
Дата: 07.05.06 10:46
Оценка:
Здравствуйте, Аноним, Вы писали:
Баг из-за того, что получив & парсер, сохранив его в своем стеке, повторно принимает его за начало entity.
scanner::token_type scanner::scan_body()
{
wchar c = get_char(); // 2.теперь считав декодированый & принимаем его за новый entity и меленькая ж.па
value_length = 0;
bool ws = is_whitespace(c);
if (c == 0) return TT_EOF;
else if (c == '<') return scan_tag();
else if (c == '&')
c = scan_entity();
while (true )
{
append_value(c);
c = input.get_char();
if (c == 0) { push_back(c); break ; }
if (c == '<') { push_back(c); break ; }
if (c == '&')
c = scan_entity();
if (is_whitespace(c) != ws)
{
push_back(c); // 1.вот тут олучив из входного буфера, декодировав сохраняем в стеке
break ;
}
}
return ws? TT_SPACE:TT_WORD;
}
Править сей лаконичный код, очень "не хочется". Надеюсь c-smile молвит слово
.
Пока такое решение:
-добавляем переменную класса
bool entity_ch;
-правим get_char
wchar scanner::get_char()
{
if (input_char) { wchar t(input_char); if (input_char!=_T('&'))entity_ch = false ; input_char = 0; return t; }
return input.get_char();
}
-правим scan_entity
// caller consumed '&'
wchar scanner::scan_entity()
{
wchar buf[32];
int i = 0;
wchar t;
for (; i < 31 ; ++i )
{
buf[i] = char (t = get_char());
if (t == _T(';' ))
break ;
// || t == '<' || t == '>' || t == '\"')
}
buf[i] = 0;
if (i == 2)
{
if (equal(buf,_T("gt" ),2)) return _T('>');
if (equal(buf,_T("lt" ),2)) return _T('<');
}
else if (i == 3 && equal(buf,_T("amp" ),3))
{
entity_ch = true ;
return _T('&');
}
else if (i == 4)
{
if (equal(buf,_T("apos" ),4)) return _T('\'' );
if (equal(buf,_T("quot" ),4)) return _T('\"' );
}
t = resolve_entity(buf,i);
if (t) return t;
// no luck ...
append_value(_T('&'));
for (int n = 0; n < i; ++n)
append_value(buf[n]);
return ';' ;
}
-правим scan_body
scanner::token_type scanner::scan_body()
{
wchar c = get_char();
value_length = 0;
bool ws = is_whitespace(c);
if (c == 0) return TT_EOF;
else if (c == '<') return scan_tag();
else if (c == '&' && !entity_ch )
c = scan_entity();
while (true )
{
append_value(c);
c = input.get_char();
if (c == 0) { push_back(c); break ; }
if (c == '<') { push_back(c); break ; }
if (c == '&')
c = scan_entity();
if (is_whitespace(c) != ws)
{
push_back(c);
break ;
}
}
return ws? TT_SPACE:TT_WORD;
}
Re[5]: Latest fixed version:
Здравствуйте, Аноним, Вы писали:
.h
#ifndef __MARKUP
#define __MARKUP
//|
//| simple XML/HTML scanner/tokenizer
//|
//| (C) Andrew Fedoniouk @ terrainformatica.com
//|
namespace markup
{
typedef wchar_t wchar ;
struct instream
{
virtual wchar get_char() = 0;
};
class scanner
{
public :
enum token_type
{
TT_ERROR = -1,
TT_EOF = 0,
TT_TAG_START, // <tag ...
// ^-- happens here
TT_TAG_END, // </tag>
// ^-- happens here
// <tag ... />
// ^-- or here
TT_ATTR, // <tag attr="value" >
// ^-- happens here
TT_WORD,
TT_SPACE,
TT_DATA, // content of followings:
TT_COMMENT_START, TT_COMMENT_END, // after "<!--" and "-->"
TT_CDATA_START, TT_CDATA_END, // after "<![CDATA[" and "]]>"
TT_PI_START, TT_PI_END, // after "<?" and "?>"
TT_ENTITY_START, TT_ENTITY_END, // after "<!ENTITY" and ">"
};
enum $ { MAX_TOKEN_SIZE = 1024, MAX_NAME_SIZE = 128 };
public :
scanner(instream& is):
input(is),
input_char(0),
value_length(0),
tag_name_length(0),
attr_name_length(0),
got_tail(false ) { c_scan = &scanner::scan_body; }
// get next token
token_type get_token() { return (this ->*c_scan)(); }
// get value of TT_WORD, TT_SPACE, TT_ATTR and TT_DATA
const wchar * get_value();
// get attribute name
const char * get_attr_name();
// get tag name
const char * get_tag_name();
// should be overrided to resolve entities, e.g.
virtual wchar resolve_entity(const char * buf, int buf_size) { return 0; }
private : /* methods */
typedef token_type (scanner::*scan)();
scan c_scan; // current 'reader'
// content 'readers'
token_type scan_body();
token_type scan_head();
token_type scan_comment();
token_type scan_cdata();
token_type scan_pi();
token_type scan_tag();
token_type scan_entity_decl();
wchar skip_whitespace();
void push_back(wchar c);
wchar get_char();
wchar scan_entity();
bool is_whitespace(wchar c);
void append_value(wchar c);
void append_attr_name(wchar c);
void append_tag_name(wchar c);
private : /* data */
//enum state { TEXT = 0, MARKUP = 1, COMMENT = 2, CDATA = 3, PI = 4 };
//state where;
token_type token;
wchar value[MAX_TOKEN_SIZE];
int value_length;
char tag_name[MAX_NAME_SIZE];
int tag_name_length;
char attr_name[MAX_NAME_SIZE];
int attr_name_length;
instream& input;
wchar input_char;
bool got_tail; // aux flag used in scan_comment, etc.
};
}
#endif
.cpp
#include "tl_markup.h"
#include "string.h"
namespace markup
{
// case sensitive string equality test
// s_lowcase shall be lowercase string
inline bool equal(const char * s, const char * s1, size_t length)
{
switch (length)
{
case 8: if (s1[7] != s[7]) return false ;
case 7: if (s1[6] != s[6]) return false ;
case 6: if (s1[5] != s[5]) return false ;
case 5: if (s1[4] != s[4]) return false ;
case 4: if (s1[3] != s[3]) return false ;
case 3: if (s1[2] != s[2]) return false ;
case 2: if (s1[1] != s[1]) return false ;
case 1: if (s1[0] != s[0]) return false ;
case 0: return true ;
default : return strncmp(s,s1,length) == 0;
}
}
const wchar * scanner::get_value()
{
value[value_length] = 0;
return value;
}
const char * scanner::get_attr_name()
{
attr_name[attr_name_length] = 0;
return attr_name;
}
const char * scanner::get_tag_name()
{
tag_name[tag_name_length] = 0;
return tag_name;
}
scanner::token_type scanner::scan_body()
{
wchar c = get_char();
value_length = 0;
bool ws = is_whitespace(c);
if (c == 0) return TT_EOF;
else if (c == '<') return scan_tag();
else if (c == '&')
c = scan_entity();
while (true )
{
append_value(c);
c = input.get_char();
if (c == 0) { push_back(c); break ; }
if (c == '<') { push_back(c); break ; }
if (c == '&') { push_back(c); break ; }
if (is_whitespace(c) != ws)
{
push_back(c);
break ;
}
}
return ws? TT_SPACE:TT_WORD;
}
scanner::token_type scanner::scan_head()
{
wchar c = skip_whitespace();
if (c == '>') { c_scan = &scanner::scan_body; return scan_body(); }
if (c == '/' )
{
wchar t = get_char();
if (t == '>') { c_scan = &scanner::scan_body; return TT_TAG_END; }
else { push_back(t); return TT_ERROR; } // erroneous situtation - standalone '/'
}
attr_name_length = 0;
value_length = 0;
// attribute name...
while (c != '=' )
{
if ( c == 0) return TT_ERROR;
if ( c == '>' ) { push_back(c); return TT_ATTR; } // attribute without value (HTML style)
if ( is_whitespace(c) )
{
c = skip_whitespace();
if (c != '=' ) { push_back(c); return TT_ATTR; } // attribute without value (HTML style)
else break ;
}
if ( c == '<') return TT_ERROR;
append_attr_name(c);
c = get_char();
}
c = skip_whitespace();
// attribute value...
if (c == '\"' )
while (c = get_char())
{
if (c == '\"' ) return TT_ATTR;
if (c == '&') c = scan_entity();
append_value(c);
}
else if (c == '\'' ) // allowed in html
while (c = get_char())
{
if (c == '\'' ) return TT_ATTR;
if (c == '&') c = scan_entity();
append_value(c);
}
else // scan token, allowed in html: e.g. align=center
while (c = get_char())
{
if ( is_whitespace(c) ) return TT_ATTR;
if ( c == '/' || c == '>' ) { push_back(c); return TT_ATTR; }
if ( c == '&' ) c = scan_entity();
append_value(c);
}
return TT_ERROR;
}
// caller already consumed '<'
// scan header start or tag tail
scanner::token_type scanner::scan_tag()
{
tag_name_length = 0;
wchar c = get_char();
bool is_tail = c == '/' ;
if (is_tail) c = get_char();
while (c)
{
if (is_whitespace(c)) { c = skip_whitespace(); break ; }
if (c == '/' || c == '>') break ;
append_tag_name(c);
switch (tag_name_length)
{
case 3:
if (equal(tag_name,"!--" ,3)) { c_scan = &scanner::scan_comment; return TT_COMMENT_START; }
break ;
case 8:
if ( equal(tag_name,"![CDATA[" ,8) ) { c_scan = &scanner::scan_cdata; return TT_CDATA_START; }
break ;
case 7:
if ( equal(tag_name,"!ENTITY" ,8) ) { c_scan = &scanner::scan_entity_decl; return TT_ENTITY_START; }
break ;
}
c = get_char();
}
if (c == 0) return TT_ERROR;
if (is_tail)
{
if (c == '>') return TT_TAG_END;
return TT_ERROR;
}
else
push_back(c);
c_scan = &scanner::scan_head;
return TT_TAG_START;
}
// skip whitespaces.
// returns first non-whitespace char
wchar scanner::skip_whitespace()
{
while (wchar c = get_char())
{
if (!is_whitespace(c)) return c;
}
return 0;
}
void scanner::push_back(wchar c) { input_char = c; }
wchar scanner::get_char()
{
if (input_char) { wchar t(input_char); input_char = 0; return t; }
return input.get_char();
}
// caller consumed '&'
wchar scanner::scan_entity()
{
char buf[32];
int i = 0;
wchar t;
for (; i < 31 ; ++i )
{
buf[i] = char (t = get_char());
if (t == ';' )
break ;
// || t == '<' || t == '>' || t == '\"')
}
buf[i] = 0;
if (i == 2)
{
if (equal(buf,"gt" ,2)) return '>';
if (equal(buf,"lt" ,2)) return '<';
}
else if (i == 3 && equal(buf,"amp" ,3))
return '&';
else if (i == 4)
{
if (equal(buf,"apos" ,4)) return '\'' ;
if (equal(buf,"quot" ,4)) return '\"' ;
}
t = resolve_entity(buf,i);
if (t) return t;
// no luck ...
append_value('&');
for (int n = 0; n < i; ++n)
append_value(buf[n]);
return ';' ;
}
bool scanner::is_whitespace(wchar c)
{
return c <= ' '
&& (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' );
}
void scanner::append_value(wchar c)
{
if (value_length < (MAX_TOKEN_SIZE - 1))
value[value_length++] = c;
}
void scanner::append_attr_name(wchar c)
{
if (attr_name_length < (MAX_NAME_SIZE - 1))
attr_name[attr_name_length++] = char (c);
}
void scanner::append_tag_name(wchar c)
{
if (tag_name_length < (MAX_NAME_SIZE - 1))
tag_name[tag_name_length++] = char (c);
}
scanner::token_type scanner::scan_comment()
{
if (got_tail)
{
c_scan = &scanner::scan_body;
got_tail = false ;
return TT_COMMENT_END;
}
for (value_length = 0; value_length < (MAX_TOKEN_SIZE - 1); ++value_length)
{
value[value_length] = get_char();
if (value_length >= 2
&& value[value_length] == '>'
&& value[value_length - 1] == '-'
&& value[value_length - 2] == '-' )
{
got_tail = true ;
value_length -= 2;
break ;
}
}
return TT_DATA;
}
scanner::token_type scanner::scan_cdata()
{
if (got_tail)
{
c_scan = &scanner::scan_body;
got_tail = false ;
return TT_CDATA_END;
}
for (value_length = 0; value_length < (MAX_TOKEN_SIZE - 1); ++value_length)
{
value[value_length] = get_char();
if (value_length >= 2
&& value[value_length] == '>'
&& value[value_length - 1] == ']'
&& value[value_length - 2] == ']' )
{
got_tail = true ;
value_length -= 2;
break ;
}
}
return TT_DATA;
}
scanner::token_type scanner::scan_pi()
{
if (got_tail)
{
c_scan = &scanner::scan_body;
got_tail = false ;
return TT_PI_END;
}
for (value_length = 0; value_length < (MAX_TOKEN_SIZE - 1); ++value_length)
{
value[value_length] = get_char();
if (value_length >= 1
&& value[value_length] == '>'
&& value[value_length - 1] == '?' )
{
got_tail = true ;
value_length -= 1;
break ;
}
}
return TT_DATA;
}
scanner::token_type scanner::scan_entity_decl()
{
if (got_tail)
{
c_scan = &scanner::scan_body;
got_tail = false ;
return TT_ENTITY_END;
}
wchar t;
unsigned int tc = 0;
for (value_length = 0; value_length < (MAX_TOKEN_SIZE - 1); ++value_length)
{
value[value_length] = t = get_char();
if (t == '\"' ) tc++;
else if ( t == '>' && (tc & 1) == 0 )
{
got_tail = true ;
break ;
}
}
return TT_DATA;
}
}
Re: markup::scanner - XML и HTML токенайзер
Re[6]: Latest fixed version:
От:
Аноним
Дата: 11.05.06 23:46
Оценка:
Здравствуйте, c-smile, Вы писали:
CS>Здравствуйте, Аноним, Вы писали:
Как всё просто оказалось
Re[2]: markup::scanner - XML и HTML токенайзер
От:
Аноним
Дата: 11.05.06 23:48
Оценка:
Здравствуйте, c-smile, Вы писали:
CS>Последняя версия:
CS>http://www.terrainformatica.com/codelib/view.php?sid=8
А ето, там ченжлога нет
?
И чем отличается от
этойАвтор: c-smile Дата: 10.05.06
?
Re[3]: markup::scanner - XML и HTML токенайзер
Здравствуйте, Аноним, Вы писали:
А>Здравствуйте, c-smile, Вы писали:
CS>>Последняя версия:
CS>>http://www.terrainformatica.com/codelib/view.php?sid=8
А>А ето, там ченжлога нет ?
Потому что codelib это "даренный конь" — он же GNU в прстонародье.
А>И чем отличается от этойАвтор: c-smile Дата: 10.05.06
?
scanner::token_type scanner::scan_body()
{
wchar c = get_char();
value_length = 0;
bool ws = false ;
if (c == 0) return TT_EOF;
else if (c == '<') return scan_tag();
else if (c == '&')
c = scan_entity();
else
ws = is_whitespace(c);
while (true )
{
append_value(c);
c = input.get_char();
if (c == 0) { push_back(c); break ; }
if (c == '<') { push_back(c); break ; }
if (c == '&') { push_back(c); break ; }
if (is_whitespace(c) != ws)
{
push_back(c);
break ;
}
}
return ws? TT_SPACE:TT_WORD;
}
я выделил места которые были изменены.
Re[2]: markup::scanner - XML и HTML токенайзер
От:
Аноним
Дата: 17.07.06 15:30
Оценка:
Здравствуйте, c-smile, Вы писали:
CS>Последняя версия:
CS>http://www.terrainformatica.com/codelib/view.php?sid=8
Ссылка не работает.
Можно поправить?
Re[3]: markup::scanner - XML и HTML токенайзер
Здравствуйте, Аноним, Вы писали:
А>Здравствуйте, c-smile, Вы писали:
CS>>Последняя версия:
CS>>http://www.terrainformatica.com/codelib/view.php?sid=8
А>Ссылка не работает. Можно поправить?
Посмотри здесь:
http://www.codeproject.com/cpp/HTML_XML_Scanner.asp
Re[4]: markup::scanner - XML и HTML токенайзер
От:
Alny
Дата: 19.07.06 08:45
Оценка:
CS>>>Последняя версия:
CS>>>http://www.terrainformatica.com/codelib/view.php?sid=8
А>>Ссылка не работает. Можно поправить?
WM>Посмотри здесь: http://www.codeproject.com/cpp/HTML_XML_Scanner.asp
а по
http://www.terrainformatica.com/codelib/view.php?sid=8 по прежнему
Scriptorium could not connect to the database. The reason was: DB Error: no such database
Локальные проблемы?
Просто по адресу
http://www.codeproject.com/cpp/HTML_XML_Scanner.asp насколько я понимаю, далеко не последняя версия. И судя по коментариям, последние изменения именно на оф. сайте?
Пока на собственное сообщение не было ответов, его можно удалить.
Удалить