// ****************************************************** // Загружаем узел вместе со всеми его вложенными узлами. // ****************************************************** 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::Read_Tag( WideStream *reader, UFString &tag ) const { tag.clear(); int count=0; while( !reader->eof() ) { wchar_t c = reader->wget(); if( c==L'>' ) break; if( !count && c==L'!' ) { // Или комментарий <!-- ... --> // или блок кода <![CDATA[ ... ]]> // или начальный тэг <!DOCTYPE tag.Add_Dirty(c); count++; wchar_t c = reader->wget(); if( c==L'-' ) { // комментарий... tag.Add_Dirty(c); count++; while( !reader->eof() ) { wchar_t c = reader->wget(); if( c==WEOF ) break; if( count>3 && c==L'>' ) { // Комментарий заканчивается на --> if( tag.back()==L'-' && tag[tag.length()-2]==L'-' ) { goto tag_complete; } } tag.Add_Dirty(c); count++; } // Если комментарий не закончен нормально, то значит была ошибка throw Invalid_Format(); } else if( c==L'D' ) { // <!DOCTYPE ...> // загружаем как обычный тэг tag.Add_Dirty(c); count++; continue; } else if( c=='[' ) { // <![CDATA[ продолжаем загружать... tag.Add_Dirty(c); count++; continue; } else { // Неизвестный тэг, для HTML проигнорируем ошибку if( doctype==Parser::HtmlDoc ) { tag.Add_Dirty(c); count++; } else { throw Invalid_Format(); } } } else if( count==7 && c==L'[' && tag==L"![CDATA" ) { // блок кода после <![CDATA[ tag.Add_Dirty(c); count++; while( !reader->eof() ) { wchar_t c = reader->wget(); if( c==WEOF ) break; if( count>8 && c==L'>' ) { // Блок кода заканчивается на ]]> if( tag.back()==L']' && tag[tag.length()-2]==L']' ) { goto tag_complete; } } tag.Add_Dirty(c); count++; } if( doctype!=Parser::HtmlDoc ) throw Invalid_Format(); } if( c==WEOF || reader->eof() ) break; tag.Add_Dirty(c); count++; } tag_complete: tag.calc_hash(); return; }