読者です 読者をやめる 読者になる 読者になる

高見知英のかいはつにっし(β)

高見知英のアプリケーション開発日誌 のほか、地域活動などの活動報告ブログ。

ATOKプラグイン作ってみました

プログラミング

2009年4月21日(火)午後9時48分追記:ATOKダイレクトプラグイン日記を作成したので、そちらにアップしました。今後これを使用される場合は、そちらを参照願います。> 日付変換プラグインを公開します。

昨日、横浜勉強会に出かけるまでの間に、ATOKプラグインを作ってみていました。
わたしが作ったのは簡単なもので、ATOK標準の日付検索機能に加えて「n日後」とか「来月の第n日曜日」とかを変換できるようにしたものです。


とりあえずダウンロードできる場所を用意していないのでSkyDriveからー。将来的にはこれもおんぷ村のコンテンツですねー。

作っての感想は、まあ、難しくないとはいわれていましたが本当に簡単でしたね。Rubyで適当にモジュール作って、ジャストシステムさんのATOKダイレクトAPI情報のページに従ってセットアップ用のXMLを作って、配置するだけ。あとは配布ツールを起動すれば、インストールまで簡単にやってくれる。
ただ毎回セットアップしてテストをするのも面倒ですので、事前にちゃんとテストをするための仕組みも必要。わたしはとりあえず、ファイルの下に「if __FILE__ == $0 then・・・」を書いていろいろテストコードを押し込みましたが、テストユニットを使うのも悪くないかもしれません。
まあ、まだやりたいことはあるので、暇があれば考えてみたいですね。

ソースコード解説

まあ、ソースコードはテストコードや変換コードとなんだかんだ長くなってしまいましたし、SkyDriveのZIPに入っているので詳しくは省きますが、ちょっと解説。

今回の日付の展開ロジックは割と力業。文字列を正規表現でチェックして、引っかかれば日付を計算して返すというもの(メインルーチンで書式化する)。

  WEEKINDEX = {
      "にち" => 0,
      "げつ" => 1,
      "" => 2,
      "すい" => 3,
      "もく" => 4,
      "きん" => 5,
      "" => 6,
      "" => 0,
      "" => 1,
      "" => 2,
      "" => 3,
      "" => 4,
      "" => 5,
      "" => 6
    }
  INDEXINDEX = {
      "いち" => 1,
      "" => 2,
      "さん" => 3,
      "よん" => 4,
      "" => 4,
      "" => 5,
      "" => 1,
      "" => 2,
      "" => 3,
      "" => 4,
      "" => 5,
    }

  def get_date(source_str)
    date = Date.today
    case source_str
    when /さきおととい/, /一昨昨日/
      date -= 3
    when /おととい/, /一昨日/
      date -= 2
    when /きのう/, /昨日/
      date -= 1
    when /きょう/, /今日/
    when /やのあさって/, /やなさって/, /弥明後日/
      date += 4
    when /しあさって/, /明明後日/
      date += 3
    when /あさって/, /明後日/
      date += 2
    when /あした/, /明日/
      date += 1
    when /(\d+|[0-9]+)(にちご|かご|日後)/
      date += Regexp.last_match(1).tr('0-9', '0-9').to_i
    when /(?:(?:こんど|つぎ|今度|))(にち|げつ||すい|もく|きん||||||||)(?:よう|)/
      date += (date.wday * -1 + 7) + WEEKINDEX[Regexp.last_match(1)]
    when /(?:らいげつ|来月)(?:だい|)(\d|[0-9]|いち||さん||よん||||||)(にち|げつ||すい|もく|きん||||||||)(?:よう|)/
      windex = Regexp.last_match(1).tr('0-9', '0-9').to_i
      windex = INDEXINDEX[Regexp.last_match(1)] if windex == 0

      date = Date.new(date.year, date.month, -1) + 1
      index = WEEKINDEX[Regexp.last_match(2)]
      res = 7 - date.wday + index + 7 * (windex - 1)
      res -= 7 if date.wday <= index
      date += res 
      raise TypeError, "invalid date!" if date.month != Date.today.month + 1
    when /(?:せんげつ|先月)(?:だい|)(\d|[0-9]|いち||さん||よん||||||)(にち|げつ||すい|もく|きん||||||||)(?:よう|)/
      windex = Regexp.last_match(1).tr('0-9', '0-9').to_i
      windex = INDEXINDEX[Regexp.last_match(1)] if windex == 0

      date = Date.new(date.year, date.month, 1) - 1
      date -= date.day - 1
      index = WEEKINDEX[Regexp.last_match(2)]
      res = 7 - date.wday + index + 7 * (windex - 1)
      res -= 7 if date.wday <= index
      date += res
      raise TypeError, "invalid date!" if date.month != Date.today.month - 1 # 移動しすぎ
    else
      date = nil
    end
    return date
  end

来月と先月のカレンダーを作る部分は、TCalのソースコードを参考に。Delphiのコードを見たのは久しぶりでした・・・。
あと、気づいた点として、ATOKの変換プラグインを作るとき、引数に入ってくるのは「そのときキャレット部分表示されてる状態の未変換文字列」です。
わかりにくいですが、早い話入力しようとしている文字列のひらがなとも限りませんし、漢字とも限りません*1。まあ今回は、「きょう」といれて「今日」と真っ先に出てこない環境も珍しいので「きょう」と「今日」の二種類だけサポートしましたが、場合によってはそれ以外も必要になるということです。変換方法にバリエーションを持たせるタイプのプラグインを作るのはなかなか難しいかもしれません。

書式化コードは以下のようなかんじ。Dateクラスのstrftimeメソッドを使ってます。

=begin
  以下は事前に定義済みです
  ABBR_DAYNAMES = %w(日 月 火 水 木 金 土)
  DAYNAMES = %w(日曜日 月曜日 火曜日 水曜日 木曜日 金曜日 土曜日)
  FORMATS = %w<%Y/%m/%d %Y年%m月%d日 %m月%d日 %m/%d %m月%d日(%j) %m/%d(%j)>
=end
  if date.class == Date then
    # 変換文字列の生成
    FORMATS.each{ |f|
      f.gsub!(/%j/, ABBR_DAYNAMES[date.wday])
      f.gsub!(/%J/, DAYNAMES[date.wday])
      candidate.push(addhyoki(
          date.strftime(f),
          "#{PLUGINNAME}:#{source}の日付",
          "#{source}の日付の文字列表現です",
          "definite_string"
      ))
    }
  end

ネットを探すと、strftimeで日本語文字列を曜日に変換できるようにする方法として、「Date::ABBR_DAYTIMES」を置き換えてしまう と言う方法が紹介されていましたが、最新のRubyでは動かないみたいです。残念。 というわけでちょっと不細工ですが、こんな感じになりました。


ATOKの変換プラグインは、結構簡単に作れます。まあ、使えるものを作るにはそれなりの工数が必要ですが、工夫しておもしろいものを作って見るといいんじゃないでしょうか。

*1:あとは数字も全角かもしれませんし、半角かもしれません。そのため上のコードではあえて[0-9]をマッチさせるようにしています