Препарация HTML, или "мертвые" ссылки 2

Препарация HTML, или "мертвые" ссылки 2

Начало см.
в КГ №4

Рад снова вас приветствовать, дорогие любители посидеть над кодом и написать что-нибудь такое, для души, ну, а заодно и для дела. Как я и обещал в предыдущей статье, я хотел бы вернуться к теме "мертвых" ссылок. Прошлый раз мы ограничились проверкой только локальных линков. Но, как показал "опрос общественного мнения", многих не удовлетворяет такой вариант, мол, подавай нам универсальную программу, чтоб и по Интернету могла самостоятельно лазить, да еще и понравившиеся картинки с возможность докачки снизу вверх сохранять на винчестере:-). Но это была шутка. А теперь займемся делом.

Как, наверное, все уже поняли, стандартными средствами С++ при проверке интернет-ссылок (условимся так называть ссылки, выходящие за пределы вашего сайта) тут не обойтись. Для начала определим цели. Нам, как и в прошлый раз, необходима программа, которая в качестве параметра получала бы HTML-файл, находила в нем линки и проверяла их на актуальность. Нашей беде, очевидно, могут помочь средства визуального проектирования. Предлагаю взять популярное средство разработки, несправедливо обойденное моим вниманием в предыдущий раз, Visual C++ и библиотеку MFC. Создаем интерфейс программы. Будет он у нас диалоговый (и поддержку OLE-технологии не обещаю — говорю сразу:-). Итак, нам понадобятся четыре Edit-бокса и три кнопки. Два окошка Edit будем использовать для вывода сообщений юзверю (т.е. нам с вами), а одно — для поиска того самого файла, который мы и будем анализировать. О назначении четвертого расскажу чуть позже. Кнопок в количестве трех штук будет достаточно — соответственно для поиска файла, начала анализа и для выхода (по "крестику" лично мне попасть бывает сложнее, чем по кнопке с греющей душу надписью EXIT). Создаем новое приложение при помощи MFCAppWizard с именем scanning.exe. Все настройки оставляем по умолчанию. Конечно, имя можно задать свое — это по желанию, — но так легче будет ориентироваться. Заходим в ветвь Dialog и проектируем что-то типа этого:



Конечно, при желании можно для окошек информации задать атрибут Read-only, щелкнув по нему правой кнопкой (Properties -> закладка Styles -> поставить галочку рядом с этой опцией). Но лично мне всегда нравилось иметь полную свободу действий в программах, пусть и "самопальных". Далее все элементы интерфейса я буду называть именами, обозначенными надписями над ними. Итак, даем элементам ID:

Имя ID Имя переменной Ее тип Имя функции
Local File IDC_path m_path CString
File IDC_path_b

void CScanningDlg::Onpathb()
Your Site URL IDC_your m_you_site Cstring
Scannig Links IDC_out m_out Cstring
Results IDC_mess m_mes CString
Scan IDC_scan

void CScanningDlg::Onscan()


Надеюсь, из этой таблички понятно, какое имя какому элементу давать. Кнопочку EXIT сделаем из стандартно предлагаемой кнопки Cancel, просто переименовав ее. На этом собственно проектирование интерфейса закончено, приступаем к реализации алгоритма. Переменные зададим с помощью ClassWizard, как и функции. Но, кроме обработчиков нажатия кнопок, нам понадобится еще одна функция, которая и будет собственно проверять ссылки. А посему на вкладке ClassView щелкаем правой кнопкой мыши по классу CscanningDlg и в появившемся меню выбираем AddMemberFunction. Возвращаемое значение типа int, получаемые параметры Cstring url: "int CScanningDlg::sc(CString url)". Теперь дело за малым — изменить уже прописанные услужливым Visual C++ тела функций void CScanningDlg::Onpathb() и void CScanningDlg::Onscan() и только что вновь созданной CScanningDlg::sc(). При помощи Onpathb() будем вызывать стандартное диалоговое окно поиска файла, а путь к этому файлу будем заносить в переменную m_path. Итак, CScanningDlg::Onpathb() будет выглядеть вот так:
void CScanningDlg::Onpathb() { CFileDialog Dfile(TRUE);// Создаем элемент класса CfileDialog D
file int iResult=Dfile.DoModal();//а теперь собственно ищем файл m_path = Dfile.GetPathName();//Прис
ваиваем переменной m_path путь к найденному файлу UpdateData( FALSE ); //и обновляем данные в форме,
 чтоб пользователь увидел результаты своей "работы":-) } Анализируемый файл нашли, теперь 
необходимо вычленить из него web-линки. Делается это следующим образом (тело функции void CScanningD
lg::Onscan()):void CScanningDlg::Onscan() { int yn; int n=0,m=0,k=0,i,t; TCHAR a[]="<a href=
\"",b[]="htm",c[]="http://",d[]="</a>";//По фрагментам
 этих строк будем вычленять из HTML-файла ссылки CString m_Text,m_urlT,Stemp; CString& ref=m_Text; i
nt flag; UpdateData( TRUE ); if(m_path=="")//Производим проверку на наличие пути к анализи
руемому файлу { MessageBox("Введите имя файла"); return; }; CStdioFile test2(m_path,CFile:
:modeRead|CFile::typeBinary); //Открываем файл на чтение while(test2.ReadString(ref))//Входим в цикл
 построчного чтения данных из файла. Завершение по достижению конца файла { n=m_Text.Find(a);//Ищем 
в прочтенной строке "признаки" ссылки m=m_Text.Find(b); k=m_Text.Find(d); if(n!=-1) { //Ес
ли что-то нашли, вычленяем адрес for(i=9;i<=m-n+2;i++) { t=n+i; m_urlT+=m_Text[t];//Сохраняем ссы
лку }; for(i=0;i<=k-n+3;i++) { t=n+i; m_out+=m_Text[t]; //Выводим пользователю Текст ссылки вмест
е с тегами для наглядности (и для скорейшего поиска ошибки) }; UpdateData( FALSE );//Обновляем данны
е flag=1; if(m_urlT.Find(c)==-1)//Ищем сроку htt:// в прочитанной строке { //Если находим, проверяем
, был ли введен корень исследуемого сайта if(m_you_site!="") { //Если был введен, добавляе
м к ссылке и устанавливаем соответствующее значение flag Stemp=m_you_site; Stemp+=m_urlT; } else { S
temp=m_urlT; flag=0; }; } else Stemp=m_urlT; //Информируем пользователя m_mes+=Stemp; m_mes+="\
r\n"; /*Производим проверку ссылки, если значение флага удовлетворяет, т.е. ссылка является гло
бальной или введен корень исследуемого сайта*/ if(flag==1) yn=CScanningDlg::sc(Stemp); //Возвращаемо
е значение после можно как-нибудь использовать, пикнуть что-нибудь, например:-) //Обновляем данные. 
UpdateData( FALSE ); m_out+="\r\n"; m_urlT=""; }; } UpdateData( FALSE ); }

Теперь пришло время для того, чтобы рассказать, зачем нам нужен четвертый Edit-бокс. Если мы будем сканировать ссылки в файле со своего сайта, то наверняка в нем будут локальные ссылки. Так вот, чтобы не запускать еще одну программу проверки (хотя бы ту, что была описана в предыдущей статье), в данное окошко можно ввести адрес корневой директории вашего сайта. Потом, если программе встретятся локальные ссылки, она может автоматически добавить к ним введенную строку, таким образом не будет разницы, локальный это линк или нет. Если введенная строка отсутствует (читай равна ""), то проверка локальных ссылок не производится. Для этого введена дополнительная переменная flag. В Scanning Links выводятся тексты ссылок вместе с тегами. Это сделано для того, чтобы оператор (т.е. вы:-)) смог быстро найти в тексте исследуемого файла ошибку и исправить ее, если это возможно. Теперь разберем самую главную функцию, которая собственно и производит проверку ссылок. Тело ее выглядит таким вот образом:
int CScanningDlg::sc(CString url) { //Обновим данные на всякий случай UpdateData( FALSE ); //С
оздаем Internet-сессию CInternetSession session( _T( "ANDY" ), PRE_CONFIG_INTERNET_ACCESS 
); //Необходимо для HTTP-соединения CHttpConnection* Serv = NULL; //Доступ к файлам CHttpFile* n_fil
e = NULL; char temp[100]; //Пытаемся соединиться try { CString Server;//Сервер CString Ob; INTERNET_
PORT nomPort;//Порт подключения DWORD ServType; //Разбираем ссылку (адрес сервера, номер порта и др.
); если операция завершается неудачей, информируем об этом пользователя if ( AfxParseURL( url, ServT
ype, Server, Ob, nomPort ) == 0 ) { m_mes += "Неправильный синтаксис адреса"; m_mes += &qu
ot;\r\n"; UpdateData( FALSE ); return 1; } // Устанавливаем подключение по HTTP-протоклолу. Ser
v = session.GetHttpConnection( Server, nomPort ); //Отсылаем запрос об обьекте n_file = Serv->Ope
nRequest( CHttpConnection::HTTP_VERB_GET, Ob, NULL, 1, NULL, NULL, INTERNET_FLAG_EXISTING_CONNECT | 
INTERNET_FLAG_NO_AUTO_REDIRECT ); //Формируем заголовок n_file->AddRequestHeaders( _T( "Acce
pt: */*\r\nUser-Agent: ANDY\r\n" ) ); n_file->SendRequest( ); DWORD dwRet; //Сохраняем в пер
еменной dwRet код состояния n_file->QueryInfoStatusCode( dwRet ); itoa( dwRet, temp, 10 ); //Сооб
щаем его пользователю m_mes += (CString)&temp[0]; m_mes += "\r\n"; CString strHeader; Upda
teData( FALSE ); //Производим анализ кода состояния if( dwRet != 200 ) { m_mes += "NOT FOUND\r\
n--------\r\n"; UpdateData( FALSE ); return 1; } if( dwRet == 200 ) { m_mes += "OK\r\n----
----\r\n"; UpdateData( FALSE ); return 1; } n_file->Close();Serv->Close(); } //Если соеди
нение завершилось неудачей, получаем сообщение об ошибке catch ( CInternetException* pEx ) { char sz
Err[1024]; pEx->GetErrorMessage( szErr, 1024 ); m_mes += "Error: ( "; itoa( int(pEx->
;m_dwError), temp ,10 ); m_mes += (CString)&temp[0]; m_mes += " ) "; m_mes += (CString)&sz
Err[0]; m_mes += "\r\n"; UpdateData( FALSE ); //Подчищаем за собой, завершаем сессию и др.
 pEx->Delete( ); // Удаление переменной CInternetException if ( n_file != NULL ) delete n_file; /
/Закрываем Internet-файл if ( Serv != NULL ) delete Serv;//Закрываем сессию session.Close( );//Закры
ваем сессию return 1; } //Независимо от того, соединились или нет, убираем за собой. if ( n_file != 
NULL ) delete n_file; if ( Serv != NULL ) delete Serv; session.Close( ); return 0; } 

Для того, чтобы приложения могли связываться с Internet в Visual C++, существует специальный класс WinInet Class. В него входят несколько подклассов. Вот некоторые из них:
CinternetSession — все приложения, которые хотят в дальнейшем использовать WinInet Class, должны сначала обратиться к CinternetSession. Этот класс создает интернет-сессию.
CinternetConnection — создает соединение. Это базовый класс для классов CFtpConnection, CGopherConnection и CHttpConnection.
ChttpConnection — создает соединение по HTTP-протоколу.
CinternetFile — при помощи этого класса можно получить доступ к файлам на сервере. Он является базовым для классов CGopherFile и CHttpFile.

Итак, для соединения по HTTP-протоколу необходимо:
1. Открыть сессию: "CInternetSession session( _T( "ANDY" ), PRE_CONFIG_INTERNET_ACCESS );"
2. Создать переменную Serv класса ChttpConnection: "CHttpConnection* Serv = NULL;"
3. Создать переменную n_file класса ChttpFile: "CHttpFile* n_file = NULL;"
4. Установить подключение по HTTP-протоклолу: "Serv = session.GetHttpConnection(Server, nPort );"
5. Послать запрос об объекте (Ob): "n_file = Serv->OpenRequest(CHttpConnection::HTTP_VERB_GET, Ob, NULL, 1, NULL, NULL, INTERNET_FLAG_EXISTING_CONNECT | INTERNET_FLAG_NO_AUTO_REDIRECT);"
6. Добавить заголовок к HTTP-запросу: "n_file->AddRequestHeaders( _T( "Accept: */*\r\nUser-Agent: ANDY\r\n" ) );"
7. Послать запрос "n_file->SendRequest( );"
8. После этого читаем файл с сервера: "int Read = n_file->Read( strBody, 1024 );" (правда, в нашей программе мы этого делать не будем за ненадобностью).

Особенность использования функций — членов класса WinInet состоит в том, что всегда есть возможность того, что ссылка является неверной, а также возможны задержки при Internet-подключении.
Как вы, наверно, уже успели заметить, с помощью WinInet Class можно довольно просто создавать интернет-приложения — даже такие "специфические", как это. Ну, использование вновь созданного приложения довольно просто. "Скармливаем" ему исследуемый файл и наслаждаемся результатом:-). Вот, пожалуй, и все.

Спичеков Александр, aka MentALzavR, zavr6@mail.ru



Компьютерная газета. Статья была опубликована в номере 06 за 2004 год в рубрике программирование :: разное

©1997-2024 Компьютерная газета