В первой части мы рассмотрели простой пример использование регулярных выражений в Delphi XE и научились получать список городов и их идентификаторов с tv.mail.ru. Теперь рассмотрим следующий шаг работы с сервисом – получение актуального списка каналов для заданного города (региона) и их распределение по категориям: метровые, спутниковые, региональные и т.д.
2. Работа с каналами
Первым делом разберемся с тем как получать список каналов актуальных для заданного региона. Для этого вспомним JavaScript-функцию, на которую я обращал внимание в прошлом посте:
function changezone() { location.href='/setup.html?zone='+ d.setupzone.zone.options[d.setupzone.zone.selectedIndex].value+ '&channels=5,108,209,235,241,244,252,256,276,290,330,566&types=1,30,30,30,30&tosave=1' }
Разберемся с тем, что передает эта функция. Если Вы хотя бы немного знакомы с JavaScript, то понять принцип действия функции не составит для вас никакого труда, ну, а для тех кто JavaScript никогда не серьезно не использовал есть вариант провести простой эксперимент.
d.setupzone.zone.options[d.setupzone.zone.selectedIndex].value
Это ничто иное как индекс города или региона, который мы уже научились определять с помощью TRegExp. Теперь составляем вот такой URL:
http://tv.mail.ru/setup.html?zone=74
Вставляем его в браузер и смотрим, что получается:
Мы оказались на странице настройки каналов для Екатеринбурга. 74 – идентификатор Екатеринбурга. Измените значение параметра zone и получите настройки для другого региона. При этом обратите внимание, что мы не воспользовались другими параметрами URL, в том числе и параметром channels, который содержит идентификаторы каналов, которые при загрузке страницы будут автоматически выбраны.
Теперь мы уже можем, не заходя на страницу сервиса, загрузить страницу, содержащую список каналов телепередач для необходимого города. Для этого допишем наше Delphi-приложение следующим образом:
1. Объявим следующие типы данных:
type TRegion = record City: string; ID : integer; end; type TRegions = array of TRegion;
Смысл понятен — храним информацию по городам в виде динамического массива, каждый элемент которого представляет собой запись record. Если хотите — используйте списки, классы, хоть дженерики — сейчас это не принципиально, главное чтоб Вы смогли организовать доступ к элементам. Теперь изменяем наше приложение следующим образом: добавим на форму TComboBox и при нажатии на кнопку «Загрузить» будем без каких-либо промежуточных действий сразу заполнять список названиями городов. Внешний вид программы:
Теперь объявляем глобальную переменную:
Regions: TRegions;
И пишем обработчик кнопки «Загрузить»:
procedure TForm1.Button1Click(Sender: TObject); const Pattern = 'value=\S(\d{1,3})\S>\S{1}МСК\s{1}\S?\d{1,2}:\d{2}\S{1}\s?(.*)$'; var Input: RawByteString; i: integer; begin try with THTTPSend.Create do begin Regions := nil; ComboBox1.Items.Clear; if HTTPMethod('GET', 'http://tv.mail.ru/setup.html') then begin Document.Position := 0; SetLength(Input, Document.Size); Document.Read(PAnsiChar(Input)^, Document.Size); if TRegEx.IsMatch(Input, Pattern, [roIgnoreCase, roMultiLine]) then with TRegEx.Matches(Input, Pattern, [roIgnoreCase, roMultiLine]) do begin SetLength(Regions, Count); for i := 0 to Count - 1 do begin Regions[i].City := Item[i].Groups[2].Value; Regions[i].ID := StrToInt(Item[i].Groups[1].Value); ComboBox1.Items.Add(Regions[i].City); end; end else ComboBox1.Text := 'Список городов не найден'; end else ComboBox1.Items.Add('Загрузка не удалась') end; except Memo1.Lines.Add('Ошибка загрузки данных с mail.ru') end; end;
Приведенный выше код процедуры по большому счёту не отличается от того, который мы использовали в прошлй раз, когда проверяли регулярку (кстати, обратите внимание, что она немного сменилась, но работает так же). Единственное различие заключается в том, что сейчас мы воспользовались исключительно классовыми методами TRegEx. Теперь можете запустить приложение и убедиться, что ComboBox исправно заполняется названиями городов.
Следующий шаг – получение списка каналов. Для этого нам необходимо сделать правильный запрос (см. выше) и составить регулярные выражения отдельно для метровых каналов и спутниковых.
Начнем с составления регулярных выражений.
Находим в исходнике страницы настроек код для checkbox’ов каналов. Для метрового канала он выглядит так:
Для спутникового канала так:
Опять первое с чего начинаем – это ищем, чем различаются эти записи. Самое главное отличие заключается в том, что в HTML-коде для спутникового канала не определяется событие onChange, которое выводит на странице предупреждение о том, что настройки следует сохранять. И именно этим различием мы и воспользуемся для того, чтобы различать метровый и спутниковый канал.
Регулярные выражения у меня получились такие. Для метрового канала:
name=\S{1}channelsetup.(\d{1,3})\S{1}.*onClick=.*onchange=\S{1}show_message.{4}(.*)<
Для спутникового:
name=\S{1}channelsetup.(\d{1,3})\S{1}.*onClick=\S{1}show_message.{5}label.*channelsetup.{2,5}>(.*)<
Здесь, как и в случае работы с городами регулярные выражения содержат по две группы: для сохранения идентификатора канала и его названия. Теперь приступим к програмированию. Объявлем следующие типы данных:
type TChannelType = (ctAll, ctMeter, ctSputnik); type TChannel = record ChannelType: TChannelType; ID : integer; ChannelName: string; end; type TChannels = array of TChannel;
Значение ctAll у TChannelType будем использовать при сортировке каналов в списке. Теперь добавим на главную форму ещё два элемента: ComboBox — в котором будем хранить типы каналов для их сортировки и CheckListBox для вывода списка каналов:
Для парсинга списка каналов напишем следующую процедуру:
procedure TForm1.ParseChannels(Input: string; ChanType: TChannelType); const Pattern_Meter = 'name=\S{1}channelsetup.(\d{1,3})\S{1}.*onClick=.*onchange=\S{1}show_message.{4}(.*)<'; Pattern_Sputnik = 'name=\S{1}channelsetup.(\d{1,3})\S{1}.*onClick=\S{1}show_message.{5}label.*channelsetup.{2,5}>(.*)<'; var i:integer; Pattern: string; begin if ChanType=ctAll then Exit; case ChanType of ctMeter: Pattern:=Pattern_Meter; ctSputnik: Pattern:=Pattern_Sputnik; end; if TRegEx.IsMatch(Input, Pattern, [roIgnoreCase, roMultiLine]) then with TRegEx.Matches(Input, Pattern, [roIgnoreCase, roMultiLine]) do begin SetLength(Channels, Length(Channels)+Count); for i := 0 to Count - 1 do begin Channels[Length(Channels)-Count+i].ChannelType:=ChanType; Channels[Length(Channels)-Count+i].ChannelName:=Item[i].Groups[2].Value; Channels[Length(Channels)-Count+i].ID:=StrToInt(Item[i].Groups[1].Value); end; end end;
Т.е. в зависимости от типа канала выбираем необходимое регулярное выражение, парсим список и заносим данные в массив. Список каналов будем получать сразу после выбора города, т.е. по событию OnChange у ComboBox1:
procedure TForm1.ComboBox1Change(Sender: TObject); var Input: RawByteString; i: integer; begin Channels:=nil; CheckListBox1.Items.Clear; with THTTPSend.Create do begin if HTTPMethod('GET','http://tv.mail.ru/setup.html?zone='+ IntToStr(Regions[ComboBox1.ItemIndex].ID)) then begin Document.Position := 0; SetLength(Input, Document.Size); Document.Read(PAnsiChar(Input)^, Document.Size); ParseChannels(Input,ctMeter); ParseChannels(Input,ctSputnik); CheckListBox1.Items.BeginUpdate; for i:=0 to Length(Channels)-1 do CheckListBox1.Items.Add(Channels[i].ChannelName); CheckListBox1.Items.EndUpdate; end; end; end;
Ну и, наконец, сортировать каналы по их типу будем при срабатывании OnChange у ComboBox2:
procedure TForm1.ComboBox2Change(Sender: TObject); var i:integer; typ: TChannelType; begin CheckListBox1.Items.Clear; typ:=TChannelType(ComboBox2.ItemIndex); CheckListBox1.Items.BeginUpdate; for i:=0 to Length(Channels)-1 do if (Channels[i].ChannelType=typ)OR (typ=ctAll) then CheckListBox1.Items.Add(Channels[i].ChannelName); CheckListBox1.Items.EndUpdate; end;
Вот теперь можно запустить приложение, загрузить список городов, выбрать город и посмотреть на работу с каналами. Вот, например, вид программы в которой выбраны метровые каналы для Москвы:
В следующий раз научимся выбирать каналы, сохранять настройки и парсить программу тв.
Так как есть подозрение, что при публикации регулярные выражения даже заключенный в теги pre и code могу с течением времени “покарежится” (хз почему так, но в некоторых постах они реально “сломались”).
Книжная полка
Описание: Рассмотрены практические вопросы по разработке клиент-серверных приложений в среде Delphi 7 и Delphi 2005 с использованием СУБД MS SQL Server 2000, InterBase и Firebird. Приведена информация о теории построения реляционных баз данных и языке SQL. Освещены вопросы эксплуатации и администрирования СУБД.
|
||
Название: О чем не пишут в книгах по Delphi
Описание: Рассмотрены малоосвещенные вопросы программирования в Delphi. Описаны методы интеграции VCL и API. Показаны внутренние механизмы VCL и приведены примеры вмешательства в эти механизмы. Рассмотрено использование сокетов в Delphi: различные режимы их работы, особенности для протоколов TCP и UDP и др.
|
Без строки
[code]
Memo1.Lines.LoadFromStream(Document);
[/code]
после
[code]
Document.Position := 0;
[/code]
Работать не хочет..список городов не парсит