📝

Java の ASCII Property ファイル向け WinMerge 展開プラグイン。

2024/08/31に公開

ASCIIコードは義務なんです。

国際文字対応してますか?
義務ですよ。🙃
果たしてますか?

概要

まだまだPropertyファイルでASCIIを使っている現場を目の当たりにして。これからも辛酸を舐めそうで。特にDIFFるとき用にとWinMergeの展開プラグインを作った。

青果物

CompareJavaAsciiPropertyFile.sct
<scriptlet>
<implements type="Automation" id="dispatcher">
	<property name="PluginEvent">
		<get/>
	</property>
	<property name="PluginDescription">
		<get/>
	</property>
	<property name="PluginFileFilters">
		<get/>
	</property>
	<property name="PluginIsAutomatic">
		<get/>
	</property>
	<method name="UnpackFile"/>
	<method name="PackFile"/>
</implements>

<script language="JScript">

/**
 * define.
 * https://learn.microsoft.com/ja-jp/office/vba/language/reference/user-interface-help/opentextfile-method
 */
var IOMode = {
	ForReading:   1,
	ForWriting:   2,
	ForAppending: 8
};
var Format = {
	TristateUseDefault: -2, // system
	TristateTrue:       -1, // UNICODE
	TristateFalse:      -0  // ASCII
};

/** general. */
var fso = new ActiveXObject('Scripting.FileSystemObject');
var wsh = new ActiveXObject('WScript.Shell');


/**
 * ReaddAllTextFile.
 * @param filePath
 * @param format
 * @return text
 */
function readTextFile(filePath, format) {
	var stream = null;
	try {
		stream = fso.OpenTextFile(filePath, IOMode.ForReading, false, format);
		return stream.ReadAll();
	} finally {
		if(stream) {
			stream.Close();
		}
	}
}

/**
 * writeTextFile
 * @param filePath
 * @param text
 * @param format
 */
function writeTextFile(filePath, text, format) {
	var stream = null;
	try {
		stream = fso.OpenTextFile(filePath, IOMode.ForWriting, true, format);
		stream.Write(text);
	} finally {
		if(stream) {
			stream.Close();
		}
	}
}

/**
 * get_PluginEvent
 * @return description.
 */
function get_PluginEvent() {
	return 'FILE_PACK_UNPACK';
}

/**
 * get_PluginDescription
 * @return description.
 */
function get_PluginDescription() {
	return 'diff property file.';
}

/**
 * get_PluginFileFilters
 * @return file name patern filter.
 */
function get_PluginFileFilters() {
	return '\.properties$;\.property$';
}

/**
 * get_PluginIsAutomatic
 * @return is automatic.
 */
function get_PluginIsAutomatic() {
	return true;
}

/**
 * UnpackFile
 * @param fileSrc source file path.
 * @param fileDst target file path for display.
 * @param pbChanged
 * @param pSubcode
 * @return
 */
function UnpackFile(fileSrc, fileDst, pbChanged, pSubcode) {
	var result = new ActiveXObject('Scripting.Dictionary');
	var srcText = readTextFile(fileSrc, Format.TristateFalse);

	var convertedText = srcText.replace(/\\u([0-9a-fA-F]{4})/g, function(_, p1) {
		return String.fromCharCode(parseInt(p1, 16));
	});

	writeTextFile(fileDst, convertedText, Format.TristateTrue);

	result.Add(0, true);
	// pbChanged
	result.Add(1, true);
	// pSubcode
	result.Add(2, 0);
	return result.Items();
}

/**
 * PackFile
 * @param fileSrc edited file path file for replace.
 * @param fileDst not use.
 * @param pbChanged
 * @param pSubcode
 * @return
 */
function PackFile(fileSrc, fileDst, pbChanged, pSubcode) {
	var result = new ActiveXObject('Scripting.Dictionary');

	var srcText = readTextFile(fileSrc, Format.TristateTrue);
	var convertedText = '';

	for(var i = 0; i < srcText.length; i++) {
		var c = srcText.charAt(i);
		var cp = srcText.charCodeAt(i);
		if(cp <= 0x007F) {
			// ASCII
			convertedText += c;
		} else {
			// Unicode Escape Sequence.
			convertedText += '\\u' + ('0000' + cp.toString(16)).slice(-4);
		}
	}

	writeTextFile(fileSrc, convertedText, Format.TristateFalse);

	result.Add(0, true);
	// pbChanged
	result.Add(1, true);
	// pSubcode
	result.Add(2, 0);
	return result.Items();
}
</script>
</scriptlet>

使いかた

環境

  • WinMerge 2.16.42.1
    ※ 古いと動かない可能性あり。

インストール

# 手順 内容
1 上記スクリプトを保存する。 (ファイル名)
CompareJavaAsciiPropertyFile.sct
2 WinMergeのプラグイン フォルダに配置する。 (一般的なインストール先の場合)
C:\Program Files\WinMerge\MergePlugins

image.png

使い方

  • 展開プラグインを指定して比較する。
    image.png

  • 手動で展開プラグインを指定する。
    image.png
    ※ 自動展開は有効にしないのがおすすめ。 (ASCII化されているPropertyファイルの方がいまや少ない (?) ので。)

効果

  • native2ascii で reverse しなくても、可読性の比較ができる。
    image.png
  • そのまま編集保存が可能。( native2ascii でASCII化する必要なし。 )
  • 開発効率の向上
    ※[要出典] 今更どのプロジェクトで? (苦笑)

WinMergeのプラグイン説明

説明資料などがなく、いくつか推察も含んでいる旨ご容赦。

実装

今回は、展開 ⇔ 保存 が相互に行える、 展開プラグイン として以下のメソッドを実装。

プロパティ

property
(method)
return discription
PluginEvent
(get_PluginEvent())
string 展開プラグインの定数。
FILE_PACK_UNPACK
PluginDescription
(get_PluginDescription())
string プラグインの説明。
PluginFileFilters
(get_PluginFileFilters())
string プラグインを自動適用する場合のファイル名フィルター。
PluginIsAutomatic
(get_PluginIsAutomatic())
boolean 自動展開可能可否。

メソッド

UnpackFile

arg1 string fileSrc WinMergeに指定されたファイル パス。
例): Z:\MergePlugin - Property Files\application_ja.properties
arg2 string fileDst 展開後、比較に用いられるファイルのパス。
コールバックされたときはまだファイルは存在しない。変更内容で生成する。
例): C:\Users\xxxx\AppData\Local\Temp\WinMerge_TEMP_20292\_WM70B3.tmp.properties
arg3 boolean pbChanged 変更有無のフラグ。(本来はポインタ)
arg4 boolean pSubcode 用途不明。(本来はポインタ)
return Scripting.Dictionary.Items() 実行の成否を含んだ複数の戻り値。pbChangedpSubcodeが参照渡し出来ないための辞書返しと思われる。
/**
 * UnpackFile
 * @param fileSrc source file path.
 * @param fileDst target file path for display.
 * @param pbChanged
 * @param pSubcode
 * @return
 */
function UnpackFile(fileSrc, fileDst, pbChanged, pSubcode) {
	var result = new ActiveXObject('Scripting.Dictionary');

	// ASCIIで読み取り。
	var srcText = readTextFile(fileSrc, Format.TristateFalse);

	// Unicodeエスケープ シーケンスを正規表現で見つけ、文字に一括置換する。。
	var convertedText = srcText.replace(/\\u([0-9a-fA-F]{4})/g, function(_, p1) {
		return String.fromCharCode(parseInt(p1, 16));
	});

	// Unicode (USC2) で書き出し。
	writeTextFile(fileDst, convertedText, Format.TristateTrue);

	// 戻り値
	result.Add(0, true);
	result.Add(1, true); // pbChanged?
	result.Add(2, 0);    // pSubcode?
	return result.Items();
}

readTextFile() writeTextFile() は言うまでもない共通処理のため割愛。

PackFile

arg1 string fileSrc WinMerge上で編集された内容のファイル。
※変更がある場合は、このファイルに対して変更をする (上書きする)。
例): C:\Users\xxxx\AppData\Local\Temp\WinMerge_TEMP_20292\MRG5F99.tmp
arg2 string fileDst 用途不明。
コールバックされたときはまだファイルは存在しない。
例): C:\Users\xxxx\AppData\Local\Temp\WinMerge_TEMP_20292\_WM5F9A.tmp
arg3 boolean pbChanged 用途不明。(本来はポインタ)
arg4 boolean pSubcode 用途不明。(本来はポインタ)
return Scripting.Dictionary.Items() 実行の成否を含んだ複数の戻り値。pbChangedpSubcodeが参照渡し出来ないための辞書返しと思われる。

え? fileSrc から読み取って fileDst を作成するんじゃないの!!!😲
え? fileSrcを上書き・・・🤔???

/**
 * PackFile
 * @param fileSrc edited file path file for replace.
 * @param fileDst not use.
 * @param pbChanged
 * @param pSubcode
 * @return
 */
function PackFile(fileSrc, fileDst, pbChanged, pSubcode) {
	var result = new ActiveXObject('Scripting.Dictionary');

	// Unicode (USC2) で読み取り (UnpackFile時と同じコード)。
	var srcText = readTextFile(fileSrc, Format.TristateTrue);

	var convertedText = '';

	// ASCII文字以外の文字をUnicodeエスケープ シーケンスに置き換えた文字列を生成する。
	for(var i = 0; i < srcText.length; i++) {
		var c = srcText.charAt(i);
		var cp = srcText.charCodeAt(i);
		if(cp <= 0x007F) {
			// ASCII
			convertedText += c;
		} else {
			// Unicode Escape Sequence.
			convertedText += '\\u' + ('0000' + cp.toString(16)).slice(-4);
		}
	}

	// ASCIIで上書き保存。
	writeTextFile(fileSrc, convertedText, Format.TristateFalse);

	// 戻り値
	result.Add(0, true);
	result.Add(1, true); // pbChanged?
	result.Add(2, 0);    // pSubcode?
	return result.Items();
}

readTextFile() writeTextFile() は言うまでもない共通処理のため割愛。

所感

実は native2ascii な現場で従事して10年余りにわたりフラストレーションをため続けていた。リポジトリと衝突したときに確認がしづらかったり。言語ファイル同士で単語状況を確認したかったりなど。もっとも、あまり application.properties を編集することないのだけど。

ただ、今後まだまだ付き合いが長くなることに合わせて。せっかくなのでWinMergeのプラグインの作成にチャレンジをしてみた。

正直情報が無く、デバッグもしづらくて大変だった。。。
_ (┐「ε:)_
VBScriptのオワコンでプラグイン仕様が変わったのか、JScriptの情報もなかったり動かなかったりで。。。

JScriptでできるのか?
ネイティブ文字コードのJScriptで国際文字対応できるのか?
やっぱCでDLL作るか?
でも文字コードが厄介だなぁ。。
ならC++CLIか?

とか色々悩んだんだけど。今回は、WSHで読み書きするUSC2がそのままWinMergeでも扱えたことが結構助かった。

application.properties向け比較ツールを作る ことも頭をよぎりつつ、DIFFツールの再開はムリゲー。と言うか20年以上付き合っていて、実績も多分にあるWinMergeに近づくなんて恐れ多いし。。。など悩んでいたので。今回プラグインを作成出来て大変良かった。

ぶっちゃけ、WinMergeの展開プラグインでPackメソッドを実装しているものがなく、情報も無くて大変だったけど。これが本家に取り込まれないかなぁ、とか妄想なんかもしてみたり。(恐れ多い!)

謝辞🙇

Discussion