Сегодня решил немного продолжить тему работы с XML-RPC в WordPress. Как это обычно со мной бывает, идея родилась в момент чтения случайного блога, вздумалось взглянуть на работу с постами в блоге и, заодно, попробовать написать что-нибудь под свои нужды.
Естественно программу я сегодня не выложу, но некоторые выкладки, листинги и идеи в посте будут присутствовать.
Вкратце работу с XML-RPC я рассматривал в посте «XML-RPC в Delphi. Первое знакомство с WordPress изнутри.» Сегодня попробуем продвинуться дальше в своей работе и использовать несколько взаимосвязанных методов для получения определенной информации из блога.
Конкретизируем цель на сегодня: необходимо получить данные по постам в блоге, используя доступные методы из API WordPress.
Для достижения поставленной цели нам понадобятся следующие модули Delphi: XMLIntf, xmldom, XMLDoc и библиотека synapce или компонент Indy idHTTP (кому как угодно).
1. Тестируем соединение с блогом.
Полагаю, что первое, что следует сделать — это проверить корректность работы с блогом на предмет следующих возможных ошибок:
- В блоге отключена возможность использования XML-RPC
- Пользователь предоставил некорректные данные (url, логин или пароль).
Для проверки возможности работы с XML-RPC в блоге достаточно воспользоваться методом demo.sayHello. Если ответом будет строка «Hello», значит всё в порядке и можно приступать к следующему шагу проверки. Для выполнения этой проверки нам потребуется выполнить три простенькие задачки:
- сформировать правильный XML-документ
- отправить запрос на сервер и получить ответ
- проанализировать ответ
Формируем XML-документ, который должен выглядеть так:
<methodCall> <methodName>demo.sayHello</methodName> <params> <param> <value> <string>test</string> </value> </param> </params> </methodCall>
Для этого воспользуемся интерфейсом IXMLDocument:
[...] var doc: IXMLDocument; //документ Root: IXMLNode; //корневой узел begin inherited Create; doc:=NewXMLDocument();//создаем пустой документ Root:=Doc.CreateElement('methodCall','');//добавляем корневой узел Doc.DocumentElement:=Root; Root.AddChild('methodName').NodeValue:='demo.sayHello';//добавляем название метода Root.AddChild('params').AddChild('param').AddChild('value').AddChild('string').NodeValue:='test';//записываем параметры метода [...]
Так как сам по себе XML-документ достаточно простой, то я позволил себе немного «похалявить» и последней строкой кода записал сразу все узлы и значение единственного параметра для нашего метода.
Теперь можно отправить документ на сервер и получить ответ:
[...] with THTTPSend.Create do begin Doc.SaveToStream(Document);//записываем документ в тело запроса if HTTPMethod('POST',aURL) then begin //запрос успешно отправлен и получен ответ end else begin //запрос не удался end; end; [...]
Что мне нравится в Synapce, так это то, что не требуется лишних «телодвижений» в плане заполнения заголовков Content-Length, Content-Type и пр. Конечно никто не мешает заполнить все возможные заголовки самому, но можно обойтись и так, как показал я выше — всё на автомате.
Двигаемся дальше — проводим анализ ответа.
Позволю себе напомнить Вам, что удачная отправка запроса на сервер никак не свидетельствует о том, что мы успешно получили доступ к XML-RPC блога. Удачная отправка запроса свидетельствует только о том, что мы отправили запрос и получили ответ, а _что_ находится в ответе ошибка или нет — мы пока не знаем.
Чтобы пока не забивать голову лишними способами и методами парсинга ответа от сервера, предлагаю в данном случае остановиться на применении простой проверки:
[...] Doc.LoadFromStream(Document,xetUTF_8);//записали XML-документ if Doc.DocumentElement.ChildNodes.FindNode('fault')=nil then ShowMessage('XML-RPC работает исправно') [...]
В соответствии со спецификацией XML-RPC сообщения об ошибках содержится в узле с названием fault. Следовательно, применительно к нашему случаю достаточно проверить наличие такого узла в ответном XML-документе — если его нет, то значит проверка прошла успешно, был сформирован корректный запрос и XML-RPC работает исправно.
Переходим к следующему шагу — проверке на корректность предоставленных данных пользователем и возможности работы пользователя с XML-RPC блога.
С XML-RPC блога имеет право работать только администратор, следовательно, необходимо узнать кто пробует получить доступ. Для этого воспользуемся методом wp.getUsersBlogs. Параметрами метода являются логин и пароль.
Но прежде, чем приступим к отправке запроса и получению ответа, думаю, стоит немного задуматься о будущем и предусмотреть работу с ошибками, формирование документов и т.д.
В предыдущей проверке, можно сказать, было баловство — простейших вариант работы типа:
отправил/получил/тут_же_разобрал/забыл/пошел_дальше.
Так как я планирую развивать модуль по работе с API WordPress и дальше, то есть смысл определиться со следующими моментами в работе:
- Сформировать «скелет» документа
- Записать в документ все необходимые параметры, учитывая типы данных
- Отправить запрос и получить ответ от сервера
- Проанализировать ответ и, если в ответе содержится сообщение об ошибке, то правильно его прочитать
Все эти четыре шага я сделал отдельными методами класса. Под «скелетом» документа я понимаю следующее содержимое:
<methodCall> <methodName>MethodName</methodName> <params> </params> </methodCall>
То есть часть документа, содержащая имя метода и узел params без содержимого. Дальше на останется только правильно заполнить список параметров. Чем мы сейчас и займемся.
Всего в XML-RPC предусмотрено использование шести простых типов данных:
- int и i4 — целые числаinteger)
- double — дробные числа
- string — строки
- base64 — закодированная строка
- dateTime.iso8601 — дата/время
- boolean
Заводим новый тип данных:
TSimpleType = (tsInt, tsI4, tsString, tsDouble, tsDateTime, tsBase64, tsBoolean);
С помощью значений этого типа будем определять тэг для значения параметра.
Так как операции создания «скелета» документа и добавления параметров метода разнесены по разным функциям, то создадим ещё один вспомогательный тип данных:
PXMLDocument = ^IXMLDocument;
Теперь сам метод добавления параметра в документ:
procedure TBlog.SetParam(SimpleType: TSimpleType; Value: string; Document: PXMLDocument); var Root: IXMLNode; begin if Document^.IsEmptyDoc then Exit;//документ пуст Root:=Document^.DocumentElement.ChildNodes.FindNode('params'); if Root=nil then Exit; //узел не найден case SimpleType of tsInt:Root.AddChild('param').AddChild('value').AddChild('int').NodeValue:=Value; tsI4:Root.AddChild('param').AddChild('value').AddChild('i4').NodeValue:=Value; tsString:Root.AddChild('param').AddChild('value').AddChild('string').NodeValue:=Value; tsDouble:Root.AddChild('param').AddChild('value').AddChild('double').NodeValue:=Value; tsDateTime:Root.AddChild('param').AddChild('value').AddChild('dateTime.iso8601').NodeValue:=Value; tsBase64:Root.AddChild('param').AddChild('value').AddChild('base64').NodeValue:=Value; tsBoolean:Root.AddChild('param').AddChild('value').AddChild('boolean').NodeValue:=Value; end; end;
Этот метод работает только в случае записи простого типа. При работе со структурами необходимо доработать алгоритм.
Теперь про анализ сообщений об ошибке. Рассмотрим пример того, как выглядит сообщение об ошибке в XML-RPC:
<methodResponse> <fault> <value> <struct> <member> <name>faultCode</name> <value> <int>403</int> </value> </member> <member> <name>faultString</name> <value> <string>Bad login/pass combination.</string> </value> </member> </struct> </value> </fault> </methodResponse>
Сообщение об ошибке приходит нам в структуре. Причём, если считать, что теги member нумеруются от нуля, то каждый чётный элемент структуры — это код ошибки, а нечётный — текст ошибки. Следовательно метод обработки сообщений об ошибке может выглядеть так:
function TBlog.ParseErrors(aDocument: PXMLDocument): TStringList; var i:integer; List: IDOMNodeList; code: string; begin List:=aDocument^.DOMDocument.getElementsByTagName('member'); Result:=TStringList.Create; for i:=0 to List.length-1 do begin case i mod 2 of 0:code:=(List.item[i].lastChild.firstChild as IDOMNodeEx).text; //чётный элемент - читаем код ошибки 1://нечётный элемент - читаем текст ошибки и записываем результат Result.Add(code+' '+(List.item[i].lastChild.firstChild as IDOMNodeEx).text); end; end; end;
Здесь код и текст ошибки записывается в TStringList. Думаю, что при необходимости можно легко сделать, чтобы код и текст читались в разные списки или массивы. Нам пока это не требуется.
Отправку документа мы уже рассматривали, поэтому сразу привожу метод проверки данных на корректность:
function TBlog.CheckUserAccess(const aURL, aUser, aPassword: string;var Error:string): boolean; var Doc:IXMLDocument; begin Doc:=GetDocument('wp.getUsersBlogs'); //создали "скелет" //добавляем параметры SetParam(tsString,aUser,@Doc); SetParam(tsString,aPassword,@Doc); SendQuery(@Doc,aURL); //отправляем запрос if not Doc.IsEmptyDoc then //если документ записан корректно begin if Doc.DocumentElement.ChildNodes.FindNode('fault')<>nil then //есть сообщение об ошибке begin Result:=false; Error:=ParseErrors(@Doc).Strings[0]; end else Result:=true; end else Result:=false; end;
Если пришло сообщение об ошибке, то записываем сообщение в переменную Error. В данном случае структура содержит только одно сообщение об ошибке — поэтому я так легко прописал:
Error:=ParseErrors(@Doc).Strings[0];
Итак, две проверки сделаны и мы определили, что XML-RPC включен и работает исправно, а пользователь ввёл корректные данные логина и пароля и может работать с API WordPress. Что дальше? А дальше начинаем основную работу — получаем данные по комментариям в блоге.
2. Получаем данные о постах блога.
Итак, что предоставляет в наше распоряжение WordPress. Сначала сделаем кратки обзор методов из xmlrpc.php.
wp.getPostStatusList — выводит значения для статуса поста. По сути на выходе будем имеет четыре строки: ‘draft’, ‘pending’, ‘private’, ‘publish’.
Пока этот метод нам бесполезен.
blogger.getRecentPosts — эта функция уже из API Blogger, но поддерживается в WordPress. На выходе будем иметь последние посты блога, включая весь контент поста.
Можно использовать метод, НО работа программы будет замедлена так как придётся «тягать» по Сети пост целиком. А если попробуем получить список постов блога целиком, то, видимо придётся ложиться спать, не дождавшись результата. Следовательно — пока оставляем метод в стороне.
metaWeblog.getRecentPosts — аналогично предыдущему методу.
mt.getRecentPostTitles — метод из MovableType API. Судя по названию — то, что нам надо. Смотрим описание метода.
Метод возвращает список, содержащий заголовки постов блога. При этом контент в список не записывается.
Входные параметры:
- String blogid
- String username
- String password
- int numberOfPosts
blogid всегда равен 1 (см. описание в xmlrpc.php)
numberOfPosts — количество постов, которые необходимо вывести в список. Если параметр имеет значение больше, чем количество постов в блоге, то метод возвращает список всех постов.
Осталось узнать, что из себя представляет этот список. А на выходе мы будем иметь массив структур, включающий в себя:
- дату создания элемента
- userid
- postid
- заголовок.
Замечательно. Воспользуемся этим методом, а заодно научимся анализировать сложные структуры ответа.
Про создание запроса, думаю, писать не стоит. Процедура аналогична той, что рассмотрена выше. А на анализе ответа сервера остановимся подробно. Стем как выглядит тип struct (структура) мы познакомились при парсинге ответа, содержащего ошибку авторизации. Посмотрим, что из себя представляет массив.
Массивы не имеют названия и описываются тегом <array>. Он содержит один элемент <data> и один или несколько дочерних элементов <value>, где задаются данные. В качестве элементов массива могут выступать любые другие типы в произвольном порядке, а также другие массивы — что позволяет описывать многомерные массивы. Так же можно описывать массив структур. Например, массив из четырех элементо будет выглядеть так:
<array> <data> <value><i4>34</i4></value> <value><string>Привет, Мир!</string></value> <value><boolean>0</boolean></value> <value><i4>-34</i4></value> </data> </array>
У нас на выходе из метода mt.getRecentPostTitles
будет содержаться массив структур, причём одна структура — это информация по одному посту блога. Следовательно, чтение данных по постам блога можно условно разделить на следующие шаги:
- Выделяем из XML-документа все элементы value
- В каждом value читаем все элементы member
- каждый второй дочерний элемент у member — данные по посту, которые необходимо запомнить.
Начнем сразу с обработки ответа. Вводим новый тип данных:
type TBlogPost = packed record id: integer; user_id: integer; dateCreated: string;//пока будем хранить дату "как есть" title: string; end; type TBlogPosts = array of TBlogPost;
Обрабатываем ответ сервера.
[...] //т.к. в массиве всего 1 тэг data, то можно получить список элементов так Values:=Doc.DOMDocument.getElementsByTagName('data').item[0].childNodes; SetLength(Result,Values.length); for i:= 0 to Values.length-1 do begin Members:=Values[i].firstChild.childNodes;//получили все members для 1 value for j:=0 to Members.length - 1 do begin with Result[i]do case j of 0:dateCreated:=(Members[j].lastChild.firstChild as IDOMNodeEx).text; 1:user_id:=StrToInt((Members[j].lastChild.firstChild as IDOMNodeEx).text); 2:id:=StrToInt((Members[j].lastChild.firstChild as IDOMNodeEx).text); 3:title:=(Members[j].lastChild.firstChild as IDOMNodeEx).text; end; end; end; [...]
Соответственно, если получено сообщение об ошибке, то можно воспользоваться рассмотренной ранее функцией.
На сегодня всё. В следующий раз продолжим работу с API и попробуем получить все комментарии из блога.
По поводу Delphi XML-RPC на sourceforge.net оставил комментарий в предыдущем посте:
http://www.webdelphi.ru/2009/10/xml-rpc-v-delphi-pervoe-znakomstvo-s-wordpress-iznutri/
Так я уже ответил :) Спасибо за ссылку — посетителям пригодится 100%.
Я сам по-осени (2009) сильно озаботился темой XML-RPC в Delphi. Даже подключился к разработке Delphi XML-RPC на sourceforge.net. Я конвертировал старое CVS хранилище в SVN, создал себе бранч-ветку для экспериментов… (Добрый владелец проекта дал мне роль админа проекта, флаг в руки, барабан на шею и сославшись на полное отсутствие времени на свой проект — зеленый свет на доработку…) Частично адаптировал проект под Unicode (D2009, D2010), перевел на Indy10… А потом на работе навалилось работы и …. В общем не закончил я процесс до конца… Вот здесь я выложил «рыбу» может кому-нибудь пригодиться… dbxmlrpc Была задумка «поженить» Delphi DB-Aware и XML-RPC.… Подробнее »
Спасибо, Сергей за комментарий, вполне возможно, что проект оживет…Кстати, то, что пробовали скрестить БД — очень хорошо. Мне это пока не требуется, а вот один из читателей блога, думаю, должен заинтересоваться, если прочитает комментарии. Я пока только учусь работать с XML-RPC :) Почему-то после того как поработал в Lazarus очень сильно привязался к библиотеке Synapce. Может потому что всё работает просто и быстро, может просто отвык от Indy и сейчас как-то не хочется работать с этими компонентами. Вот и сейчас даже уже в Delphi 2010 «прикрутил» себе синапс и пробую соорудить что-нибудь на свой лад…Просто стало интересно между делом написать… Подробнее »
Маленькое стилистическое замечание: имхо, правильное название библиотеки SynapSe
[…] delphi xmlrpc ———————— Думаю, что многие из тех, кто читает мой блог — это студенты, учащиеся различных ВУЗов Москвы и других городом, поэтому, наверное им будет интересно про дипломы вуза москва. Так что, если Вас интересует документ об образовании, например дипломы вуза Москва, то проходите по приведенной выше ссылке. ———————— Понравилась статья? Тогда: […]
[…] Небольшие изменения в работе блога. WordPress. Работа с XML-RPC в Delphi. […]