Smultron 文字コード判定 2022-JPの追加。

メール関連のテキストを扱う必要があってSmultronでテキストを開こうとしたら予想どおりの文字化け。これまで文字コードの判定には何度か手を入れてきたけれど、ISO-2022-JPは特に何もしてこなかったので、今回はISO-2022-JP対応コードを書いてみました。

Smultron本体のエンコード判定方法の流れはv10.4で追加された新しいメソッド、NSString stringWithContentsOfFile:usedEncoding error:を使うように変わっていて、ここで変換してうまくいかなければguessEncodingFromData:textDataを使うように変更になっていますが、このメソッドSJIS/EUCがダメでちょっとつかえません。

string = [NSString stringWithContentsOfFile:path usedEncoding:&encoding error:&error];
if (error != nil || string == nil) {
  if (textData == nil) {
    textData = [[NSData alloc] initWithContentsOfFile:path];
  }
  encoding = [SMLText guessEncodingFromData:textData];
  if (encoding == 0 || encoding == -1) {
    encoding = [[SMLDefaults valueForKey:@"EncodingsPopUp"] integerValue];
  }
}

なので、このメソッドの使用を後回しにしてこれまでどおりguessEncodingFromData:textDataを優先してエンコードを判定、まず判定したエンコードを用いて変換してみる。それでダメな場合にこちらを使うという流れに修正しました。

if (textData == nil)
  {
    textData = [[NSData alloc] initWithContentsOfFile:path];
  }
encoding = [SMLText guessEncodingFromData:textData];
if (encoding == 0 || encoding == -1) 
  { // Something has gone wrong or it hasn't found an encoding, so use default
    encoding = [[SMLDefaults valueForKey:@"EncodingsPopUp"] integerValue];
  }
else
  {
    string = [NSString stringWithContentsOfFile:path encoding:encoding error:&error];
  }
if (error != nil || string == nil)
  {
    string =
       [NSString stringWithContentsOfFile:path usedEncoding:&encoding error:&error];
  }

次に肝心のguessEncodingFromData:textDataですが、これまで試行錯誤の経緯から、最初に自分で実装した文字コード判定ロジックと、藤棚さんのブログから拝借したロジックの組み合わせで判定することにします。以前の自分の判定コードに戻すことにしたのは、initWithData:textData encodingでは誤判定が多いといことなんです。この方式の場合トライするエンコードの順序が非常に重要になるのですが、僕の場合はEUC/SJIS/MacOS(JIS0213)は明確な優先順位を付けかねるので、最初にある程度当たりを付けてからエンコードにトライしてみるほうがいいだろという判断です。(効率の問題は置いておくとしてですが)

このうち、SJIS(Windows)/EUCについては以前書いた記事の通りですが今回はISO-2022-JPの判定を加えます。ISO-2022-JPに関しては、割り切って漢字への切り替えさえ判定できればいいだろうということで(仮に切り替えがなくても?たぶん?ASCIIとしてうまく開ける?かな)ESC$@とESC@B二つだけを判定することにしました。

-(unsigned int)is2022JP:(unsigned char*)charData nLen:(unsigned int)nLen
{
  if (nLen < 3) return 0;
  if (*charData == 0x1b && *(charData+1) == 0x24 && 
      (*(charData+2) == 0x40 || *(charData+2) == 0x42))
    {// ESC$@(78JIS) || ESC$B(83JIS) 
      return 3;
    }
  return 0;
}

あとは、入力をスキャンして対象文字の多寡を判定します。後は、一番可能性の高いと判定した文字コードで一回変換してみてうまくいけば確定。万が一うまくいかない場合は、判定したい文字コードで変換を繰り返しエラーのない変換(正しい変換とは限らない)を探します。以下、少し長いですがコードを掲載します。

unsigned int pos,utf8,jp2022,euc,sjis,r;
pos = utf8 = euc = sjis = jp2022 = r = 0;

if (encoding == 0 && [textData length] > 2)
{
  unsigned int nLen = [textData length];
  unsigned char* charData = (unsigned char*)[textData bytes];
  while(pos < nLen)
    {
      if ((r=[self isUTF8:charData+pos nLen:nLen-pos])>0)
        {
          utf8++;
          pos+=r;
        }
      else if ([self isEUC:charData+pos nLen:nLen-pos])
        {
          euc++;
          pos+=2;
        }
      else if ([self isSJIS:charData+pos nLen:nLen-pos])
        {
          sjis++;
          pos+=2;
        }
      else if ((r=[self is2022JP:charData+pos nLen:nLen-pos])>0)
        {
          jp2022++;
          pos+=r;
        }
       else
        {
          pos++;
        }
    }
}

if (utf8>0 && utf8>euc  && utf8>sjis && utf8>jp2022)
  {
    encoding = NSUTF8StringEncoding;
  }
else if (euc>0 && euc>utf8 && euc>sjis && euc>jp2022)
  {
    encoding = NSJapaneseEUCStringEncoding;
  }
else if (sjis>0 && sjis>utf8 && sjis>euc && sjis>jp2022)
  {
    encoding = NSShiftJISStringEncoding;
  }
else if (jp2022>0 && jp2022>utf8 && jp2022>euc && jp2022>sjis)
  {
    encoding = NSISO2022JPStringEncoding;
  }

NSString* try_str = 
  [[[NSString alloc] initWithData:textData encoding:encoding] autorelease];
if (try_str != nil) return encoding;

ここまできてまだ変換できない場合はお手上げで、initWithData:encodingで変換を試みます。ここでは、これまで判定していないMacOS(日本語)とUTF16への変換をトライします。本当はこの二つもコードをスキャンして判定したほうがいいのですが、判定方法の調べがつかず手抜きです。

NSStringEncoding encodings[] =
{
  -2147483647,// MacOS Japanese
  NSUnicodeStringEncoding,
  0
};

// try to encoding
int i = 0;
while(encodings[i] != 0)
  {
    try_str = 
      [[[NSString alloc] initWithData:textData encoding:encodings[i]] autorelease];
    if (try_str != nil)
      {
        encoding = encodings[i];
        break;
      }
      i++;
  }
return encoding;

いろんなところで文字化けのことはイラだつことが多いですが、もっとしっかり勉強使用と思いつつなかなか手を出せずにいます。

添付のパッチはSmultron 3.5ベースです。${SmultronSource}/で、

patch -p0 -i smlv35_encode_jp.patch

してください。

添付サイズ
smlv35_encode_jp.patch8.11 KB

この記事のトラックバックURL:

http://hippos-lab.com/blog/trackback/267

Comments

usami-kです。
macports-jpでこの記事のパッチを利用させていただいています。
パッチを公開いただきましてありがとうございます。

SourceCode Repository - directory - macports-jp: trunk/dports/editors/Smultron-ja
http://svn.sourceforge.jp/view/trunk/dports/editors/Smultron-ja/?root=macports-jp