0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Windows の iconv コマンドを WSH/JScript でささっと作る

Posted at

はじめに

近いうちに Visual Studio Express 2017 がサポート期限がくるので LLVM clang への引っ越しを考えていますが1,問題は LLVM clang の場合,ソースコードは UTF-8 一択なのが困り事です2

会社で使っている ARM clangshift_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 とします。
  • UNIXiconv コマンドには「変換できなかった文字を出力しない」という -c オプションがありますが,これもサポートしません。これも ADODB.Stream を使う制約です。
  • UNIXiconv コマンドには「対応しているコードを表示する」という -l オプションがありますが,これもサポートしません。

実装コード

まずは,実装コードを以下に示します。

iconv.js
//------------------------------------------------------------------------------
// グローバル変数
//------------------------------------------------------------------------------
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

サンプルテキスト

サンプルテキストを以下に示します。実際は,下記テキストの内容を各種文字コードでエンコードしたテキストファイルを用意しました。

sample.txt
 、。,.・:
~∥|…‥‘’
ABCDEFGHIJKLMNOPQRSTUVWXYZ
abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ
abcdefghijklmnopqrstuvwxyz

自動検出機能

上記のサンプルテキストを用いて自動検出機能の性能を調べました。自動検出には _autodetect_autodetect_all の二種類ありますが,いずれを使っても shift_jisjiseuc-jp の判別は完璧でした。_autodetect_all を使うと BOM があれば UTF-8UTF-16LEUTF-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 ではエラーになります。

  1. 今更^2 Visual Studio Express 2017 で最新の Windows SDK を使えるか? - Qiita

  2. clang-clで日本語を扱うと色々面倒 - Qiita

  3. 【VBA】UTF-8(BOMなし)+LF改行のテキストファイルを出力する方法 - Qiita

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?