はじめに
近いうちに Visual Studio Express 2017 がサポート期限がくるので LLVM clang への引っ越しを考えていますが1,問題は LLVM clang の場合,ソースコードは UTF-8 一択なのが困り事です2。
会社で使っている ARM clang が shift_jis で書かれた日本語コメントを許容するので,Windows 用の clang-cl にも shift_jis を通す隠しオプションとか外部プリプロセッサとかユーザ拡張とか無いものか?とアレコレ調査していたのですが,残念ながら簡単には実現できそうにありません。
ということで,大量に存在する shift_jis のソースコードを UTF-8 に変換するツールを作成することにしました。UNIX の文字コード変換ツールにちなんで iconv とします。
仕様案(お品書き)
- 開発言語は Windows ならデフォルトで使える WSH / JScript とします。文字コード変換には ADODB.Stream を使います。
- コマンドラインオプションは下記とします。入出力ファイル名とオプションの順序は自由です。
iconv(.js) [/F:入力コード] [/T:出力コード] [入力ファイル名] [出力ファイル名] - 入出力ファイル名はいずれも必須とします。リダイレクトもパイプもできません。これは ADODB.Stream を使う制約です。
- 入力コードのデフォルトは自動検出とします。これは ADODB.Stream にそういう機能があるからです。一方,出力コードのデフォルトは shift_jis とします。
-
UNIX の iconv コマンドには「変換できなかった文字を出力しない」という
-cオプションがありますが,これもサポートしません。これも ADODB.Stream を使う制約です。 -
UNIX の iconv コマンドには「対応しているコードを表示する」という
-lオプションがありますが,これもサポートしません。
実装コード
まずは,実装コードを以下に示します。
//------------------------------------------------------------------------------
// グローバル変数
//------------------------------------------------------------------------------
var input_charset = "_autodetect_all"; // 入力ファイルの文字コード
var output_charset = "shift_jis"; // 出力ファイルの文字コード
//------------------------------------------------------------------------------
// メイン関数の呼び出し
//------------------------------------------------------------------------------
var opts = WScript.Arguments.Named;
var args = WScript.Arguments.Unnamed;
var ret = main(args, opts);
try {
WScript.Quit(ret);
} catch(e) {
/* 何もしない */
}
//------------------------------------------------------------------------------
// ヘルプメッセージ
//------------------------------------------------------------------------------
function usage() {
WScript.StdErr.WriteLine("文字コードを変換します。");
WScript.StdErr.WriteLine("");
WScript.StdErr.WriteLine("iconv(.js) (オプション) [入力ファイル] [出力ファイル]");
WScript.StdErr.WriteLine("");
WScript.StdErr.WriteLine("<オプション>");
WScript.StdErr.WriteLine("/F:[*] 入力ファイルの文字コードを指定します。");
WScript.StdErr.WriteLine(" デフォルトは " + input_charset + " です。");
WScript.StdErr.WriteLine("/T:[*] 出力ファイルの文字コードを指定します。");
WScript.StdErr.WriteLine(" デフォルトは " + output_charset + " です。");
return -1;
}
//------------------------------------------------------------------------------
// ADODB.Stream ラッパークラス
//------------------------------------------------------------------------------
function ADODBSTREAM() {
this.adTypeBinary = 1;
this.adTypeText = 2;
this.adReadAll = -1;
this.adReadLine = -2;
this.adWriteChar = 0;
this.adWriteLine = 1;
this.adSaveCreateNotExist = 1;
this.adSaveCreateOverWrite = 2;
this.stream = WScript.CreateObject("ADODB.Stream");
this.ReadAllText= function(filename, charset) {
this.stream.Type = this.adTypeText;
this.stream.CharSet = charset;
this.stream.Open();
this.stream.LoadFromFile(filename);
var buf = this.stream.ReadText(this.adReadAll);
this.stream.Close();
return buf;
};
this.WriteAllText= function(filename, charset, buf, overwrite) {
this.stream.Type = this.adTypeText;
this.stream.CharSet = charset;
this.stream.Open();
this.stream.WriteText(buf, this.adWriteChar);
if(overwrite)
this.stream.SaveToFile(filename, this.adSaveCreateOverWrite);
else
this.stream.SaveToFile(filename, this.adSaveCreateNotExist);
this.stream.Close();
};
}
//------------------------------------------------------------------------------
// メイン関数
//------------------------------------------------------------------------------
function main(args, opts) {
//--------------------------------------------------------------------------
// コマンドライン解析
//--------------------------------------------------------------------------
if(args.Count < 2) return usage();
var input_filename = args(0); // 入力ファイル名
var output_filename = args(1); // 出力ファイル名
if(opts.Exists("F")) input_charset = opts("F");
if(opts.Exists("T")) output_charset = opts("T");
//--------------------------------------------------------------------------
// 入力ファイルの存在チェック
//--------------------------------------------------------------------------
var fso = WScript.CreateObject("Scripting.FileSystemObject");
if(!fso.FileExists(input_filename)) {
WScript.StdErr.WriteLine("入力ファイル " + input_filename + " は存在しません!!");
return -1;
}
//--------------------------------------------------------------------------
// ファイルの文字コード変換
//--------------------------------------------------------------------------
var ads = new ADODBSTREAM();
var buf = ads.ReadAllText(input_filename, input_charset);
ads.WriteAllText(output_filename, output_charset, buf, true);
return 0;
}
解説
ADODB.Stream の薄いラッパーなので,あまり内容がありませんが,一応,解説します。
ファイルを読み込むとき,書き込むときのいずれもまず ADODB.Stream のラッパークラス ADODBSTREAM のインスタンスを作成します。
var ads = new ADODBSTREAM();
ファイルを読み込むときは,ファイル名 input_filename と文字コード input_charset を指定すると,一括で読み込みます。メソッド名 ReadAllText は .NET の同名メソッドにちなみました。
var buf = ads.ReadAllText(input_filename, input_charset);
ファイルを書き込むときは,ファイル名 output_filename と文字コード output_charset,書き込む文字列 buf,そして上書き許可オプション(上書き許可時は true とする)を指定すると一括で書き込みます。メソッド名 WriteAllText も .NET の同名メソッドにちなんでいます。
ads.WriteAllText(output_filename, output_charset, buf, true);
使い方
引数なし,あるいは必要な引数(入力ファイル名と出力ファイル名の2つ)が不足している場合,ヘルプメッセージを表示します。筆者の作るツールは基本そうしています。
c:\Qiita>iconv
文字コードを変換します。
iconv(.js) (オプション) [入力ファイル] [出力ファイル]
<オプション>
/F:[*] 入力ファイルの文字コードを指定します。
デフォルトは _autodetect_all です。
/T:[*] 出力ファイルの文字コードを指定します。
デフォルトは shift_jis です。
文字コードを変換する場合は下記のように入力と出力の文字コード両方を指定したほうが良いと思います。一応,自動判別の機能はありますが,入力ファイルのサイズが小さいと誤判定してしまう可能性が否定できないからです。
c:\>Qiita>iconv sample-sjis.txt sample-utf8.txt /f:shift_jis /o:utf-8
サンプルテキスト
サンプルテキストを以下に示します。実際は,下記テキストの内容を各種文字コードでエンコードしたテキストファイルを用意しました。
、。,.・:
~∥|…‥‘’
ABCDEFGHIJKLMNOPQRSTUVWXYZ
abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ
abcdefghijklmnopqrstuvwxyz
自動検出機能
上記のサンプルテキストを用いて自動検出機能の性能を調べました。自動検出には _autodetect と _autodetect_all の二種類ありますが,いずれを使っても shift_jis,jis,euc-jp の判別は完璧でした。_autodetect_all を使うと BOM があれば UTF-8,UTF-16LE,UTF-16BE の判別も可能になります。BOM がない場合は UTF-8 のみ判別可能でした。
ということで,自動判別には _autodetect_all を使うことにしました。
| 文字コード | _autodetect | _autodetect_all |
|---|---|---|
| shift_jis | ○ | ○ |
| iso-2022-jp (jis) | ○ | ○ |
| euc-jp | ○ | ○ |
| UTF-8 (BOM無) | × | ○ |
| UTF-8 (BOM有) | × | ○ |
| UTF-16LE (BOM無) | × | × |
| UTF-16LE (BOM有) | × | ○ |
| UTF-16BE (BOM無) | × | × |
| UTF-16BE (BOM有) | × | ○ |
注意事項
出力コードを UTF-8, UTF-16LE, UTF-16BE にすると,出力ファイルの先頭には必ず BOM が付きます。BOM 無しで出力する技術3も存在するようですが,clang-cl コンパイラが BOM を通してくれるので,今のところそのままにしています。
今後の課題
ご覧の通り,ADODB.Stream の薄いラッパーに過ぎないので,文字コードを指定する文字列をどのように指定すれば良いのか良く分からないことです。たとえば UTF-16BE で出力したい場合,UTF-16BE ではエラーになります。