JScript.NET で読み込んだファイルの文字コードが UTF-8 か Shift-JIS か判定する

JScript.NET で読み込んだテキストファイルの文字コードを判別したかった。

こちらのサイトで、Jcode.pm という Perl 製の有名な文字コード判定スクリプトを VB.NET と C# に移植していた。

元のソースは JIS や EUC も判定できるのだが、今回はコレをベースに、「テキストファイルが Shift-JIS か UTF-8 のどちらか」を判別する簡易版を JScript.NET で作ってみた。このコード部分だけなら JScript.NET 特有の処理は含んでいないので、そのまま JavaScript としても利用できると思われる。

ドキュメンテーションコメント部分は、Closure Compiler で使えるアノテーションコメントに沿って書いてみた。

/**
 * 文字コードを Shift-JIS か UTF-8 か判定する
 * 
 * @param {Array.<Byte>} bytes テキストファイルのバイト配列
 * @return {String} UTF-8 と判定したら "utf-8"、Shift-JIS と判定したら "shift-jis" を返す
 */
function getCode(bytes) {
  var len = bytes.Length;
  
  if(len < 2) {
    // 短すぎると判別不可能、UTF-8 とみなすことにする
    return "utf-8";
  }
  else if((bytes[0] == 0xEF) && (bytes[1] == 0xBB) && (bytes[2] == 0xBF)) {
    // BOM 付き UTF-8
    return "utf-8";
  }
  
  var sjis = 0;
  for(var i = 0; i < len - 2; i++) {
    var b1 = bytes(i);
    var b2 = bytes(i + 1);
    if( ( (0x81 <= b1 && b1 <= 0x9F) || (0xE0 <= b1 && b1 <= 0xFC) ) &&
        ( (0x40 <= b2 && b2 <= 0x7E) || (0x80 <= b2 && b2 <= 0xFC) ) ) {
      sjis += 2;
      i++;
    }
  }
  
  var utf8 = 0;
  for(var i = 0; i < len - 2; i++) {
    var b1 = bytes(i);
    var b2 = bytes(i + 1);
    if( (0xC0 <= b1 && b1 <= 0xDF) &&
        (0x80 <= b2 && b2 <= 0xBF) ) {
      utf8 += 2;
      i++;
    }
    else if(i < len - 2) {
      var b3 = bytes(i + 2);
      if( (0xE0 <= b1 && b1 <= 0xEF) &&
          (0x80 <= b2 && b2 <= 0xBF) &&
          (0x80 <= b3 && b3 <= 0xBF) ) {
        utf8 += 3;
        i += 2;
      }
    }
  }
  
  if(sjis > utf8) {
    return "shift-jis";
  }
  else if(utf8 > sjis) {
    return "utf-8";
  }
  
  // 不明・UTF-8 とみなすことにする
  return "utf-8";
}

Jcode.pm のことはこのスクリプトを作る時に初めて知ったのだが、どうやらテキストデータのバイト配列から「Shift-JIS っぽい並び」や「UTF-8 っぽい並び」を見付けてはポイントを加算し、一番ポイントが高い文字コード種別を返しているようだ。

こうして作った getCode() 関数は以下のように使う。

/**
 * テキストファイルの文字コードを判定した上でテキストを読み込み、
 * CR+LF で改行した文字列を返却する
 * 
 * @param {String} filePathStr 読み込みたいテキストファイルのフルパス
 * @return {String} CR+LF で改行したテキストファイルの文字列
 */
function readTextFile(filePathStr) {
  // 引数のテキストファイルを読み込み、文字コードを判定する (getCode() を使用)
  var enc = getCode(System.IO.File.ReadAllBytes(filePathStr));
  
  // 変数 enc に判定した文字コードが入るため、これを利用して改めてテキストファイルを読み込む
  var stream = new StreamReader(filePathStr, Encoding.GetEncoding(enc));
  
  // テキストを保持する変数
  var text = "";
  
  // 1行ずつテキストを読み込み、改行コードを付与していく
  while(stream.Peek() > -1) {
    text += stream.ReadLine() + "\r\n";
  }
  
  stream.Close();
  
  return text;
}

この関数では、引数にテキストファイルのパスをもらい、さきほどの getCode() で文字コードを判定している。判定した文字コード種別を使用して Stream で読み込んだら、改行コードを CR+LF に統一して文字列で返却している。