VC++のヘルプで調べてみるとJISをS-JISに変換する関数として
_mbcjistojmsというのがあります。これは、マイクロソフト固有の関数で
他社のコンパイラをお使いの方は使用できません。(多分別な名前で同様の
働きをする関数があるのではないかと思いますが・・)
cの上位8ビットは変換する文字の先行バイト、下位8ビットには後続バイト を指定します。#include <mbstring.h> unsigned int _mbcjistojms(unsigned int c);
戻り値が変換後の文字となります。変換できないときは0を返します。
JISコードは先行、後続バイトともに0x21から0x7eの範囲にあります。 この範囲外のコードを指定すると当然失敗します。
この関数を利用して、片っ端から変換していけばJISで書かれた文書を簡単にS-JISに変換できる! と思われるかもしれませんが、そうは問屋が卸しません。(実験してみるとすぐにわかります。)
実はJISでは2バイト文字(列)が始まる前と、後で3バイトの特別な合図があります。 正確には2バイト文字が始まるときと、ASCII文字が始まる時の合図です。
つまりこの合図ではさまれた間にJISの2バイト文字(列)が来ます。始まりの合図 0x1b, 0x24, 0x42 終わりの合図 0x1b, 0x28, 0x42
この他に0x1b, 0x28, 0x4aとか 0x1b, 0x24, 0x40という合図もありますが、 ほとんど使われません。
では、具体的に変換のプログラム方を考えてみます。 いろいろスマートな方法もあると思いますが、ここではわかりやすさを重視して かなりダサい方法を取ります。
JISで書かれた文字列を格納している配列(szOrg)と変換後の文字列を格納する配列(szResult)を
用意します。
szOrgの先頭文字から1バイトずつヌル文字が来るまで順番に検査をしていきます。
始まりの合図を見つけたら変換フラグをTRUEにします。
終わりの合図を見つけたら変換フラグをFALSEにします。
「合図」の部分は読み飛ばします。
変換フラグがTRUEでなおかつコードが[0x21, 0x7e]の範囲にあれば、次のコード
とあわせてS-JISに変換します。szOrgのiバイト目で変換が起こった場合次に調べるコードは
i+2バイト目となります
変換したコードをszResultに書き込みます。(2バイト)
変換条件に合わないものはそのままszResultに1バイトずつ書き込みます。
さて、これで大まかな変換はできます。しかし、変換後の文字列を エディットコントロールなどに表示する場合、少し困ることがあります。 JISでは改行は「\n」で表されますが、エディットコントロールなどでは「\r\n」 にしなくてはいけません。この辺は試行錯誤の連続です。
では、プログラムを見ていくことにしましょう。メニューの「開く」で S-JISに変換したいファイルを選択します。そうするとクライアント領域に 貼り付けてあるエディットコントロールにファイルの中身がS-JISに変換してから表示されます。
これは、どうということもないメニューのリソース・スクリプトです。// conv01.rcの一部 ///////////////////////////////////////////////////////////////////////////// // // Menu // MYMENU MENU DISCARDABLE BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM "開く(&O)...", IDM_OPEN END END
mbstring.hをインクルードするのを忘れないでください。// conv01.cpp #ifndef STRICT #define STRICT #endif #include <windows.h> #include <windowsx.h> #include <mbstring.h> #include "resource.h" #define ID_EDIT 100 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); ATOM InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); int MyOpen(HWND, char *, HWND); void MyJisToSJis(char *, char *); char szClassName[] = "conv01"; //ウィンドウクラス HINSTANCE hInst;
いつもと同じです。int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow) { MSG msg; hInst = hCurInst; if (!InitApp(hCurInst)) return FALSE; if (!InitInstance(hCurInst, nCmdShow)) return FALSE; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } //ウィンドウ・クラスの登録 ATOM InitApp(HINSTANCE hInst) { WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; //プロシージャ名 wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst;//インスタンス wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszMenuName = "MYMENU"; //メニュー名 wc.lpszClassName = (LPCSTR)szClassName; wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); return (RegisterClassEx(&wc)); } //ウィンドウの生成 BOOL InitInstance(HINSTANCE hInst, int nCmdShow) { HWND hWnd; hWnd = CreateWindow(szClassName, "猫でもわかるJIS->SJIS変換", //タイトルバーにこの名前が表示されます WS_OVERLAPPEDWINDOW, //ウィンドウの種類 CW_USEDEFAULT, //X座標 CW_USEDEFAULT, //Y座標 CW_USEDEFAULT, //幅 CW_USEDEFAULT, //高さ NULL, //親ウィンドウのハンドル、親を作るときはNULL NULL, //メニューハンドル、クラスメニューを使うときはNULL hInst, //インスタンスハンドル NULL); if (!hWnd) return FALSE; ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; }
ウィンドウ・プロシージャです。//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id; static HWND hEdit; RECT rc; char szFileName[MAX_PATH]=""; switch (msg) { case WM_CREATE: hEdit = CreateWindowEx(0, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_WANTRETURN | WS_VSCROLL | WS_HSCROLL | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_MULTILINE, 0, 0, 0, 0, hWnd, (HMENU)ID_EDIT, hInst, NULL); break; case WM_SIZE: GetClientRect(hWnd, &rc); MoveWindow(hEdit, 0, 0, rc.right - rc.left, rc.bottom - rc.top, TRUE); break; case WM_COMMAND: switch (LOWORD(wp)) { case IDM_OPEN: MyOpen(hWnd, szFileName, hEdit); break; } break; case WM_CLOSE: id = MessageBox(hWnd, "終了してもよいですか", "終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { DestroyWindow(hEdit); DestroyWindow(hWnd); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0; }
WM_CREATEが来たらエディットコントロールを作ります。
WM_SIZEが来たらエディットコントロールの大きさを調整して親ウィンドウの クライアント領域にぴったりあわせます。
メニューのIDM_OPENが選択されたら、自作関数MyOpenを呼び出します。
終了時に一応エディットコントロールを破棄します。(親が破棄されたら子供も 自動的に破棄されるのでこの部分は省略しても問題ありません)
GetOpenFileName関数やらOPENFILENAME構造体について忘れてしまった人は 第73章を参照してください。int MyOpen(HWND hWnd, char *szFileName, HWND hEdit) { DWORD dwSize; OPENFILENAME ofn; HANDLE hFile; DWORD dwAccBytes = 0; char szTitle[64], *szTitle_org = "猫でもわかるJIS->SJIS変換[%s]"; char *lpszStr, *lpszResult; char szFile[MAX_PATH]; //オープンするJIS文書 HANDLE hStr, hResult; memset(&ofn, 0, sizeof(OPENFILENAME)); ofn.lStructSize = sizeof(OPENFILENAME); ofn.hwndOwner = hWnd; ofn.lpstrFilter = "text(*.txt)\0*.txt\0All files(*.*)\0*.*\0\0"; ofn.lpstrFile = szFileName; ofn.lpstrFileTitle = szFile; ofn.nMaxFile = MAX_PATH; ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; ofn.lpstrDefExt = "txt"; ofn.lpstrTitle = "JISファイルを開く"; if(GetOpenFileName(&ofn) == 0) return -1; hFile = CreateFile(szFileName, GENERIC_READ, 0, 0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); dwSize = GetFileSize(hFile, NULL); if (dwSize >= 1024 * 64) { MessageBox(hWnd, "ファイルサイズが大きすぎます", "Too Big File!", MB_OK); CloseHandle(hFile); dwSize = 0; return -1; } //最後にヌル文字を付け加えるのでファイルサイズより1バイト多く確保 hStr = GlobalAlloc(GHND, dwSize + 1); if (hStr == NULL) { MessageBox(hWnd, "メモリ確保に失敗しました", "Error", MB_OK); return -2; } lpszStr = (char *)GlobalLock(hStr); //変換後の文字列を格納するメモリは少し余分に確保しておくと安全 hResult = GlobalAlloc(GHND, dwSize + 1024); if (hResult == NULL) { MessageBox(hWnd, "メモリ確保に失敗しました", "Error", MB_OK); return -2; } lpszResult = (char *)GlobalLock(hResult); ReadFile(hFile, lpszStr, dwSize, &dwAccBytes, NULL); lpszStr[dwAccBytes] = '\0'; MyJisToSJis(lpszStr, lpszResult); Edit_SetText(hEdit, lpszResult); GlobalUnlock(hStr); GlobalUnlock(hResult); GlobalFree(hStr); GlobalFree(hResult); wsprintf(szTitle, szTitle_org, szFile); SetWindowText(hWnd, szTitle); CloseHandle(hFile); return 0; }
メモリの動的確保について忘れてしまった人は 第82章を参照してください。
さて、ファイルの内容をlpszStrに読み込んだらMyJisToSJis関数を 呼び出して変換を行います。あとは、ファイル名をタイトルバーに表示したり、 エディットコントロールに変換後の内容を表示するだけです。
JISをS-JISに変換する関数です。void MyJisToSJis(char *lpOrg, char *lpDest) { int i = 0, iR = 0, c; BOOL bSTART = FALSE;//JIS->SJIS変換が必要かどうか while (1) { if (lpOrg[i] == 0x1b && lpOrg[i + 1] == 0x24 && lpOrg[i + 2] == 0x42) { bSTART = TRUE; i += 3; continue; } if (lpOrg[i] == 0x1b && lpOrg[i+ 1] == 0x28 && lpOrg[i + 2] == 0x42) { bSTART = FALSE; i += 3; continue; } if (lpOrg[i] >= 0x21 && lpOrg[i] <= 0x7e && bSTART) { c = MAKEWORD(lpOrg[i + 1], lpOrg[i]); lpDest[iR] = (char)HIBYTE(_mbcjistojms(c)); lpDest[iR + 1] = (char)LOBYTE(_mbcjistojms(c)); i += 2; iR += 2; continue; } if (i == 0 && lpOrg[0] == '\n') { lpDest[iR] = '\r'; lpDest[iR + 1] = '\n'; i++; iR += 2; continue; } if (lpOrg[i] == '\n' && lpOrg[i - 1] != '\r') { lpDest[iR] = '\r'; lpDest[iR + 1] = '\n'; i++; iR += 2; continue; } if (lpOrg[i] == '\0') { lpDest[iR] = lpOrg[i]; break; } lpDest[iR] = lpOrg[i]; iR++; i++; } return; }
最初の説明で大体わかると思いますが、変換の条件としてbSTARTがTRUEなだけではなく 安全のため、わざわざlpOrg[i]が0x21から0x7eの範囲にあることを付け加えています。
また「\n」を見つけたら、その1バイト前が「\r」でないことを確かめてから 「\n」を「\r\n」に変換しています。(lpOrgの先頭が「\n」の場合もあるので注意)
lpOrgに「\0」を見つけたら永久ループを脱出します。
いずれの条件にも当てはまらない場合はそのままコピーします。
ifの条件が複雑にならないようにcontinueを使っていることに注意してください。
これで、たいていのJISは大丈夫ですが、完璧ではありません。 Macから送られてきたメールをそのままテキストファイルに保存して このプログラムでJISに変換すると行末にごみが表示されます。
Update 01/Oct/1999 By Y.Kumei