#!/usr/local/bin/perl
$cfile = "default";
$cpath = $ENV{'HOME'} . "/Library/Calendars/";
#
#
# iCal v1.0 (by Apple, Sep. 2002) の出力(vCalendar形式)をCBRC-web形式、
#       yy/mm/dd [localtion] event (Description, ...)
# の形にするフィルタ
#
#                                                Oct.  7, 2002 TOMINAGA Daisuke
#                                                   tominaga-daisuke@aist.go.jp
#                                               http://www.cbrc.jp/%7Etominaga/
#
# 使用方法
#   1. Perl 5.8 をインストールし、perlコマンドの場所をこのスクリプトの１行目で
#      指定する。
#   2. ターミナル上で、iCalのカレンダー名を引数としてこのスクリプトを実行する。
#   3. 標準出力に、CBRC-webな形式で予定が書き出される。
#   A. 引数なしで特定のカレンダーを変換してほしいときは、このスクリプトの２行目
#      にそのカレンダー名を記述する。日本語のカレンダー名を指定したいときは、こ
#      の方法が一番確実。このスクリプトは EUC であることを想定している。
#
# 検証環境
#   Mac OS X 10.2 および perl 5.8.0
#
#
# 仕様とか覚え書きとか
#
# ・iCal のカレンダー名が日本語の時は、Unicode で指定しなければならない。コマ
#   ンドラインで指定するときは UTF-8、このファイルの先頭部分で指定するときは
#   EUCにする（このファイル全体が EUC で、その $cfile に対して EUC から UTF-8
#   に変換しているから）。
#
# ・引数を指定しなかったときは、default という名前のカレンダーを探す。しかし普
#   通この名前のカレンダーはない。
#
# ・vCalendar の BEGIN:VEVENT 行から END:VEVENT 行の間を、適当に整形する。ほか
#   の部分は無視する。イベント名は SUMMARY で指定されたものをそのまま出力する。
#   具体的には、iCal 中の件名、開始日時、終了日時、メモが出力される。他は無視。
#
# ・iCal のメモ欄にかかれた内容は、 DESCRIPTIONタグに記述される。ここで指定さ
#   れている文字列は\n（改行ではなく、\ と n）で結合されているが、メモ欄では複
#   数の行である。内容全体を（）で囲んでイベント名の後に出力する。行が URL と
#   思われる文字列の場合（行頭あるいは\nの直後がhttp://の場合）は、イベント名
#   から HTML タグでリンクを張る。URL はメモ欄では単独の行でなければならない。
#   [ ] で囲まれた文字列は、[ ] を付けたまま、イベント名の先頭に空白文字を挟ん
#   で付加される。これも、[ ] だけで単独の行でなければならない。
#
# ・iCalのカレンダーファイル中で、行頭が空白文字の行は、その前の行の
#   DESCRIPTION の内容の続きである。DESCRIPTION の内容では、" と , は　\　でエ
#   スケープされている。
#
# ・DTSTART が文字 T をはさんで年月日８桁と時刻６桁であるとして、T よりも後があ
#   る時には時刻（ T の直後４桁）をイベント名の前につける。なければつけない。
#       20020920        -> 2002年9月20日
#       20020920T123000 -> 2002年9月20日12時30分
#
# ・理解するタグは全部で６種類のみ。このうち BEGIN と END に関しては、VEVENT
#   しか理解しない。
#       BEGIN, END, SUMMARY, DTSTART, DTEND,  DESCRIPTION.
# 
# ・このスクリプトの文字コードは EUC。入力ストリームは UTF-8。出力ストリームは
#   EUC。Mac OS X でのファイル名も UTF-8。ターミナルの文字コードも EUC。
#
#
# 苦労の跡
#
# Oct. 7, 2002
# ・iCalのメモに時刻らしきものがあったら、iCal中の指定時刻よりも優先して表示す
#   ることにした。
# ・URLに%や#が入っていてもいいようにした。
# ・予定の時刻順にソートするようにした。
# Oct. 2, 2002
# ・iCalのメモがやたらたくさんあるときには、適当にはしょることにした。
# Sep. 25, 2002
# ・コメントをたくさん書いた。日本語のファイル名でも開けるように、ちょっと工夫
#   してみた。vCalendar 中では "," もエスケープされるのに気づいた。perlでも
#   globbing できるのに気づいたので、iCal ファイルが開けなかったときは候補を表
#   示するようにした。
# Sep. 24, 2002
# ・メモに\[.*\]というパターンがあったら、それを場所と見なして、イベント名の前
#   に付けるようにした。
# Sep. 23, 2002
# ・重複イベントをまとめるようにした。開始時刻と終了時刻の両方に :00 が入ってる
#   場合に、:00 を省くようにした。
# Sep. 22, 2002
# ・iCal のカレンダーファイルがどこにあるのか見つけた。ので、デフォルトのパスを
#   決めた。
# Sep. 20, 2002
# ・とりあえず作った。
#
# 以下、スクリプト本体。

use 5.8.0;
use Switch;
use Encode;
use Encode qw(from_to);

# iCal のカレンダーファイル中には現れてほしくない文字列。
$SEP = "--i2cRecordSeparator--";

# まず、２行目で指定されたファイル名を Unicode にする
from_to($cfile, 'euc-jp', 'utf8');

if ($ARGV[0] ne "") { $iCalCalendar = $cpath . $ARGV[0] . ".ics"; }
else                { $iCalCalendar = $cpath . $cfile   . ".ics"; }

# iCal ファイルをオープン。失敗したときは、候補を表示して終了する。
unless (open(ICAL, $iCalCalendar)) {
  print $iCalCalendar .  "は開けませんよ？\n";
  print "以下から選んで指定してね。(" . $cpath . "にあるものです。)\n";
  while (<$cpath*>) {
    from_to($_, 'utf8', 'euc-jp');
    s|.*/(.*)\.ics$|$1|; # ディレクトリと拡張子を取り除くと、カレンダー名
    print "    " . $_ . "\n";
  }
  exit 0;
}

# ここからメインの処理ループ ##################################################
# イベント抽出ループとイベント整形フェイズからなる。
while (<ICAL>) {
  if (/^BEGIN:VEVENT/) {             # イベント定義に出会ったら
    $content = $sdate = $edate = $comment = "";

    while (<ICAL>) {                 # イベント抽出ループ
      if (/^END:VEVENT/) { last; }   # イベント定義が終了したら
      s|\n$||;                       # 入力行の行末の改行を削除

      if (/^ +/) {                   # 行頭が空白の時は、１行前はDESCRIPTIONで
        s|^ +||;                     # この行はその続きとみなす
        $comment = $comment . $_;    # この行の内容を前の行の内容に追加する
        next;                        # そして次の行へ
      }

      # タグと記述内容の抽出
      ($tag) = split(":");           # タグと内容のセパレータは ":"
      s/^[^:]+://; $val = $_;        # タグとセパレータをのぞいた残りが内容

      # タグの判別と内容の退避
      switch ($tag) {
        case /SUMMARY/     { $content = $val; }  # 用件
        case /DTSTART/     { $sdate   = $val; }  # 開始日時
        case /DTEND/       { $edate   = $val; }  # 終了日時
        case /DESCRIPTION/ { $comment = $val; }  # なんかコメントなど
      }
    }                                # イベント抽出ループ

    # イベント整形フェイズ
    $place = ""; $tmemo = "";
    &comment; # iCal のメモ欄の処理。$comment は $content の後ろに付けられる

    # 日付の処理
    $sdate =~ /^[0-9]{2}([0-9]{2})/; $y = $1;    # 開始年
    $sdate =~ /^[0-9]{4}([0-9]{2})/; $m = $1;    # 開始月
    $sdate =~ /^[0-9]{6}([0-9]{2})/; $d = $1;    # 開始日
    $date = $y . "/" . $m . "/" . $d;            # 開始年月日 == 終了年月日

    # 時刻の処理
    # 開始時刻と終了時刻を - で結んで、イベント内容の前に置く。開始と終了の両方
    # が :00 だったら、:00 は取り除く。
    if ($sdate =~ /T([0-9]{2})([0-9]{2})[0-9]{2}$/) { $stime = $1.":".$2; }
    else                                            { $stime = ""; }
    if ($edate =~ /T([0-9]{2})([0-9]{2})[0-9]{2}$/) { $etime = $1.":".$2; }
    else                                            { $etime = ""; }
    $time = $stime . "-" . $etime;
    if ($time =~ /:00.*:00/) { $time =~ s/:00//g; }
    if ($time eq "-") { $time = ""; }            # 時刻指定が全然ないとき
    else              { $time = $time . " "; }   # 時刻と内容の間に空白
    if ($tmemo ne "") { $time = $tmemo; }        # メモがあったらそっちを優先

    # 連想配列にイベントを放り込んでいく。キーは日付。
    $content = $place . $time . $content;
    $d = $date; # 単に notation を短くしたいだけ。
    if ($events{$d} == "") { $events{$d} = $content; }
    else                   { $events{$d} = $events{$d} . $SEP . $content; }
  } # -> 次のイベント定義を探しに行く
}

# イベント書きだし
foreach $d (keys %events) {
  @ev = split(/$SEP/, $events{$d});
  @ev = sort by_time(@ev);
  $events{$d} = $d . " ";
  foreach $e (@ev) {
    $events{$d} = $events{$d} . $e . ", ";
  }
  $events{$d} =~ s|\, $||;
}

@events = values(%events);
@events = sort(@events);
foreach $e (@events) {
  from_to($e, 'utf8', 'euc-jp'); # iCal 中の日本語はすべて UTF-8
  printf "%s\n",  $e;
}

# main loop 終了 ##############################################################

# コメント処理
# iCalのメモの部分をイベント名の後に出力する。 URLだったら、$content からリン
# クを張る。[ ] があったら $place に入れる。時刻っぽいものがあったら $tmemo に
# 入れる。
sub comment {
  $comstr = "";
  @clist = split(m|\\n|, $comment);  # 文字列 \n で分割
  foreach (@clist) {
    if (/\n$/) { chop($_); }         # 行末の改行文字を削除
    if (/^$/)  { next; }             # 元々空行のところだったらスキップ
    s|\\([",])|$1|g;                 # \" や \, をただの " や , に置き換え
    # まず URL を抽出してリンクを張る
    if (m|(http://[0-9A-Za-z-\.]+/[0-9A-Za-z#&=~./_\%\-]*)|) {
      $url = $1;                     # マッチした文字列を退避
      s/$url//;                      # URL を $_ から取り除く
      $content = "<A href=\"" . $url . "\">" . $content . "</A>";
    }
    # つぎに [...] で示されるのを行頭へ付けるために退避
    if (m|(\[[^\[\]]+\])|) {
      $plc = $1;
      s|\[[^\[\]]+\]||;
      $place = $plc . " ";
    }
    # 時刻を取り出して退避
    if (m|([012][0-9]:[0-5][0-9]-?)|) {
      $tmm = $1;
      s/$tmm//;
      $tmemo = $tmm . " ";
    }
    if (/^\s*$/) { next; }
    s|^\s+||;
    s|\s+$||;
    $comstr = $comstr . "(" . $_ . ")";
  }
  $comstr =~ s#\)\(#\, #g;
  if (length($comstr) > 100) {
    $comstr =~ s|(^[^,]+).*(\))$|\1\2|;
  }
  $content = $content . $comstr;
}

# 時刻でレコードをソートするための比較関数
sub by_time {
  $aa = $a; $bb = $b;
  $aa =~ s|\[[^\[\]]+\]||;                  # 場所を示す [ ] を取る
  $bb =~ s|\[[^\[\]]+\]||;
  $aa =~ s|:||;                             # 時刻の : を取る
  $bb =~ s|:||;
  if    ($aa gt $bb) {  1; }
  elsif ($aa lt $bb) { -1; }
  else               {  0; }
}
