#!/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@cbrc.jp # http://www.cbrc.jp/%7Etominaga/ # # 使用方法 # 1. Perl 5.8 をインストールし、perlコマンドの場所をこのスクリプトの1行目で # 指定する。 # 2. ターミナル上で、iCalのカレンダー名を引数としてこのスクリプトを実行する。 # 3. 標準出力に、CBRC-webな形式で予定が書き出される。 # A. 引数なしで特定のカレンダーを変換してほしいときは、このスクリプトの2行目 # にそのカレンダー名を記述する。日本語のカレンダー名を指定したいときは、こ # の方法が一番確実。このスクリプトは 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 をはさんで年月日8桁と時刻6桁であるとして、T よりも後があ # る時には時刻( T の直後4桁)をイベント名の前につける。なければつけない。 # 20020920 -> 2002年9月20日 # 20020920T123000 -> 2002年9月20日12時30分 # # ・理解するタグは全部で6種類のみ。このうち 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--"; # まず、2行目で指定されたファイル名を 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 () { if (/^BEGIN:VEVENT/) { # イベント定義に出会ったら $content = $sdate = $edate = $comment = ""; while () { # イベント抽出ループ if (/^END:VEVENT/) { last; } # イベント定義が終了したら s|\n$||; # 入力行の行末の改行を削除 if (/^ +/) { # 行頭が空白の時は、1行前は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 = "" . $content . ""; } # つぎに [...] で示されるのを行頭へ付けるために退避 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; } }