// ****************************************************** // Загружаем узел вместе со всеми его вложенными узлами. // ****************************************************** Node* Parser::Load_Node( WideStream *reader ) const { if( reader->eof() ) return NULL; // Пропускаем пробелы. wchar_t c=0; while( !reader->eof() ) { c = reader->wget(); if( c==WEOF ) return NULL; if( c!=L' ' && c!=L'\t' && c!=L'\r' && c!=L'\n' ) break; } // Символ должен быть '<'. if( reader->eof() ) return NULL; if( c!=L'<' ) throw Invalid_Format(); // Считываем тэг. UFString tag; Read_Tag( reader, tag ); if( tag.empty() ) throw Invalid_Format(); Node *node = new Node; Break_Tag( tag, node->name, node->attrs, node->body ); // Тэги бывают пяти видов - единичный <abc/>, открывающий <abc>, // закрывающий </abc>, комментарий <!--...-->, блок <![CDATA[ ... ]> // Для HTML требуется также специальная обработка тэгов <script>...</script> // и одиночных типа <br> if( tag.back() == L'/' ) { // Единичный тэг. node->SetClosed(true); } else if( node->name==L"![CDATA[" ) { node->node_type = Node::CDataNode; node->SetClosed(true); } // Для HTML надо проверить одиночные теги типа <br> else if( doctype==HtmlDoc && IsHtmlClosed(tag) ) { // Единичный тэг. node->SetClosed(true); } else if( tag.front()==L'/' ) { // Закрывающий тэг. // В имени убираем символ '/' node->name = remove_char( node->name, L'/' ); node->SetClosing(true); } else if( tag.front()==L'!' ) { if( tag.length()>=3 && tag.eq_beg( L"!--" ) ) { // Комментарий <!-- ... --> node->SetClosed(true); node->node_type = Node::CommentNode; } /* else if( tag.eq_beg( L"CDATA[") ) { node->SetClosed(true); node->node_type = Node::CDataNode; } */ else { if( doctype==Parser::XmlDoc ) { delete node; throw Invalid_Format(); } else { node->SetClosed(true); node->node_type = Node::InvalidNode; } } } else if( doctype==Parser::HtmlDoc && node->name.eqi(L"script") ) { // Для пары тегов <script>...</script> правила особые - тело является // произвольным набором символов, читаемых как единое целое в тело узла. node->node_type = Node::ScriptNode; node->body.reserve(256); while( !reader->eof() ) { wchar_t c = reader->wget(); if( c==WEOF || reader->eof() ) break; if( c==L'<' ) { // вдруг это начало закрывающего тэга </script> lem::UFString tbuf; tbuf.reserve(32); tbuf = c; while( !reader->eof() ) { wchar_t c2 = reader->wget(); if( c2==WEOF || reader->eof() ) break; tbuf.Add_Dirty(c2); if( c2==L'>' ) break; } if( tbuf.eqi(L"</script>") ) { node->SetClosed(true); break; } else { node->body.Add_Dirty( tbuf ); } } else { node->body.Add_Dirty(c); } } node->body.calc_hash(); } else { // Открывающий тэг. // Дальше может идти либо список вложенных тэгов, либо строка - тело. wchar_t c=0; // Пропустим пробелы. while( !reader->eof() ) { c = reader->wget(); if( c!=L' ' && c!=L'\t' && c!=L'\n' && c!=L'\r' ) // Встретили непустой символ! break; } if( c==L'<' ) { reader->unget(c); // Теперь надо загрузить все вложенные тэги и построить дерево. while( !reader->eof() ) { Node *nested = Load_Node( reader ); if( !nested ) { lem_rub_off(node); throw Invalid_Format(); } if( nested->IsComment() ) { // Комментарии удаляем. lem_rub_off(nested); } else if( nested->IsCData() ) { // Блок <[CData[ ... ]]> добавляем к телу node->body += nested->body; lem_rub_off(nested); } else if( nested->GetClosing() ) { if( nested->GetName() != node->GetName() ) { lem_rub_off(node); lem_rub_off(nested); throw Invalid_Format(); } // Список вложенных узлов закончен. node->SetClosed(true); lem_rub_off(nested); break; } else if( !nested->GetClosed() ) { lem_rub_off(node); lem_rub_off(nested); throw Invalid_Format(); } if( nested!=NULL ) { node->GetNodes().push_back( nested ); if( doctype==Parser::HtmlDoc ) { node->visible_text += nested->visible_text; } nested = NULL; } // Идем до следующего тэга. // Пропустим пробелы. while( !reader->eof() ) { c = reader->wget(); if( c!=L' ' && c!=L'\t' && c!=L'\n' && c!=L'\r' ) { // Встретили непустой символ! reader->unget(c); break; } } if( reader->eof() ) break; if( c != L'<' ) { if( doctype!=Parser::HtmlDoc ) { lem_rub_off(node); // Тело тэга может быть либо список вложенных тэгов, либо строкой. // У нас тут получается смесь. throw Invalid_Format(); } else { reader->wget(); goto load_tag_body; } } } } else { load_tag_body: // Собираем тело тэга - до закрывающего. node->body.reserve(128); bool just_started=true; while( !reader->eof() ) { lem::Stream::pos_type tpos = reader->tellp(); if( !just_started ) c = reader->wget(); else just_started = false; if( c==L'<' ) { // Начало нового тэга if( reader->wget()==L'/' ) { // это закрывающий тэг, он должен закрывать текущий тэг. reader->seekp(tpos); break; } else { // Произвольный вложенный тэг (HTML) reader->seekp(tpos); Node *nested = Load_Node(reader); if( !nested || !nested->GetClosed() ) { throw Invalid_Format(); } else if( nested->IsComment() ) { lem_rub_off(nested); } else if( nested->IsCData() ) { node->body += nested->body; lem_rub_off(nested); } if( nested!=NULL ) { node->nodes.push_back(nested); if( doctype==Parser::HtmlDoc ) { node->visible_text += nested->visible_text; } } continue; } } if( c==L'&' ) { // Далее идет либо символическое имя символа, либо его числовой код. c = reader->wget(); if( c==L'#' ) { // Считываем hex код символа до ; UFString hex; hex.reserve(6); while( !reader->eof() ) { c = reader->wget(); if( c==L';' || c==WEOF ) break; hex.Add_Dirty(c); } c = lem::to_int( hex ); } else { FString char_name; char_name.reserve(8); char_name.Add_Dirty( char(c) ); while( !reader->eof() ) { c = reader->wget(); if( c==L';' || c==WEOF ) break; char_name.Add_Dirty( char(c) ); } c = lem::CodeConverter::Sgml_2_Char(char_name); } } node->body += c; if( doctype==Parser::HtmlDoc ) { node->visible_text += c; } } if( reader->wget()!=L'<' ) { lem_rub_off(node); throw Invalid_Format(); } // Сейчас должен быть закрывающий тэг! UFString close_tag; Read_Tag( reader, close_tag ); UFString name2, body2; Collect< pair<UFString,UFString> > attrs2; Break_Tag( close_tag, name2, attrs2, body2 ); if( doctype==Parser::HtmlDoc && !remove_char( name2,L'/').eqi(node->name) ) { lem_rub_off(node); throw Invalid_Format(); } else if( remove_char(name2,L'/') != node->name ) { lem_rub_off(node); throw Invalid_Format(); } node->SetClosed(true); } } if( doctype==Parser::HtmlDoc ) { // Некоторые типы тэгов в HTML фактически вводят в текст разделитель. if( IsTextDelimiterTag( node->GetName() ) ) { node->visible_text += L' '; } } return node; }
void Parser::Break_Tag( const UFString &tag, UFString &name, Collect< pair<UFString,UFString> > &attrs, UFString &body // для ![CDATA[ ... ]] ) const { int i=0; // Пропускаем пробелы while( tag[i] ) { if( tag[i]!=L' ' && tag[i]!=L'\t' && tag[i]!=L'\r' && tag[i]!=L'\n' ) break; i++; } if( tag.eq_beg(L"![CDATA[") ) { name = L"![CDATA["; body = lem::mid( tag, 8, tag.length()-10 ); return; } // Пропускаем имя тэга while( tag[i] ) { if( tag[i]==L' ' || tag[i]==L'\t' || tag[i]==L'\r' || tag[i]==L'\n' || tag[i]==L'>' ) break; name += tag[i]; i++; } // Читаем атрибуты while( tag[i] ) { // Читаем очередную пару имя=значение // Пропускаем пробелы while( tag[i] ) { if( tag[i]!=L' ' && tag[i]!=L'\t' && tag[i]!=L'\r' && tag[i]!=L'\n' ) break; i++; } if( !tag[i] || tag[i]=='/' || tag[i]=='>' ) return; // Читаем имя атрибута = до знака '=' или пробела или конца тэга UFString var, value; if( tag[i]==L'"' ) { // В апострофах - читаем как есть до второго апострофа i++; while( tag[i] ) { if( tag[i]==L'"' ) { i++; break; } if( tag[i]==L'\n' || tag[i]=='\r' ) throw Invalid_Format(); var += tag[i]; i++; } } else { while( tag[i] ) { if( tag[i]==L'=' || tag[i]==L'/' || tag[i]==L'>' || tag[i]==L' ' || tag[i]==L'\t' || tag[i]==L'\r' || tag[i]==L'\n' ) break; var += tag[i]; i++; } } // После имени атрибута до знака '=' могут быть пробелы - пропускаем. while( tag[i] ) { if( tag[i]!=L' ' && tag[i]!=L'\t' && tag[i]!=L'\r' && tag[i]!=L'\n' ) break; i++; } if( tag[i]==L'=' ) { // Читаем значение атрибута - до пробела или конца тэга i++; // Пропускаем начальные пробелы while( tag[i] ) { if( tag[i]!=L' ' && tag[i]!=L'\t' && tag[i]!=L'\r' && tag[i]!=L'\n' ) break; i++; } if( tag[i]==L'"' ) { // Значение атрибута - в апострофах, читаем как есть i++; while( tag[i] ) { if( tag[i]==L'"' ) { i++; break; } if( tag[i]==L'\n' || tag[i]=='\r' ) throw Invalid_Format(); value += tag[i]; i++; } } else { while( tag[i] ) { if( tag[i]==L'=' || tag[i]==L'/' || tag[i]==L'>' || tag[i]==L' ' || tag[i]==L'\t' || tag[i]==L'\r' || tag[i]==L'\n' ) break; value += tag[i]; i++; } } } // Добавляем очередной атрибут attrs.push_back( make_pair( var, value ) ); } return; }