2009年12月20日日曜日

(Code: c) gtrans

Google Translate を利用して露英翻訳するCのコードです。以前書いたシェルスクリプトをCでリライト。
#include <stdio.h>
#include <curl/curl.h>
#include <stdlib.h>
#include <string.h>

#define OFFSET 24
static const char *url = "http://translate.google.com/translate_a/t";
static const char *data = "client=t&sl=ru&tl=en&text=";
static const char *useragent = "User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0)";
static const char *result_null = "{\"src\":\"el\"}";


static char *
urie(const char *text)
{
  char *text_urie;
  char *p;

  text_urie = malloc(strlen(text) * 3 + 1);
  p = text_urie;
  while (*text) {
    snprintf(p, 4, "%%%02X", (unsigned char) *text);
    text++;
    p += 3;
  }
  *p = '\0';

  return text_urie;
}

static size_t
write_data(void *buffer, size_t size, size_t nmemb, void *userp)
{
  int len = size * nmemb;
  char result[len + 1];
  char *p, *p_e;
  int len_trans;
  char *trans;

  if (len) {
    strncpy(result, buffer, len);
    result[len] = '\0';
    if (strcmp(result, result_null)) {
      if (len < OFFSET) {
fprintf(stderr, "Warning: unknown state (len < OFFSET)\n");
return -1;
      }
      p = result + OFFSET;
      p_e = p;
      while (*p_e != '"' && *p_e)
p_e++;
      if (*p_e != '"') {
fprintf(stderr, "Warning: unknown state (*p_e != '\"')\n");
return -1;
      }
      len_trans = p_e - p;
      if (len_trans) {
trans = malloc(len_trans + 1);
strncpy(trans, p, len_trans);
trans[len_trans] = '\0';
printf("%s\n", trans);
free(trans);
      }
    }
  }

  return len;
}


int
main(int argc, char **argv)
{
  CURL *curl;
  char *text_urie;
  int size_data_all, size_url_all;
  char *data_all, *url_all;
  CURLcode res;
  struct curl_slist *headers = NULL;

  if (argc < 2) {
    fprintf(stderr, "Error: need an arg (Russian phrase)\n");
    return 1;
  } else if (argc > 2) {
    fprintf(stderr, "Error: too many args. need just an arg (Russian phrase)\n");
    return 1;
  }    

  curl = curl_easy_init();
  if(!curl) {
    fprintf(stderr, "Error: curl_easy_init() failed\n");
    return 1;
  }

  text_urie = urie(argv[1]);

  size_data_all = strlen(data) + strlen(text_urie) + 1;
  data_all = malloc(size_data_all);
  strcpy(data_all, data);
  strcat(data_all, text_urie);
  free(text_urie);

  size_url_all = strlen(url) + 1 + size_data_all;
  url_all = malloc(size_url_all);
  snprintf(url_all, size_url_all, "%s?%s", url, data_all);
  free(data_all);

  curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10);
  curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
  curl_easy_setopt(curl, CURLOPT_URL, url_all);
  headers = curl_slist_append(headers, useragent);
  curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data); 

  res = curl_easy_perform(curl);

  free(url_all);
  curl_slist_free_all(headers);
  curl_easy_cleanup(curl);

  if(res) {
    fprintf(stderr, "Error: curl_easy_perform() failed (%d): %s\n",
    res, curl_easy_strerror(res));
    return 1;
  }

  return 0;
}

  • urie()で翻訳したいフレーズをURIエンコード

  • libcurlで翻訳したいフレーズをGETで送信

  • 24バイトのオフセットで結果をパース

UserAgentがcurlだと拒絶される、空だと翻訳結果がKOI8-Rで返ってきてUTF-8の環境でパースしづらくなるのでIEに偽装しています。UAがIEやFirefoxだとUTF-8で結果が返ってくるみたい。


コンパイル

実際にはMakefileを書いてmakeで済ませてます。コンパイル前にlibcurlのdevパッケージをインストールしてます。
$ gcc -Wall -O2 `curl-config --cflags`  `curl-config --libs`  gtrans.c   -o gtrans


テスト翻訳
$ ./gtrans 'здравств'
hello

$ ./gtrans 'я из Японии'
I'm from Japan

$ ./gtrans 'только хорошие умирают молодыми'
only the good die young

$ ./gtrans 'through ascii phrase'
through ascii phrase

$ ./gtrans ' '
(出力なし)

$ ./gtrans ''
(出力なし)

$ ./gtrans '誤って日本語を入力'
誤っ て 日本語 を 入力

$ ./gtrans `echo "えすじす" | iconv -t SJIS-WIN`
(文字化け)
まずまずシェルスクリプト版と同じ動作。関数化してチャットロガーに仕込む下準備が整った!Cでlibcurlをはじめて使ったのだけどすごく便利でした。


環境

OS: Linux
debian-lenny
libcurl-7.18.2