WSH/Jscript で動く GetPrivateProfileString()/WritePrivateProfileString() のクローンを探したけど、これといったものが見当たらなかったので再発明してみた。変なデータに遭遇した場合を含め、できるだけ GetPrivateProfileString()/WritePrivateProfileString() と同じ挙動にしたつもり*1。
仕様
- 1回の呼び出しごとに処理が完結する。
- セミコロンで始まる行はコメント。
- 開き角括弧で始まらず、かつ、イコールを含まない行はコメント扱い。
- 書き換えをしてもコメント部分はそのまま維持。
- 読み込み時、行頭・行末の空白類文字は除去する。つまり、INI ファイル内では任意のインデントを許容。
[セクション1] ; ここはコメント ; ここもコメント キー1=値1 ; ここはコメントじゃない この行はコメント扱い
- 無名セクション/無名キーも問題なく読み書き可能。
[] =値 ; ↑この値は getini(inifilename, "", "") で取得できる
- 重複セクション/重複キーは最初に出現したものを処理対象とし、それ以外は無視。
[セクション1] キー1=値A キー1=値B ; ↑2つめの「キー1=値B」は読み書き不可能 [セクション1] キー1=値C キー2=値D ; ↑2つめの「[セクション1]」と「キー1=値C」「キー2=値D」 は読み書き不可能
- セクションの閉じ括弧はなくてもいい。
[セクション1 ↑ これでもセクションになる
- セクション名/キー名の大文字小文字は区別しない。上書き時は INI ファイル側の表記を維持。
- セクション名/キー名の前後の空白類文字は除去する。上書き時は INI ファイル側の表記を維持。
- 読み込み時、値の前後の空白類文字は除去する。ただし、引用符 ("〜" または '〜') で囲まれた値は引用符の内側がそのまま読み込まれる。
キー1 = "ho" "ge" ; ↑この値は「ho" "ge」となる
使用例
// 値を設定 putini(inifilename, "セクション1", "キー1", "値1"); putini(inifilename, "セクション1", "キー2", "値2"); // 値を取得 var result = getini(inifilename, "セクション1", "キー1"); //=> "値1" WScript.Echo(result); // 指定したセクションの全エントリをハッシュとして取得 var result = ""; var data = getini(inifilename, "セクション1"); //=> {"キー1": "値1", "キー2": "値2"} for (key in data) { result += key + "=" + data[key] + "\n"; } WScript.Echo(result); // 全セクションの全エントリをハッシュのハッシュとして取得 var result = ""; var data = getini(inifilename); //=> {"セクション1": {"キー1": "値1", "キー2": "値2"}} for (section in data) { result += "[" + section + "]\n"; for (key in data[section]) { result += key + "=" + data[section][key] + "\n"; } } WScript.Echo(result); // エントリを削除 putini(inifilename, "セクション1", "キー1", null); // セクションを削除 putini(inifilename, "セクション1", null, null);
ソース
// INI ファイルから値を読む // getini(filename, section, key): そのエントリの値を取得 // getini(filename, section): そのセクションの全エントリをハッシュとして取得 // getini(filename): 全セクションの全エントリをハッシュのハッシュとして取得 function getini(filename, section, key) { section = (typeof(section) == "undefined") ? null : strip(section); key = (typeof(key) == "undefined") ? null : strip(key); var data = new Array(); try { var f = fso.OpenTextFile(filename, 1, false); // 読み込み var pos = 0; // 0:セクション前 1:セクション内 while (!f.AtEndOfStream) { var line = f.ReadLine(); if (!line.match(/^\s*;/)) { if (m = line.match(/^\s*\[([^\]]*)\]/)) { if (section === null) { data[strip(m[1])] = new Array(); } else if (pos == 0 && casecmp(strip(m[1]), section)) { pos = 1; } else if (pos == 1) { break; } } else if (pos == 1 && (m = line.match(/^\s*(.*?)=(.*)$/))) { var v = strip(m[2]).replace(/^(["'])(.*)\1$/, "$2"); if (key === null) { data[strip(m[1])] = v; } else if (casecmp(strip(m[1]), key)) { data = v; // typeof(data) == "string" break; } } } } f.Close(); } catch(e) { if (e.number != -0x7ff5ffcb) { // 「ファイルが見つかりません」以外のエラー WScript.Echo(WScript.ScriptFullName + "\n" + filename + "\n" + e.description); } } if (section === null) { for (var s in data) { data[s] = getini(filename, s); } } else if (key !== null && typeof(data) != "string") { data = null; } return data; } // INI ファイルに値を書く // putini(filename, section, key, value): そのエントリに値を設定 // putini(filename, section, key, null): そのエントリを削除 // putini(filename, section, null, null): そのセクションを削除 function putini(filename, section, key, value) { if (typeof(key) == "undefined") return false; if (typeof(value) == "undefined") return false; section = strip(section); key = strip(key); try { var f = fso.OpenTextFile(filename, 1, true); // 読み込み/新規作成 var upper = ""; var lower = ""; var pos = 0; // 0:セクション前 1:セクション内 2:セクション後 while (!f.AtEndOfStream) { var line = f.ReadLine(); if (!line.match(/^\s*;/)) { if (m = line.match(/^\s*\[([^\]]*)\]/)) { if (pos == 0 && casecmp(strip(m[1]), section)) { pos = 1; if (key === null) continue; upper += line + "\n"; continue; } else if (pos == 1) { pos = 2; } } else if (pos == 1 && (m = line.match(/^(.*?)=(.*)$/))) { if (key === null) continue; upper += lower; lower = ""; if (casecmp(strip(m[1]), key)) { key = m[1]; break; } upper += line + "\n"; continue; } } lower += line + "\n"; if (pos >= 2) break; } if (key !== null && value !== null) { if (pos) { // エントリを追加/置換 upper += key + "=" + value + "\n"; } else { // セクションとエントリを追加 lower += "[" + section + "]\n" + key + "=" + value + "\n"; } } if (!f.AtEndOfStream) { lower += f.ReadAll(); } f.Close(); var f = fso.OpenTextFile(filename, 2, true); // 書き込み f.Write(upper + lower); f.Close(); } catch(e) { WScript.Echo(WScript.ScriptFullName + "\n" + filename + "\n" + e.description); } return upper + lower; } function strip(arg) { return (typeof(arg) == "string") ? arg.replace(/^\s+|\s+$/g, "") : arg; } function casecmp(arg1, arg2) { if (typeof(arg1) == "string") arg1 = arg1.toLowerCase(); if (typeof(arg2) == "string") arg2 = arg2.toLowerCase(); return (arg1 == arg2); }
お願い
これってグローバル名前空間を汚染するよねぇ…。どなたかうまいことパッケージ化してくださいませんか?
とりあえず↓こんなんやってみたけど、はたしてこれがベストプラクティスなのかどうか全然わからない ヽ(´д`)ノ
var IniFile = (function() { var fso = WScript.CreateObject("Scripting.FileSystemObject"); var strip = function strip(arg) { ... }; var casecmp = function casecmp(arg1, arg2) { ... }; return { get: function(filename, section, key) { ... }, put: function(filename, section, key, value) { ... } }; })();
10/27 追記
バグ修正しました。