036.【ID】2つのテキストで使われている文字の差分を取得
書いたコード
var tmp = []; var log = []; var txtFile1 = File.openDialog("テキスト1を指定してください", "*.txt"); if (txtFile1) { flag = txtFile1.open("r"); if (flag) { var tgt1 = txtFile1.read(); txtFile1.close(); } } var txtFile2 = File.openDialog("テキスト2を指定してください", "*.txt"); if (txtFile2) { flag = txtFile2.open("r"); if (flag) { var tgt2 = txtFile2.read(); txtFile2.close(); } } tgt1 = tgt1.split(/(?=(?:[\0-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]))/); var uniqueTgt1 = removeDuplicates(tgt1); tgt2 = tgt2.split(/(?=(?:[\0-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]))/); var uniqueTgt2 = removeDuplicates(tgt2); for (var i = 0, l = uniqueTgt1.length; i < l; i++) { tmp.push({ chr: uniqueTgt1[i], code: fixedCharCodeAt(uniqueTgt1[i]), cnt1: countItems(tgt1, uniqueTgt1[i]), cnt2: 0 }); } ut2: for (var i = 0, l = uniqueTgt2.length; i < l; i++) { for (var j = 0; j < tmp.length; j++) { if (tmp[j]['chr'] === uniqueTgt2[i]) { tmp[j]['cnt2'] = countItems(tgt2, uniqueTgt2[i]); continue ut2; } } tmp.push({ chr: uniqueTgt2[i], code: fixedCharCodeAt(uniqueTgt2[i]), cnt1: 0, cnt2: countItems(tgt2, uniqueTgt2[i]) }) } for (var i = 0; i < tmp.length; i++) { if (tmp[i]['cnt1'] !== tmp[i]['cnt2']) { log.push(tmp[i]) } } /* { chr: 'あ', code: 12354, cnt1: 1 cnt2: 2 } */ var logTxt = '文字\tコードポイント\tテキスト1\tテキスト2 \r'; for (var i = 0; i < log.length; i++) { for (j in log[i]) { logTxt = logTxt + log[i][j] + '\t'; } logTxt = logTxt.replace(/\t$/, '\r'); } var logPath = txtFile2.fsName.replace(txtFile2.name, "log.txt"); writeTxt(logPath, logTxt); function fixedCharCodeAt(str, idx) { idx = idx || 0; var code = str.charCodeAt(idx); var hi, low; // 上位サロゲートの場合コードポイントを返す if (0xD800 <= code && code <= 0xDBFF) { hi = code; low = str.charCodeAt(idx + 1); if (isNaN(low)) { throw 'High surrogate not followed by low surrogate in fixedCharCodeAt()'; } return ((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000; } // 下位サロゲートの場合falseを返す if (0xDC00 <= code && code <= 0xDFFF) { return false; } return code; } function countItems(arr, key) { var cnt = 0; for (var i = 0; i < arr.length; i++) { if (arr[i] === key) { cnt++; } } return cnt; } function removeDuplicates(arr) { var exist = {}; var result = []; for (var i = 0, l = arr.length; i < l; i++) { var tmp = arr[i]; if (!exist[tmp]) { exist[tmp] = true; result.push(tmp); } } return result; } function writeTxt(path, txt) { var fObj = new File(path); fObj.encoding = "UTF-8"; //デフォルトはShift-JIS if (fObj.open("w")) { fObj.write(txt); fObj.close(); return fObj; } else { alert("ファイルが開けません\n" + path); } }
メモ
わかったこと
- サロゲートペア周り
String.lengthはUnicodeコードポイント単位での文字列の長さではなく、UTF-16符号単位の長さを返す。
つまり、 - サロゲートペアの文字のlengthは2となる。
- String.split("")はサロゲートペアを破壊する。
- String.charCodeAt()は上位サロゲート/下位サロゲートそれぞれにアクセスする。
わからなかったこと
MDNにサロゲートペアに対応したfixedCharCodeAt関数が掲載されている。
勉強がてらコメントをいれつつ1行ずつ読み下していったら楽しかった&色々分からんところがあったのでので記録。
// * 自分の書き込みは「// *~」としている function fixedCharCodeAt(str, idx) { // ex. fixedCharCodeAt('\uD800\uDC00', 0); // 65536 // ex. fixedCharCodeAt('\uD800\uDC00', 1); // false idx = idx || 0; // * Falsy || Truthy ではTruthyの値が返る // * Falsy1 || Falsy2 ではFalsy2の値が返る // * Falsyな値:0、−0、undefined、空文字、null、false、NaN // * 関数を定義した時の引数(パラメータ)より呼び出した時に与えた引数の方が少ない時、 // * 残りのパラメータにはundefinedが代入される // * つまりidxを渡さなかったとき第二引数はundefinedとなり、idxに0が代入される var code = str.charCodeAt(idx); var hi, low; // High surrogate (could change last hex to 0xDB7F to treat high // private surrogates as single characters) // * high private surrogates: 上位私用サロゲート。0xDB80~0xDBFFを外字領域として使用できる。 // * 0xD800~0xDBFF: 上位サロゲート。 // * この時点ではcodeは55399なんだが、なぜ 0xD800 <= code && code <= 0xDBFF ができるのか。 // * codeも0xD800もtypeof()の結果はnumber。 // * コンソールに「0xD800」と入れると「55296」が返ってくる。 // * Number('0xD800')? if (0xD800 <= code && code <= 0xDBFF) { hi = code; low = str.charCodeAt(idx + 1); if (isNaN(low)) { // * どういう状況でこれが起こりうるのか? throw 'High surrogate not followed by low surrogate in fixedCharCodeAt()'; } // * 上位サロゲートだったらそのコードポイントを返す return ((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000; } if (0xDC00 <= code && code <= 0xDFFF) { // Low surrogate // We return false to allow loops to skip this iteration since should have // already handled high surrogate above in the previous iteration // * 下位サロゲートだったらfalseを返す // * これは前述の処理で下位サロゲートも取得しているのでループ時にスキップするため return false; /*hi = str.charCodeAt(idx - 1); low = code; return ((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000;*/ } return code; }
分からなかったのは、
if (0xD800 <= code && code <= 0xDBFF){
のところ。
codeも0xD800もtypeof()の結果はnumber。
0xD800がどういう扱いなのかよく分からない。
積み残し
- 結合文字対応
Unicodeの特殊な文字 “結合文字列” – ものかの
JavaScript : String の配列化(結合文字も考慮) : typeOf 'aki_mana' - 比較材料のテキストのファイル名が「log.txt」だと上書きしてしまうのでなんとかする。