コモノExtendScript100本ノック

超初心者のDTPオペレーターが週にひとつスクリプトを書くブログ

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);
  }
}

メモ

わかったこと

わからなかったこと

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がどういう扱いなのかよく分からない。

積み残し

参考