Во истину глаголят «Спешка нужна при ловле блох». Вот и я, начиная работу над Google Celenfar API для Delphi поторопился, забыв эту пословицу. Думал обойтись малой кровью, как при работе с FeedBurner API — разобрать пару XML-документов и сформировав на выходе пару записей. Ан не тут то было. Дойдя до работы с событиями календаря понял, что каждый раз парсить по-разному в сущности однотипный документ фида дело лишнее и не нужное. Можно, конечно, оставить всё как есть и продолжать методом тыка определять содержимое документа, но мне лень :) Да и толку от такого модуля будет мало. Поэтому решил подойти к вопросу с начала, а не с неизвестно какого боку, как я начал. И первое, что решил сделать — это поработать с Google Data Protocol с самого начала, т.к. собственно на нём всё держится и с использованием этого протокола работают все API Google.Для данных API форматом по умолчанию является Atom. Дополнительно мы можем использовать, например, формат RSS для чтения и отправки данных. При этом модель предоставления данных будет такой же самой, но будут небольшие изменения в именах элементов.
Любой фид (Feed) Google может содержать следёющие элементы:
- Title — заголовок
- ID — идентификатор фида, например, для событий календария id — это ссылка на RSS-канал событий.
- HTML Link — ссылка фида. Каждый элемент Link в XML-документе содержит атрибуты rel, href и type. Feed может содержать несколько ссылок.
- Description — описание фида. В зависимости от формата данных описание может содержаться в элементах subtitle или
description
- Language — язык фида
- Copyright — авторское право
- Author — автор фида. При этом дочерними элементами узла являются name (имя) и email (электронная почта) автора.
- Last Update Date (элемент updated) — дата последнего изменения фида
- Generator — данные о генераторе фида.
- Icon, Logo — ссылка на иконку фида.
Любой из перечисленных элементов может отсутствовать. Далее каждый фид может содержать неограниченное количество элементов entry, у каждого из которых также определен «стандартный» набор элементов. В этих же элементах entry располагаются и другие данные, например, часовой пояс календаря, местоположение, цвет и т.д. Кроме того в этих элементах могу содержаться ссылки на другие фиды и фиды целиком, например, элемент entry может содержать в себе фид с комментариями пользователей и т.д. И каждый раз разбирать по новой такой фид, как я уже говорил, мягко говоря не улыбает.
Поэтому, первый шаг, который я решил сделать при работе с протоколом — это научиться загружать нужный мне feed, читать его основные элементы и определять наличие элементов entry.
Читать будем всё, в т.ч. типы ссылок фида, uri генератора и т.д. Поэтому прежде всего определим типы данных для отдельных элементов фида. Так элемент фида link может содержать три атрибута rel, type и href поэтому этот элемент можно представить, например в виде записи:
type TLinkElement = record rel: string; typ: string; href: string; end;
Никакого текстового содержания у этого элемента нет. Элемент Author может содержать два текстовых узла name и email, чтобы держать эти анные вместе я решил определить элемент Author как:
type TAuthorElement = record Email: string; Name : string; end;
Элемент Generator может содержать атрибуты version и uri, а тексовое содержимое узла — название генератора:
type TGeneratorElement = record varsion: string; uri: string; name: string; end;
Остальные элементы у Feed — это текстовые узлы XML-документа, содержащие определенную информацию. Остается определиться с тем, как хранить ссылки фида, т.к. их количество может меняться. Выделять в классе N-ное количество полей — не вариант, поэтому я решил представить все ссылки фида в виде списка TList:
type TLinkElementList = class(TList) private procedure SetRecord(index: Integer; Ptr: PLinkElement); function GetRecord(index: Integer): PLinkElement; public constructor Create; procedure Clear; destructor Destroy; override; property LinkElement[i: Integer]: PLinkElement read GetRecord write SetRecord; end;
Этот список аналогичен тому, который я рассматривал в посте про вспомогательный модуль.
В итоге заготовка под класс Feed’а получилась такой:
type TFeed = class private FDocument : IXMLDocument; FEtag : string; FTitle : string; FID : string; FHTMLLinks : TLinkElementList; FDescription : string; FLanguage : string; FCopyright : string; FAuthor : TAuthorElement; FUpdateDate : TDateTime; FCategory : TCategoryElement; FGenerator : TGeneratorElement; FIcon : string; FLogo : string; FEntrys : array of TEntryElemet; procedure GetBasicElements; public constructor Create; procedure LoadFeed(const cLinkURL: string; ExtendedHeaders: TStringList); property ETag: string read FEtag write FEtag; property Title: string read FTitle write FTitle; property Id: string read FID write FID; property Description: string read FDescription write FDescription; property Language : string read FLanguage write FLanguage; property Copyright: string read FCopyright write FCopyright; property UpdateDate : TDateTime read FUpdateDate write FUpdateDate; property Category : TCategoryElement read FCategory write FCategory; property Icon : string read FIcon write FIcon; property Logo : string read FLogo write FLogo; property HTMLLink : TLinkElementList read FHTMLLinks; end;
В Create создаем пустой список:
constructor TFeed.Create; begin inherited Create; FHTMLLinks:=TLinkElementList.Create; end;
Теперь пробуем загрузить како-нибудь фид с данным и прочитать его содержимое. Я решил далеко не ходить и воспользоваться фидом событий своего календаря.
Загрузку фида будем осуществлять с помощью класса THTTPSender, рассмотренного в первом посте про Google Celendar API (кстати класс немного изменился и вырос, потом расскажу, что там и к чему).
Метод LoadFeed содержит следующие входные параметры:
cLinkURL — ссылка на фид
ExtendedHeaders — список дополнительных заголовков запроса. Используется тогда, когда необходимо подтвердить авторизацию в сервисе.
Сам метод выглядит так:
procedure TFeed.LoadFeed(const cLinkURL: string; ExtendedHeaders: TStringList); begin with THTTPSender.Create do begin if ExtendedHeaders.Count>0 then Headers.Assign(ExtendedHeaders); Method:='GET'; FDocument:=NewXMLDocument(); FDocument.LoadFromXML(PostRequest(cLinkURL)); if FDocument.IsEmptyDoc then ShowMessage('Загрузка не удалась') else GetBasicElements end; end;
То есть получаем документ, грузим его в XML Document и если документ оказывается не пустой, т.е. не было никаких ошибок соединения, то пробуем чиать данные фида методом GetBasicElements.
При этом, есть один тонкий момент, который будет доработан, когда THTTPSender будет работать в полную силу. Момент заключается в том, что при любом ответе сервера документ будет не пустой, т.к. в теле ответа будет содержаться html-код с описанием ошибки. Поэтому, если Вы решите (вдруг) воспользоваться приведенным выше кодом, то пока советую использовать для отправки/получения данных какую-нибудь готовую библиотеку, например, Synapse или Indy.
Сам метод GetBasicElements достаточно прост и работа его заключается в том, чтобы прочитать все элементы фида и сохранить их в соответствующие поля. Выглядит он так (часть метода):
procedure TFeed.GetBasicElements; var i:integer; Root: IXMLNode; LinkElement: PLinkElement; begin if FDocument.IsEmptyDoc then Exit; Root:=FDocument.DocumentElement; if Root.Attributes['gd:etag']<>null then FEtag:=Root.Attributes['gd:etag']; for i:=0 to Root.ChildNodes.Count - 1 do begin with Root.ChildNodes.Get(i)do begin if LowerCase(NodeName)='title' then FTitle:=Text else [...] else if LowerCase(NodeName)='link' then begin New(LinkElement); with LinkElement^ do begin if Attributes['rel']<>null then rel:=Attributes['rel']; if Attributes['type']<>null then typ:=Attributes['type']; if Attributes['href']<>null then href:=Attributes['href']; end; FHTMLLinks.Add(LinkElement); end else [...] end; end; end;
В следующий раз планирую доработать THTTPSender и разобрать детально элементы entry, т.к. с ними придётся очень часто иметь дело в работе с Google Celendar API.