Smultronエディタ

MacOSXでRubyコードを書くのにはSmultronを愛用しています。最初はターミナルでviしていたのですがやっぱり日本語の入力が気に入らなくて(表示だけできてもしかたないじゃん)使っていたのですが、Smultronにも少し問題があって、エンコーディングを自動判定にしておいても全然自動判定してくれないんですね。ずっとバグなのかなぁ?って疑問で昨日ドキュメントを調べてみようと思い立ちWebをのぞいてみるとソースが公開されていました。
で、ソースを見たらhtmlの"charset"とxmlの"encoding"、UTF8と16の判定しかしていませんでした。しかもこのどれかにマッチしない場合いきなりNSISOLatin1StringEncodingでencodingするんですね。ここのNSISOLatin1StringEncodingで失敗してはじめてNSString defaultCStringEncodingを使うみたい。僕の場合、MacOSXの時はUTF8でコードを書いて会社から持ち帰るコードやテスト用のデータなんかはSJISなので開くたびアラートがあがってエンコーディング設定を選択し直していたので非常に面倒です。で、変更しちゃいました。本来、自動判定しているコードに自分のよく使うShiftJISとかEUCJPとかを追加するべきなんでしょうがObjectiveCはあまりよく知らないしCocoaもちょっと自信がありませんので最後の部分でNSISOLatin1StringEncodingしているところをNSString defaultCStringEncodingに、アラート用のフラグもこのエンコードに失敗したときセットするよう変更しました。これで、めでたく会社から持ち帰りのRubyスクリプトの変更が楽になりました。あとは、会社のLINUX用スクリプトのEUCですかね。こちらはちゃんとコードを書かないとダメそうです。
しかし、2バイト文字文化圏のプログラマはかならずこういうしなくても良いような(?)苦労をさせられますね。UNICODEが登場しても解決しないというかむしろ複雑になっているような気がします...

でも、やっぱりEUCも扱いたいしきちんと修正しないと一応プログラマなんだしと思い、真剣にSmultronのソースを読みました。Objectiv-Cは初めてだけれどC/C++は長いこと使っているしなんとかなるだろっていう気軽なノリです。仕事じゃないし。
最初コメントだけを拾い読みして勘違いしていたのですが、UTFの判定もBOMをチェックしているだけということが判明。残念ながらSmlutronはこのあたりは緩い仕様みたいです。(英語文化圏の人には興味のない箇所ですかな?)SJISやEUCの判定は以前何かの仕事で使ったコードが手元にあるのでなんとかなるのですが、UTF8の判定は初めて。Wikiで調べてみると案外サッパリした仕様。上位バイトの1の数で文字数が分かるなんてエレガントです。その他いろいろwebを検索して仕判定用コードを書きました。まぁ~Objective-Cバリバリの人から見たらそのまんまC/C++ですがなんとかコンパイルもOK目的を達成できました。
今回、ObjectiveCというかCocoaプログラムを初めてちゃんとみたのですがこれまたおもしろそうですね。今はRubyの勉強中なんですがCocoaのも気を引かれます。仕事でRubyは使えますがCocoaはさすがにねぇ。OSX Server使っている部署も社内にあるんですけど、仕事出してくれないかなぁ。

-(unsigned int)isUTF8:(const unsigned char *)charData 
                                    nLen:(unsigned int)nLen
{
  if( (nLen >= 3) &&
    (*(charData+0) & (unsigned char)0xf0)==
       (unsigned char)0xe0 &&
    (*(charData+1) & (unsigned char)0xc0)==
       (unsigned char)0x80 &&
    (*(charData+2) & (unsigned char)0xc0)==
       (unsigned char)0x80 )
  {
    return 3;
  }
  if( (nLen >= 2) &&
    (*(charData+0) & (unsigned char)0xe0)==
       (unsigned char)0xc0 &&
    (*(charData+1) & (unsigned char)0xc0)=
       (unsigned char)0x80) 
  {
      return 2;
  }
    return 0;
}
-(BOOL)isSJIS:(unsigned char*)charData nLen:(unsigned int)nLen
{
  if (nLen < 2) return NO;
  if ((*charData >= 0x81 && *charData <= 0x9f) || 
      (*charData >= 0xe0 && *charData <= 0xfc))
  {
      if ((*(charData+1) >= 0x40 && 
           *(charData+1)< 0xfc) && 
           (*(charData+1) != 0x7f))
       {
          return YES;
       }
  }
  return NO;
}
-(BOOL)isEUC:(unsigned char*)charData nLen:(unsigned int)nLen
{
  if (nLen < 2) return NO;
  if ((*charData >= 0x8e && *charData <= 0xfe) &&
      (*charData != 0x8f && *charData != 0xa0))
   {
      if (*(charData+1) >= 0xa0 && *(charData+1) <= 0xfe)
      {
         return YES;
      }
   }
   return NO;
}

この三つの関数をguessEncodingFromData()に以下のように埋め込んで一応自分で使うには事足りるようになりました。

  if (!foundExplicitEncoding && [textData length] > 2) {
    unsigned int nLen = [textData length];
    unsigned char* charData = (unsigned char*)[textData bytes];
    unsigned int pos,utf8,euc,sjis,r;
    pos = utf8 = euc = sjis = r = 0;
    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{
       pos++;
     }
   }
   if (utf8 > 0 && utf8 >= euc  && utf8 >= sjis)
       encoding = NSUTF8StringEncoding;
   if (euc  > 0 && euc  >= utf8 && euc  >= sjis)
      encoding = NSJapaneseEUCStringEncoding;
     if (sjis > 0 && sjis >= utf8 && sjis >= euc )
       encoding = NSShiftJISStringEncoding;

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

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

Comments