Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2010年02月04日] [更新 2015年10月03日] Delphi サンプルプログラム集
920_Web API XML-RPC プロトコルによる郵便番号情報取得
動作確認等 Windows 7 U64(SP1) + Delphi XE(UP1) Pro + Indy 10.5.4
920_WebAPI_XM-RPC.zip [1,740 KB] 2015年10月03日版 (EXE 同梱)



  • 2015年10月03日
  • Windows 7 U64(SP1) + Delphi XE(UP1) Pro で動作確認




XML-RPC プロトコル


Web API は,ウェブサーバにあるデータやプログラム等を,クライアント等,別のコンピュータのプログラムから利用するための技術です.
プロトコルとしては,現在一番多く使われているのは REST ではないでしょうか.
ここでは,そのうちの 1 つである XML-RPC を使用して,郵便専門ネットのサイトで提供している郵便番号から住所を検索する Web API である WXML-RPC サービスをテストしてみました.XML-RPC では,取得したいリクエストの内容を記述した XML を POST コマンド (Indy では POSTメソッド) で送り,結果を XML で受け取ります.この XML の中から必要なデータを取出すことになります.XML-RPC を使用する Web API はあまり多くないようです.

以下のサンプルプログラムでは,XMLDoc ユニットの TXMLDocument コンポーネントを使用して XML を操作しています.また XML データバインドの機能を使用して XML のデータバインディングをしています.バインディングを行うと,XML の操作が Delphi のクラスのように扱えて便利です.
XML データバインドの機能は,Entreprise 版にはかなり前のバージョンからあったようですが,Pro 版では Delphi 2007 からあります.

XML-RPC ではリクエスト用の XML コードが必要ですが,そのひな形が Fabrice weblog のドサイトから Fabrice XML-RPC Client をダウンロードするとそのファイルに含まれています.

REST による郵便番号検索のサンプルは [921_WebAPI REST 郵便番号と住所検索] にあります.






01_郵便ネットで郵便番号から住所等の情報を取得 1


このサンプルでは,生成する XML の様子を見るために,ボタンを 3 つ配置して各々の動作を分け,ファイルに保存していますが,実際には必要ありません.1 つのボタンで全て実行できますし,保存も必要ありません.

本サンプルでは,XML コードを表示していますが,実際のアプリする際は,XML コードを表示する必要はありません.次項のサンプルでも,[921_WebAPI_REST による郵便番号と住所検索] のサンプルでも XML コードの表示はしていません.



図1
設計時画面
  • TIdHTTP (Indy) コンポーネント
    TXMLDocument コンポーネント
    を配置
  • TMemo は XML の表示用
  • XML コードを表示する必然性は全くない.単なる確認用
図2
リクエスト用の XML
  • これを [送信] する
図3
取得した XML
  • ここから値を取出す
  • 最初 (1 回目)はここでアプリを終了し,この XML を元にXMLデータバインディングを行った
  • XML コードを表示する必然性は全くない.単なる確認用
図4
XML からデータを取得

リスト1
リクエスト用のXML
<methodName> がリクエスト内容 (郵便専用ネットの説明ではメソッドのこと)
1010047 が対象となる郵便番号
<?xml version="1.0"?>
<methodCall>
  <methodName>yubin.fetchAddressByPostcode</methodName>
  <params>
    <param>
      <value>
        <string>1010047</string>
      </value>
    </param>
  </params>
</methodCall>


リスト2
サンプルプログラムのコード
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ComCtrls, IdBaseComponent, IdComponent, IdTCPConnection,
  IdTCPClient, IdHTTP, ExtCtrls, xmldom, XMLIntf, msxmldom, XMLDoc, Xmlxform;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    Button1: TButton;
    IdHTTP1: TIdHTTP;
    XMLDocument1: TXMLDocument;
    Memo1: TMemo;
    Button2: TButton;
    Edit1: TEdit;
    Button3: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
    FSendXMLFileName : string;
    FGetXMLFileName  : string;
  end;

var
  Form1: TForm1;

implementation

uses
  fetchAddressByPostcode,
  PostXML;

{$R *.dfm}

//=============================================================================
//  フォーム表示開始時の処理
//  TMemoのタブストップ単位を変更(単なる体裁上の問題)
//  すぐテスト可能なように郵便番号をセット
//=============================================================================
procedure TForm1.FormShow(Sender: TObject);
var
  LTabStops : array of DWORD;
begin
  SetLength(LTabStops, 1);
  LTabStops[0] := 4 * 2;
  Memo1.Perform(EM_SETTABSTOPS, 1, LPARAM(@LTabStops[0]));

  //調べる郵便番号
  Edit1.Text :='1010047';

  FSendXMLFileName := 'fetchAddressByPostcode.xml';
  FGetXMLFileName  := 'PostXML.xml';
end;

//=============================================================================
//  Edit1.Textの値を元に送信リクエスト用のXMLを生成
//  ひな形のXML内の郵便番号に相当する値をEdit1.Textの値にする
//
//  ひな形となるfetchAddressByPostcode.xmlは,以下のサイトで入手
//  http://www.fabrice.co.jp/blog/download/win32-gui
//  Fabrice XML-RPC Clientをダウンロードしたfbxmlrpc-111.zipに収められている
//  これをXMLバインディングしてfetchAddressByPostcode.pasを作成し,usesする
//  (注: XMLバインディングするとプロジェクトに追加される)
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  //POSTで取得したXMLをバインディングして作成したpasファイルにも同じクラス名
  //があるので,使用するファイル名を明示的に指定する
  LMethodCallList : fetchAddressByPostcode.IXMLMethodCallType;
  LValueList      : fetchAddressByPostcode.IXMLValueType;
begin
  //Memo1は取得したXMLコードの確認用
  Memo1.Clear;
  //XMLを読み込む
  XMLDocument1.LoadFromFile(FSendXMLFileName);

  //ルートノード
  LMethodCallList := fetchAddressByPostcode.GetmethodCall(XMLDocument1);
  //郵便番号のノード
  LValueList := LMethodCallList.Params.Param.Value;
  //値をセットして保存
  LValueList.String_ := (Trim(Edit1.Text));
  XMLDocument1.SaveToFile(FSendXMLFileName);

  //確認のためにTMemoに表示
  Memo1.Text := XMLDocument1.XML.Text;
end;

//=============================================================================
//  リクエストの送信
//  XML-RPCではPOSTコマンド(IndyではPOSTメソッド)を使用する
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LPostURL   : String;
  LXMLStream : TMemoryStream;
  LResStream : TMemoryStream;
begin
  //Memo1は取得したXMLコードの確認用に使用
  Memo1.Clear;

  //リクエストの送り先URL
  LPostURL   := 'http://yubin.senmon.net/service/xmlrpc/';
  //送るXMLのコードを格納したTMemroyStream
  LXMLStream := TMemoryStream.Create;
  //リクエストの結果を受取るTMemoryStream
  LResStream := TMemoryStream.Create;

  //リクエストの内容を書いたXMLを読み出す
  LXMLStream.LoadFromFile(FSendXMLFileName);

  Screen.Cursor := crHourGlass;
  try
    try
      //リクエスト送信
      IdHTTP1.Post(LPostURL, LXMLStream, LResStream );
      //結果が入ったTMemoryStreamを保存(中身はXML)
      LResStream.SaveToFile(FGetXMLFileName);
      //確認のためにXMLをTMemoに表示
      XMLDocument1.LoadFromFile(FGetXMLFileName);
      //Delphi2007以前(Unicode未対応)ではUTF-8のデコードが必要
      //Memo1.Text := UTF8Decode( XMLDocument1.XML.Text);
      Memo1.Text := XMLDocument1.XML.Text;
    except
      //エラー
    end;
  finally
    Screen.Cursor := crDefault;
    LXMLStream.Free;
    LResStream.Free;
  end;
  //最初にXMLデータを受取った時はここで終了(exit)
  //XMLバインディングを行いPostXML.pasを作成する
  //XMLバインディングはリクエストの内容が異なる場合は再作成する
  //このサンプルではyubin.fetchAddressByPostcodeというリクエストのみ扱っている
end;

//=============================================================================
//  取得したXMLから値を取出して表示
//
//  取得したXMLをXMLバインディングして作成したpasファイルを利用
//  XMLバインディングするとプロジェクトに追加される.これをusesする
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
  //リクエスト用のXMLをバインディングして作成したpasファイルにも同じクラス名
  //があるので,使用するファイル名を明示的に指定する
  LMethodCallList : PostXML.IXMLMethodResponseType;
  LValueList      : PostXML.IXMLValueType;
  LStruct         : PostXML.IXMLStructType;
  LMemberCount    : Integer;
  LName           : string;
  LValue          : string;
  i               : Integer;
begin
  //Memo1は取得したXMLコードの確認用
  Memo1.Clear;
  //XMLを読み込む
  XMLDocument1.LoadFromFile(FGetXMLFileName);

  //ルートノード
  LMethodCallList := PostXML.GetmethodResponse(XMLDocument1);
  //最初のタグ位置
  LValueList := LMethodCallList.Params.Param.Value;

  //データがあるタグ位置
  LStruct := LValueList.Array_.Data.Value.Struct;

  //の数
  LMemberCount := LStruct.Count;
  //順番に値を取得して表示
  //順番はWebAPIによって決まっているので,必要な値だけの取得も可能
  for i := 0 to LMemberCount-1 do begin
    LName  := LStruct.Member[i].Name;
    if LName = 'yid' then begin
      LValue := IntToStr(LStruct.Member[i].Value.Int);
    end else begin
      LValue := LStruct.Member[i].Value.String_;
    end;
    Memo1.Lines.Add(Format('%-18s  %-s', [LName, LValue]));
  end;
end;

end.




02_郵便ネットで郵便番号から住所等の情報を取得 2


今度はボタン 1 つで実行するコード例です.ここでは,リクエスト用の XML コードを,コード内で作成するようにしています.短いのでこの方法でも十分でしょう.
ポイントは,この文字列を TStringList 等ではなく,TXMLDocument の XML プロパティに代入していることです.TStringList を使用すると文字コードページのエンコードが必要になります.
XML は UTF-8 で書くことになっています.


図5
設計時画面
  • TIdHTTP (Indy) コンポーネント
    TXMLDocument コンポーネント
    を配置
  • TMemo は結果の表示用
図6
実行結果例

リスト3 
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ComCtrls, IdBaseComponent, IdComponent, IdTCPConnection,
  IdTCPClient, IdHTTP, ExtCtrls, xmldom, XMLIntf, msxmldom, XMLDoc, Xmlxform;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    Button1: TButton;
    IdHTTP1: TIdHTTP;
    XMLDocument1: TXMLDocument;
    Memo1: TMemo;
    Edit1: TEdit;
    procedure Button1Click(Sender: TObject);
    procedure FormShow(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses PostXML;

{$R *.dfm}

//=============================================================================
//  フォーム表示開始時の処理
//  すぐテスト可能なように郵便番号をセット
//=============================================================================
procedure TForm1.FormShow(Sender: TObject);
begin
  //調べる郵便番号
  Edit1.Text :='1010047';
end;

//=============================================================================
//  [実行]ボタン
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LPostURL    : String;
  LXMLStream : TMemoryStream;
  LResStream : TMemoryStream;

  LMethodCallList : PostXML.IXMLMethodResponseType;
  LValueList      : PostXML.IXMLValueType;
  LStruct         : PostXML.IXMLStructType;
  LMemberCount    : Integer;
  LName           : string;
  LValue          : string;
  i               : Integer;
begin
  Memo1.Clear;
  Screen.Cursor := crHourGlass;

  //リクエストの送り先URL
  LPostURL   := 'http://yubin.senmon.net/service/xmlrpc/';
  //送るXMLのコードを格納したTMemroyStream
  LXMLStream := TMemoryStream.Create;
  //リクエストの結果を受取るTMemoryStream
  LResStream := TMemoryStream.Create;

  XMLDocument1.XML.Clear;

  //リクエスト用のXMLを作成
  XMLDocument1.XML.Add('<?xml version="1.0"?>');
  XMLDocument1.XML.Add('<methodCall>');
  XMLDocument1.XML.Add('  <methodName>yubin.fetchAddressByPostcode</methodName>');
  XMLDocument1.XML.Add('  <params>');
  XMLDocument1.XML.Add('    <param>');
  XMLDocument1.XML.Add('      <value>');
  XMLDocument1.XML.Add('        <string>' + Trim(Edit1.Text) + '</string>');
  XMLDocument1.XML.Add('      </value>');
  XMLDocument1.XML.Add('    </param>');
  XMLDocument1.XML.Add('  </params>');
  XMLDocument1.XML.Add('</methodCall>');

  //送信用のStreamに保存
  XMLDocument1.Active := True;
  XMLDocument1.SaveToStream(LXMLStream);

  try
    try
      //リクエスト送信
      IdHTTP1.Post(LPostURL, LXMLStream, LResStream );
      //取得結果のXMLをXMLDocumentに入れる
      XMLDocument1.LoadFromStream(LResStream);
    except
      //エラー
    end;
  finally
    LXMLStream.Free;
    LResStream.Free;
  end;

  //ルートノード
  LMethodCallList := PostXML.GetmethodResponse(XMLDocument1);
  //最初のタグ位置
  LValueList := LMethodCallList.Params.Param.Value;

  //データがあるタグ位置
  LStruct := LValueList.Array_.Data.Value.Struct;

  //の数
  LMemberCount := LStruct.Count;
  //順番に値を取得して表示
  //順番はWebAPIによって決まっているので,必要な値だけの取得も可能
  for i := 0 to LMemberCount-1 do begin
    LName  := LStruct.Member[i].Name;
    if LName = 'yid' then begin
      LValue := IntToStr(LStruct.Member[i].Value.Int);
    end else begin
      LValue := LStruct.Member[i].Value.String_;
    end;
    Memo1.Lines.Add(Format('%-18s  %-s', [LName, LValue]));
  end;

  Screen.Cursor := crDefault;
end;

end.




03_XML データバインディングの手順


[ファイル] [新規作成] [その他] で新規作成のダイアログを表示します.添付ファイルにはバインディングした pas ファイルが含まれています.新たにバインディングしなくてもプロジェクトのテスト動作は可能です.


図7
[XML] で選択
図8
XML ファイルを選択
図9
今回の場合の例
図10
そのまま[次へ]
  • クラス名や変数名の変更が可能であるが,今回はこのまま
図11
そのまま [完了]
  • 保存の際にエラーが発生するようであれば [設定を保存しない] を選択する (実際には保存は不要)
図12
バインディング終了
  • これをそのまま保存
    保存先を確認するか,作成する EXE のあるフォルダ内がいいかも知れない
  • プロジェクトに自動で追加される
  • uses に追加して使用する