Сегодня решил немного продолжить тему работы с 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 и попробуем получить все комментарии из блога.
Пока я пишу следующий пост, Вы можете прогуляться и купить ковер, если у Вас его ещё нет :)
CCleaner - это отличная программа для очистки Вашего компьютера от ненужного хлама. Можете ccleaner скачать бесплатно и забыть о том, что такое ручная уборка жёсткого диска ПК.
Интересный блог http://vsesmispb.livejournal.com/. Первое, что бросается в глаза - риторический вопрос от автора "Нахрена козе баян" :) Ответ на этот вопрос найдёт только самый внимательный читатель:)
----------------------
| Делись! | Загружай! | Плюсуй! |
| | |









18 Янв 2010 в 7:23 пп
По поводу Delphi XML-RPC на sourceforge.net оставил комментарий в предыдущем посте:
http://www.webdelphi.ru/2009/10/xml-rpc-v-delphi-pervoe-znakomstvo-s-wordpress-iznutri/
18 Янв 2010 в 7:30 пп
Так я уже ответил :) Спасибо за ссылку — посетителям пригодится 100%.
18 Янв 2010 в 8:10 пп
Я сам по-осени (2009) сильно озаботился темой XML-RPC в Delphi. Даже подключился к разработке Delphi XML-RPC на sourceforge.net. Я конвертировал старое CVS хранилище в SVN, создал себе бранч-ветку для экспериментов…
(Добрый владелец проекта дал мне роль админа проекта, флаг в руки, барабан на шею и сославшись на полное отсутствие времени на свой проект — зеленый свет на доработку…)
Частично адаптировал проект под Unicode (D2009, D2010), перевел на Indy10… А потом на работе навалилось работы и …. В общем не закончил я процесс до конца…
Вот здесь я выложил «рыбу» может кому-нибудь пригодиться… dbxmlrpc
Была задумка «поженить» Delphi DB-Aware и XML-RPC. То есть хотелось реализовать прозрачную передачу данных стандартными для Delphi средствами (dataset, datasource, dbgrid и т.д.) по протоколу XML-RPC.
Хватило только на подготовку платформы… (D2009, D2010, Indy10). До работы с DB я так и не дошел. Пока не до этого, к сожалению…
Эх… Жаль не знал про Ваш сайт раньше…
Вот как-то так…
Сергей.
18 Янв 2010 в 8:24 пп
Спасибо, Сергей за комментарий, вполне возможно, что проект оживет…Кстати, то, что пробовали скрестить БД — очень хорошо. Мне это пока не требуется, а вот один из читателей блога, думаю, должен заинтересоваться, если прочитает комментарии. Я пока только учусь работать с XML-RPC :) Почему-то после того как поработал в Lazarus очень сильно привязался к библиотеке Synapce. Может потому что всё работает просто и быстро, может просто отвык от Indy и сейчас как-то не хочется работать с этими компонентами. Вот и сейчас даже уже в Delphi 2010 «прикрутил» себе синапс и пробую соорудить что-нибудь на свой лад…Просто стало интересно между делом написать что-нибудь для работы именно с WordPress. Что получится пока не знаю — может клиент для блога, а может просто библиотекой выложу все на радость людям :)
19 Янв 2010 в 11:18 дп
Маленькое стилистическое замечание: имхо, правильное название библиотеки SynapSe