#include <stdio.h>
#include <curl/curl.h>
#include <stdlib.h>
#include <string.h>
#define FMT_WARN "Warning: %s\n"
#define FMT_WARN_US "Warning: unknown state %s\n"
#define FMT_ERR "Error: %s\n"
#define FMT_ERR_WITH "Error: %s (%d): %s\n"
#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 char *
unesc(const char *str)
{
int len;
char *str_unesc;
char *p;
len = strlen(str);
str_unesc = malloc(len + 1);
p = str_unesc;
while (*str) {
if (*str != '\\') {
*p = *str;
} else {
str++;
if (*str != 'u') {
*p = *str;
} else {
str++;
if (*str == '0') {
str++;
} else {
fprintf(stderr, FMT_WARN_US, "!(*str == '0') (1)");
return NULL;
}
if (*str == '0') {
str++;
} else {
fprintf(stderr, FMT_WARN_US, "!(*str == '0') (2)");
return NULL;
}
if (*str == '2') {
str++;
if (*str == '6') {
*p = '&';
} else {
fprintf(stderr, FMT_WARN_US, "!(*str == '6')");
return NULL;
}
} else if (*str == '3') {
str++;
if (*str == 'c') {
*p = '<';
} else if (*str == 'e') {
*p = '>';
} else {
fprintf(stderr, FMT_WARN_US, "!((*str == 'c') || (*str == 'e'))");
return NULL;
}
} else {
fprintf(stderr, FMT_WARN_US, "!((*str == '2') || (*str == '3'))");
return NULL;
}
}
}
str++;
p++;
}
*p = '\0';
return str_unesc;
}
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;
char *trans_unesc;
if (len) {
strncpy(result, buffer, len);
result[len] = '\0';
if (strcmp(result, result_null)) {
if (len < OFFSET) {
fprintf(stderr, FMT_WARN_US, "(len < OFFSET)");
return -1;
}
p = result + OFFSET;
p_e = p;
while (*p_e != '"' && *p_e) {
if (*p_e == '\\')
p_e++;
p_e++;
}
if (*p_e != '"') {
fprintf(stderr, FMT_WARN_US, "(*p_e != '\"')");
return -1;
}
len_trans = p_e - p;
if (len_trans) {
trans = malloc(len_trans + 1);
strncpy(trans, p, len_trans);
trans[len_trans] = '\0';
if ((trans_unesc = unesc(trans)) != NULL) {
printf("%s\n", trans_unesc);
} else {
fprintf(stderr, FMT_WARN, "unesc failed");
printf("%s\n", trans);
}
free(trans_unesc);
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, FMT_ERR, "need an arg (Russian phrase)");
return 1;
} else if (argc > 2) {
fprintf(stderr, FMT_ERR, "too many args. need just an arg");
return 1;
}
curl = curl_easy_init();
if(!curl) {
fprintf(stderr, FMT_ERR, "curl_easy_init() failed");
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, FMT_ERR_WITH, "curl_easy_perform() failed",
res, curl_easy_strerror(res));
return 1;
}
return 0;
}
- urie()で翻訳したいフレーズをURIエンコード
- libcurlで翻訳したいフレーズをGETで送信
- エスケープ文字(\)を考慮して24バイトのオフセットで結果をパース
- unesc()でエスケープ文字と特定の記号(\, &, <, >)をアンエスケープ処理
以前に書いたコードはエスケープや数値文字参照を考慮してませんでした。
「я сказал "да"」のようにダブルクオーテーションマーク(")が含まれるフレーズを翻訳すると下の結果が返ってくる。ダブルクオーテーションマークのエスケープに"\"が使われている模様。
{"sentences":[{"trans":"I said \"yes\"","orig":"я сказал \"да\"","translit":""}],"src":"ky"}
特定の記号を含むフレーズ、例えば「& < >」を翻訳すると下の結果が返ってくる。数値文字参照になっている模様。
{"sentences":[{"trans":"\u0026 \u003c\u003e","orig":"\u0026 \u003c \u003e","translit":""}],"src":"en"}
このような翻訳結果をエスケープ文字を考慮してパースした後、unesc()でアンエスケープ処理して対応しました。
コンパイル実際には
Makefileを書いてmakeで済ませてます。コンパイル前にlibcurlのdevパッケージをインストールしてます。
$ gcc -Wall -O2 `curl-config --cflags` `curl-config --libs` gtrans.c -o gtrans
テスト翻訳$ ./gtrans 'я сказал "да"'
I said "yes"
$ ./gtrans 'вы & я'
You & I
$ ./gtrans '<<<<<'
<<<<<
$ ./gtrans '>>>>>'
>>>>>
まずまず動作。ダブルクオーテーションや特定の記号を含んだフレーズも問題なく翻訳できるようになりました。