郵便番号から住所の入力支援を行う Ajax プログラム

第二回目は、 SQLite を使って郵便番号から住所を検索するプログラムで作成したデータベースを利用して、 C言語用CGIライブラリ:Cockatrice を使ったAjax のバックエンド・プログラムを実装します。

ここでは、郵便番号を入力すると自動的に住所を補完する <input> タグ用の支援機能プログラムを実装します。 オンラインストアなどで良く利用されている仕組みです。

要求
入力した郵便番号に該当する住所で <input> の項目を自動補完する
設計
  • 郵便番号と住所のデータをデータベースで管理する
  • パラメータ pc で郵便番号を受け取る CGI プログラムとする
  • 郵便番号は 7桁とする
  • 結果は XML 形式で返す
  • 検索に成功した場合のみ、郵便番号に対応した住所で自動補完する

JavaScript と CGI プログラムの連携

JavaScript プログラムはフロントエンドとして動作し、 「/cgi-bin/postal.cgi?pc=郵便番号」の URI を使用してして、サーバに郵便番号検索の問い合わせを行います。 CGI プログラムはバックエンドとして動作し、 JavaScript から送られた郵便番号をキーにデータベースを検索します。

CGI プログラムは、以下に示す XML で検索結果を JavaScript プログラムに返します。 JavaScript プログラムでは、<statu> タグの内容で応答を判断します。 <status> タグの内容が found であれば、 郵便番号に対応した住所が <address> タグに設定されています。 また、大口事業所名がある場合は、<name> に事業所名が設定されています。 それ以外は、<status> タグは failed になります。

全国版住所の応答例

  <?xml version="1.0" encoding="euc-jp" standalone="yes" ?>
  <postcode>
    <status>found</status>
    <address>東京都千代田区霞が関霞が関ビル(地階・階層不明)</address>
  </postcode>

事業所版住所の応答例

  <?xml version="1.0" encoding="euc-jp" standalone="yes" ?>
  <postcode>
    <status>found</status>
    <address>東京都千代田区霞が関1丁目3−2</address>
    <name>日本郵政公社</name>
  </postcode>

該当住所なし/エラー発生時の応答例

  <?xml version="1.0" encoding="euc-jp" standalone="yes" ?>
  <postcode>
    <status>failed</status>
  </postcode>

CGI プログラムの作成

SQLite データベースの検索プログラムは、 SQLite を使って郵便番号から住所を検索するプログラムの postal コマンドのソースコードで既に実装していますので、 ここではそれを元に lookup() 関数を実装します。 postal.c の lookup() との違いは、 SQLite データベースへのアクセスが Cockatrice の API 経由になっただけです。 郵便番号を検索する手順の変更は全くありません。


static int
lookup(cgi, sql, postcode, address, name)
       register CGI * cgi;
       const char * sql; /* 検索用 SQL */
       const char * postcode; /* 7桁の郵便番号 */
       char ** address; /* 住所が設定される */
       char ** name; /* 事業所名が設定される */
{
  int status = -1;
  char * s;

  asprintf(&s, sql, postcode);
  if (s) {
    if (cgi->database->query(s) == 0) {
      register int i;
      register int n;
      const char ** val;

      n = cgi->database->fetch(&val);
      if (n >= 3) {
        /* 住所を作成/設定 */
        asprintf(address, "%s%s%s",
                       val[0],  /* prefecture */
                       val[1],  /* city */
                       val[2]); /* address */
        if (*address != NULL) {
          status = 0;
        }
        /* 事業所名を設定 */
        if (n == 4 && name != NULL) {
          *name = strdup(val[3]);
        }
      }
      for (i = 0; i < n; i++) {
        free(val[i]);
      }
    }
  }

  return status;
}

完全版のソースコード postal.cgi.c は、以下の通りです。 マクロ定義 XML_STRING 中の encoding="euc-jp" の指定は、 郵便番号データベースを EUC-JP の文字コードで管理しているため必須です。 コンパイル後は、cgi-bin などの CGI プログラム用のディレクトリにインストールしてください。

HTML 用にコメントなどを一部削除しています。


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <cockatrice.h>

#define POSTCODE_DB "/usr/local/share/postal/postcode.db"
#define POSTCODE_LEN (7)
#define SQL_JAPAN \
  "SELECT prefecture,city,address FROM japan WHERE postcode=%s;"
#define SQL_OFFICE \
  "SELECT prefecture,city,address,name FROM office WHERE postcode=%s;"

#define XML_STRING \
  "<?xml version=\"1.0\" encoding=\"euc-jp\" standalone=\"yes\" ?>"

/* Local functions declaration */
static int postal(CGI * cgi);
static int lookup(CGI * cgi, const char * sql, const char * postcode,
                  char ** address, char ** name);

/* Functions definition */
static int
postal(cgi)
 register CGI * cgi;
{
  int status = -1;
  const char * postcode = cgi->param("pc");

  if (postcode != NULL) {
    static const char digits[] = "0123456789";
    size_t len;
    size_t pos;

    len = strlen(postcode);
    if (len != POSTCODE_LEN) {
      fprintf(stderr, "The length of %s is not 7.\n", postcode);
      return -1;
    }
    pos = strspn(postcode, digits);
    if (pos != POSTCODE_LEN) {
      fprintf(stderr, "%s has invalid character.\n", postcode);
      return -1;
    }

    if (cgi->database->open(POSTCODE_DB) == 0) {
      char * address;
      char * name = NULL;

      status = lookup(cgi, SQL_JAPAN, postcode, &address, NULL);
      if (status != 0) {
        status = lookup(cgi, SQL_OFFICE, postcode, &address, &name);
      }

      cgi->header("text/xml");
      fprintf(stdout, XML_STRING "<postcode>");
      if (status == 0) {
        fprintf(stdout,
                "<status>found</status>"
                "<address>%s</address>", address);
        free(address);
        if (name != NULL) {
          fprintf(stdout, "<name>%s</name>", name);
          free(name);
        }
      }
      else {
        fprintf(stdout, "<status>failed</status>");
      }
      fprintf(stdout, "</postcode>\n");

      status = cgi->database->close();
    }
  }

  return status;
}

static int
lookup(cgi, sql, postcode, address, name)
       register CGI * cgi;
       const char * sql;
       const char * postcode;
       char ** address;
       char ** name;
{
  int status = -1;
  char * s;

  asprintf(&s, sql, postcode);
  if (s) {
    if (cgi->database->query(s) == 0) {
      register int i;
      register int n;
      const char ** val;

      n = cgi->database->fetch(&val);
      if (n >= 3) {
        asprintf(address, "%s%s%s", val[0], val[1], val[2]);
        if (*address != NULL) {
          status = 0;
        }
        if (n == 4 && name != NULL) {
          *name = strdup(val[3]);
        }
      }
      for (i = 0; i < n; i++) {
        free(val[i]);
      }
    }
  }

  return status;
}

int
main(argc, argv)
     int argc;
     char **argv;
{
  CGI * cgi;
  int status;

  cgi = newCGI(CC_MODULE_SQLITE3);
  if (cgi == NULL) {
    fprintf(stderr, "Error: %d\n", cgi_errno);
    return -1;
  }
  status = postal(cgi);
  cgi->done();

  return status;
}

コンパイルは、次のように行います。

  % gcc -I/usr/local/include -L/usr/local/lib -o postal.cgi posta.cgil.c
    -lcockatrice -liconv -lmd

JavaScript プログラムの作成

プログラムの動作検証用に次のような <form> を作成します。

  <form action="#" method="post" enctype="multipart/form-data">
  <fieldset>
  <legend>配送先</legend>
  <p>
  <label for="postcode">郵便番号</label>
  <input type="text" name="postcode" value="" size="7" maxlength="7"
      id="postcode" onchange="getAddress()" />
  </p>
  <p>
  <label for="address">住所</label>
  <input type="text" name="address" value="" size="50" maxlength="100"
      id="address" />
  </p>
  </fieldset>
  </form>

JavaScript プログラム(postal.js)は、 以下のようになります。


function handleHttpResponse() {
  if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
    var xmlDoc = xmlHttp.responseXML;
    if (xmlDoc.documentElement) {
      var status = xmlDoc.getElementsByTagName('status').item(0).firstChild;
      if (status.data == 'found') {
        var address = xmlDoc.getElementsByTagName('address').item(0).firstChild;
        document.getElementById('address').value = address.data;
      }
    }
    else {
      document.getElementById('address').value = '';
    }
  }
}

function getAddress() {
  var postcode = document.getElementById('postcode').value;
  xmlHttp.open('GET', '/cgi-bin/postal.cgi?pc=' + escape(postcode), true);
  xmlHttp.onreadystatechange = handleHttpResponse;
  xmlHttp.send(null);
}

var xmlHttp = window.XMLHttpRequest ? new XMLHttpRequest() : (function() {
  try       { return new ActiveXObject('Msxml2.XMLHTTP'); }
  catch (e) { return new ActiveXObject('Microsoft.XMLHTTP'); }
})();

上記の JavaScript を利用する場合は、 上記コードを対象とするウェブページに組み込むか、 外部ファイルとして読み込むようにします。

xmlHttp 変数が Ajax と呼ばれる仕組みを提供するために利用されます。 getAddress() が郵便番号を入力し、 フォーカスの移動が起きたときに呼び出される関数です。 この関数がバックエンドの postal.cgi に検索要求を送ります。

handleHttpResponse() は、postal.cgi の結果を受信して処理する関数です。 XML 形式の応答を解析して、 <status> タグの内容が found の場合のみ、 住所を自動補完します。

動作検証

では、実際に作成したプログラムがどのように動作するのかを見てみます。 郵便番号の蘭に7桁の数値を入力してください。 フォーカスが住所に移動すると、郵便番号に応じた住所が自動的に補完されます。

配送先

今回、<input> タグへの入力支援機能で作成した JavaScript/CGI プログラムは、 Ajax アプリケーションの基本的な枠組だけの実装です。 ちょっとの改良で他のアプリケーションにも利用できると思います。

Google