WordPress. Работа с XML-RPC в Delphi

Источник: webdelphi

Источник: webdelphi 

Сегодня решил немного продолжить тему работы с XML-RPC в WordPress. Как это обычно со мной бывает, идея родилась в момент чтения случайного блога, вздумалось взглянуть на работу с постами в блоге и, заодно, попробовать написать что-нибудь под свои нужды.

Естественно программу я сегодня не выложу, но некоторые выкладки, листинги и идеи в посте будут присутствовать.

Вкратце работу с XML-RPC я рассматривал в посте "XML-RPC в Delphi. Первое знакомство с WordPress изнутри." Сегодня попробуем продвинуться дальше в своей работе и использовать несколько взаимосвязанных методов для получения определенной информации из блога.

Конкретизируем цель на сегодня: необходимо получить данные по постам в блоге, используя доступные методы из API WordPress .

Для достижения поставленной цели нам понадобятся следующие модули Delphi: XMLIntf , xmldomXMLDoc и библиотека synapce или компонент Indy idHTTP (кому как угодно).

1. Тестируем соединение с блогом.

Полагаю, что первое, что следует сделать - это проверить корректность работы с блогом на предмет следующих возможных ошибок:

1.    В блоге отключена возможность использования XML-RPC

2.    Пользователь предоставил некорректные данные (url, логин или пароль).

Для проверки возможности работы с XML-RPC в блоге достаточно воспользоваться методом demo.sayHello . Если ответом будет строка "Hello" , значит всё в порядке и можно приступать к следующему шагу проверки. Для выполнения этой проверки нам потребуется выполнить три простенькие задачки:

  • сформировать правильный XML-документ
  • отправить запрос на сервер и получить ответ
  • проанализировать ответ

Формируем XML-документ, который должен выглядеть так:

<methodCall>

   <methodName>demo.sayHello</methodName>

     <params>

        <param>

           <value>

                <string>test</string>

           </value>

        </param>

     </params>

</methodCall>

Для этого воспользуемся интерфейсом IXMLDocument:

1

2

3

4

5

6

7

8

9

10

11

[...]

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-документ достаточно простой, то я позволил себе немного "похалявить" и последней строкой кода записал сразу все узлы и значение единственного параметра для нашего метода.
Теперь можно отправить документ на сервер и получить ответ:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

[...]

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 блога. Удачная отправка запроса свидетельствует только о том, что мы отправили запрос и получили ответ, а _что_ находится в ответе ошибка или нет - мы пока не знаем .
Чтобы пока не забивать голову лишними способами и методами парсинга ответа от сервера, предлагаю в данном случае остановиться на применении простой проверки:

1

2

3

4

5

[...]

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 и дальше, то есть смысл определиться со следующими моментами в работе:

1.    Сформировать "скелет" документа

2.    Записать в документ все необходимые параметры, учитывая типы данных

3.    Отправить запрос и получить ответ от сервера

4.    Проанализировать ответ и, если в ответе содержится сообщение об ошибке, то правильно его прочитать

Все эти четыре шага я сделал отдельными методами класса.  Под "скелетом" документа я понимаю следующее содержимое:

<methodCall>

 <methodName>MethodName</methodName>

 <params>    </params>

</methodCall>

То есть часть документа, содержащая имя метода и узел params без содержимого. Дальше на останется только правильно заполнить список параметров. Чем мы сейчас и займемся.

Всего в XML-RPC предусмотрено использование шести простых типов данных:

1.    int и i4 - целые числаinteger)

2.    double - дробные числа

3.    string - строки

4.    base64 - закодированная строка

5.    dateTime.iso8601 - дата/время

6.    boolean

Заводим новый тип данных:

1

TSimpleType = (tsInt, tsI4, tsString, tsDouble, tsDateTime, tsBase64, tsBoolean);

 

С помощью значений этого типа будем определять тэг для значения параметра.

Так как операции создания "скелета" документа и добавления параметров метода разнесены по разным функциям, то создадим ещё один вспомогательный тип данных:

1

PXMLDocument = ^IXMLDocument;

 

Теперь сам метод добавления параметра в документ:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

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 нумеруются от нуля, то каждый чётный элемент структуры - это код ошибки, а нечётный - текст ошибки. Следовательно метод обработки сообщений об ошибке может выглядеть так:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

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. Думаю, что при необходимости можно легко сделать, чтобы код и текст читались в разные списки или массивы. Нам пока это не требуется.

Отправку документа мы уже рассматривали, поэтому сразу привожу метод проверки данных на корректность:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

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. В данном случае структура содержит только одно сообщение об ошибке - поэтому я так легко прописал:

1

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
будет содержаться массив структур, причём одна структура - это информация по одному посту блога. Следовательно, чтение данных по постам блога можно условно разделить на следующие шаги:

1.    Выделяем из XML-документа все элементы value

2.    В каждом value читаем все элементы member

3.    каждый второй дочерний элемент у member - данные по посту, которые необходимо запомнить.

Начнем сразу с обработки ответа. Вводим новый тип данных:

1

2

3

4

5

6

7

8

9

10

type

TBlogPost = packed record

  id: integer;

  user_id: integer;

  dateCreated: string;//пока будем хранить дату "как есть"

  title: string;

end;

 

type

TBlogPosts = array of TBlogPost;

Обрабатываем ответ сервера.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

[...]

//т.к. в массиве всего 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 и попробуем получить все комментарии из блога.


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=23206