comparison Calcon.pm @ 1:144819f5d2f6

Initial revision
author kono
date Fri, 24 Jan 2003 13:41:18 +0900
parents
children cb79baed256e
comparison
equal deleted inserted replaced
0:111809a2ea45 1:144819f5d2f6
1 package Calcon;
2
3 ## $Id$
4
5 use 5.008;
6 use strict;
7 use warnings;
8
9 require Exporter;
10
11 our @ISA = qw(Exporter);
12
13 # Items to export into callers namespace by default. Note: do not export
14 # names by default without a very good reason. Use EXPORT_OK instead.
15 # Do not simply export all your public functions/methods/constants.
16
17 # This allows declaration use Calcon ':all';
18 # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
19 # will save memory.
20 our %EXPORT_TAGS = ( 'all' => [ qw(
21
22 ) ] );
23
24 our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
25
26 our @EXPORT = qw(
27
28 );
29
30 our $VERSION = '0.01';
31
32
33 # Preloaded methods go here.
34
35 # if you don't have NKF
36 # package Calcon::NKF;
37 #
38 # コード変換しなくても動くことは動くけど、いくつか問題がある。
39 #
40 # sub nkf {
41 # return shift(@_);
42 # }
43
44 # デバッグ中に本当にこのパッケージを見ているかどうかの確認用。
45 # print STDERR "new versoin!!\n";
46
47 #######################################################################/
48
49 package Calcon::Basic ;
50 use strict;
51 # use warnings;
52 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
53 @ISA = ();
54
55 # このパッケージ用の汎用ライブラリ。Date や Record などの
56 # ファクトリーもここにある。Read/Write の両方から参照される。
57 # Date/Record の実装を変えたいときは、ここを変更する。
58
59 my $date_class = 'Calcon::Date';
60 my $record_class = 'Calcon::Record';
61
62 sub new {
63 my ($this,$opts,$file) = @_;
64 # ClassName->new で呼び出される時のためにこれがある。Perl の決り文句。
65 my $class = ref($this) || $this;
66 my $self = {};
67 bless $self, $class;
68 # 入出力ファイル名
69 $self->{'-file'} = $file if ($file);
70 # $self->initialize();
71 $self->option($opts);
72 return $self;
73 }
74
75 # 下位クラスから呼び出される初期化。ここでは何もしない。しかし、
76 # 呼び出されるのだから用意しておく必要がある。
77
78 sub initialize {
79 my ($self) = @_;
80 }
81
82 # option 関係。
83
84 sub set_debug {
85 my ($self,$flag) = @_;
86 $self->{'-debug'} = $flag;
87 }
88
89 sub option {
90 my ($self,$option) = @_;
91
92 foreach my $opt ( $option =~ /./g ) {
93 if ($opt eq '-') {
94 } elsif ($opt eq 'n') {
95 $self->{'-file-out'} = 1;
96 } elsif ($opt eq 'd') {
97 $self->set_debug(1);
98 } elsif ($opt eq 'a') {
99 $self->{'-address-only'} = 1;
100 } elsif ($opt eq 'c') {
101 $self->{'-calendar-only'} = 1;
102 } elsif ($opt eq 'F') {
103 $self->{'-future-only'} = 1;
104 } elsif ($opt eq 't') {
105 $self->{'-tomorrow'} = 1;
106 } elsif ($opt eq 'C') {
107 $self->{'-count'} = 5;
108 }
109 }
110 }
111
112 # デバッグ用レコード表示ルーチン。
113
114 sub show {
115 my ($self,$record) = @_;
116 $record->show();
117 }
118
119 # 時間関係のライブラリ
120
121 sub localtime {
122 my ($self,$date) = @_;
123 return $date->localtime();
124 }
125
126 sub date {
127 my ($self,$date) = @_;
128 return $date->date();
129 }
130
131 sub today {
132 $date_class->today;
133 }
134
135 sub unix_time {
136 my ($self,$date) = @_;
137 return $date->unix_time();
138 }
139
140 # Factory Pattern
141
142 sub make_date_unix {
143 my ($self,$date) = @_;
144 return $date_class->make_date_unix($date);
145 }
146
147 sub make_date {
148 my ($self,$date) = @_;
149 return $date_class->make_date($date);
150 }
151
152 sub make_record {
153 my ($self) = @_;
154 my %record;
155 my $record = \%record;
156 bless $record,$record_class;
157 }
158
159 #######################################################################/
160
161 package Calcon::Record ;
162 use strict;
163 # use warnings;
164 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
165 use Time::Local;
166 @ISA = ();
167
168 # 変換に用いる中間データ形式。オブジェクトにすると、デバッグの
169 # 時に便利。
170
171 sub show {
172 my ($self) = @_;
173 foreach my $key (keys %$self) {
174 my $value = $self->{$key};
175 if (ref $value) {
176 $value = $value->value();
177 }
178 print "$key: $value\n" if (defined($value) && $value ne '');
179 }
180 print "\n";
181 }
182
183 # 中身を文字列で返す。
184
185 sub value {
186 my ($self) = @_;
187 my $data;
188 foreach my $key (keys %$self) {
189 my $value = $self->{$key};
190 if (ref $value) {
191 $value = $value->value();
192 }
193 $data .= "$key: $value\n" if (defined($value) && $value ne '');
194 }
195 $data;
196 }
197
198 # 等しいかどうか
199
200 sub equal {
201 my ($self,$record) = @_;
202
203 foreach my $key (keys %{$self}) {
204 next if (!defined $self->{$key} && !defined $record->{$key});
205 if(ref $self->{$key} && ref $record->{$key}) {
206 return 0 if (! $self->{$key}->equal($record->{$key}));
207 } else {
208 return 0 if ($self->{$key} ne $record->{$key});
209 }
210 }
211 return 1;
212 }
213
214 # 与えられたレコードリストに含まれる情報しか持っていないかどうか
215
216 sub information_in_list {
217 my ($self,$records) = @_;
218
219 my $lines;
220 foreach my $record (@$records) {
221 foreach my $key (keys %{$record}) {
222 my $value;
223 if (ref $record->{$key}) {
224 $value = $record->{$key}->value();
225 } else {
226 $value = $record->{$key};
227 }
228 foreach my $line (split(/\n/,$value)) {
229 $line =~ s/\s+/ /g;
230 next if (! $line);
231 $lines->{$line} = $key;
232 }
233 }
234 }
235 return $lines;
236 }
237
238 # 与えられたレコードリストに対して相対的に新しい情報だけのレコードを作る。
239
240 sub new_information {
241 my ($self,$records) = @_;
242 my $lines = $self->information_in_list($records);
243
244 my $info;
245 foreach my $key (keys %{$self}) {
246 my $value;
247 if (ref $self->{$key}) {
248 $value = $self->{$key}->value();
249 } else {
250 $value = $self->{$key};
251 }
252 foreach my $line (split(/\n/,$value)) {
253 $line =~ s/\s+/ /g;
254 next if (! $line);
255 next if (defined $lines->{$line}) ;
256 if (defined $info->{$key}) { $info->{$key} .= "\n$line";}
257 else { $info->{$key} .= $line; }
258 }
259 }
260 if(defined $info) {
261 bless $info ;
262
263 # 必要なキーを残す
264
265 $info->{'-date'} = $records->[0]->{'-date'}
266 if (defined ($records->[0]->{'-date'})) ;
267 $info->{'-name'} = $records->[0]->{'-name'}
268 if (defined ($records->[0]->{'-name'})) ;
269 # else error だけど、まぁ、良い。
270 }
271 $info;
272 }
273
274 # 与えられたリストにおなじ値を持つレコードが含まれているかどうか
275
276 sub is_included {
277 my ($self,$records) = @_;
278 my $lines = $self->information_in_list($records);
279
280 foreach my $key (keys %{$self}) {
281 my $value;
282 if (ref $self->{$key}) {
283 $value = $self->{$key}->value();
284 } else {
285 $value = $self->{$key};
286 }
287 foreach my $line (split(/\n/,$value)) {
288 $line =~ s/\s+/ /g;
289 next if (! $line);
290 return 0 if (! defined $lines->{$line}) ;
291 }
292 }
293 return 1;
294 }
295
296 #######################################################################/
297
298 package Calcon::Date ;
299 use strict;
300 # use warnings;
301 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
302 use Time::Local;
303 @ISA = ();
304
305 # 日付に関するオブジェクト
306 # Perl に標準なものがあるんだろうけど。
307 # record とおなじインタフェースを持つべき
308
309 my @monthname = ( 'Jan','Feb', 'Mar', 'Apr', 'May', 'Jun',
310 'Jul','Aug','Sep','Oct','Nov', 'Dec');
311 my %monthname;
312 my $i;
313 foreach my $name (@monthname) { $monthname{$name} = $i++; }
314
315 # use unix time scalar as an object
316 # < 1902/1/1-12/31 date in every year
317 # 1903/1/1 00:00-23:59 time in evey day
318 # 1903/1/1-7 every weekday
319 # It is better to use [$date,$tags] array for this class.
320 # あんまり良い実装じゃないね。せこすぎ。
321
322 my $every_day_min = timelocal(0,0,0,1,0,1902);
323 my $every_day_max = timelocal(0,0,0,1,0,1903);
324 my $every_time_min = timelocal(0,0,0,1,0,1903);
325 my $every_time_max = timelocal(59,59,23,1,0,1903);
326 my $every_weekday_min = timelocal(0,0,0,4,0,1903); # Sunday
327 my $every_weekday_max = timelocal(0,0,0,11,0,1903);# Sunday
328
329 my $today = time - 24*3600;
330
331 my %week = (
332 'Sun'=> timelocal(0,0,0,4,0,1903),
333 'Mon'=> timelocal(0,0,0,5,0,1903),
334 'Tue'=> timelocal(0,0,0,6,0,1903),
335 'Wed'=> timelocal(0,0,0,7,0,1903),
336 'Thu'=> timelocal(0,0,0,8,0,1903),
337 'Fri'=> timelocal(0,0,0,9,0,1903),
338 'Sat'=> timelocal(0,0,0,10,0,1903),
339 );
340 my @week_name = (
341 'Sun',
342 'Mon',
343 'Tue',
344 'Wed',
345 'Thu',
346 'Fri',
347 'Sat',
348 );
349
350 sub is_allday {
351 my ($self) = @_;
352 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
353 localtime($$self);
354 return ($sec==0 && $min==0 && $hour==0);
355 }
356
357 sub is_day {
358 my ($self) = @_;
359 return ( $every_day_min <= $$self && $$self < $every_day_max );
360 }
361
362 sub is_time {
363 my ($date) = @_;
364 return ( $every_time_min <= $$date && $$date < $every_time_max );
365 }
366
367 sub future {
368 my ($self) = @_;
369 return ( $$self >= $today );
370 }
371
372 sub tomorrow {
373 my ($self) = @_;
374 return ( $today+24*3600*2 >= $$self && $$self >= $today-24*3600/2);
375 }
376
377 sub is_weekday {
378 my ($date) = @_;
379 return ( $every_weekday_min <= $$date && $$date < $every_weekday_max );
380 }
381
382 sub localtime {
383 my ($self) = @_;
384 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
385 localtime($$self);
386 return ($year+1900,$mon+1,$mday,$hour,$min);
387 }
388
389 sub make_date {
390 my ($self,$date) = @_;
391 my ($year,$month,$day,$hour);
392 my ($sec,$min);
393
394 $hour = $min = $sec = 0;
395
396 if ($date =~ m-(\d+)/(\d+)/(\d+)-) {
397 # $year = $1 - 1900; this is no longer good for timelocal
398 $year = $1;
399 $month = $2-1;
400 $day = $3;
401 } elsif ($date =~ m-(\d+)/(\d+)-) {
402 $year = 1902;
403 $month = $1-1;
404 $day = $2;
405 } else {
406 if ($week{$date}) {
407 my $weekday = $week{$date};
408 bless $date;
409 return $date;
410 }
411 if ($date =~ m-(\d+):(\d+)-) {
412 $hour = $1;
413 $min = $2;
414 }
415 $year = 1903; $month = 0; $day = 1;
416 return &make_date1($year,$month,$day,$hour,$min,$sec);
417 }
418 if ($date =~ m-(\d+):(\d+)-) {
419 $hour = $1;
420 $min = $2;
421 }
422 return &make_date1($year,$month,$day,$hour,$min,$sec);
423 }
424
425 sub make_date1 {
426 my ($year,$month,$day,$hour,$min,$sec) = @_;
427 my ($date,$self);
428
429 if ( eval '$date = timelocal($sec,$min,$hour,$day,$month,$year)' ) {
430 } else {
431 $date = timelocal(0,0,0,1,0,70);
432 }
433 $self = \$date;
434 bless $self;
435 }
436
437 sub make_date_unix {
438 my ($self,$date) = @_;
439 $self = \$date;
440 bless $self;
441 }
442
443 sub date {
444 my ($self) = @_;
445 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
446 CORE::localtime($$self);
447 my $date;
448 if ($self->is_day()) {
449 $date = ($mon+1)."/$mday";
450 } elsif ($self->is_weekday()) {
451 return $week_name[$wday];
452 } elsif ($self->is_time()) {
453 $date = sprintf(" %02d:%02d",$hour,$min) if ($hour || $min);
454 } else {
455 $date = ($year+1900)."/".($mon+1)."/$mday";
456 $date .= sprintf(" %02d:%02d",$hour,$min) if ($hour || $min);
457 }
458 return $date;
459 }
460
461 sub unix_time {
462 my ($self) = @_;
463 $$self;
464 }
465
466 sub add {
467 my ($self,$add) = @_;
468 my ($result);
469 $result = $$self + $add;
470 bless \$result;
471 }
472
473 sub date_after {
474 my ($self,$day2) = @_;
475 return $$self<$$day2;
476 }
477
478 sub today {
479 my $today = time;
480 bless \$today;
481 }
482
483 # record のインタフェース
484
485 sub show {
486 my ($self) = @_;
487 print $self->date();
488 }
489
490 sub value {
491 my ($self) = @_;
492 $self->date();
493 }
494
495 sub equal {
496 my ($self,$date) = @_;
497 return ($self->unix_time() != $date->unix_time());
498 }
499
500 #######################################################################/
501
502 package Calcon::Reader ;
503 use strict;
504 # use warnings;
505 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
506 @ISA = ( 'Calcon::Basic' );
507
508 # Reader の基底クラス
509
510 # Reader は decode method を持つ必要がある。
511
512 sub decode {
513 my ($self) = @_;
514 }
515
516 sub set_output{
517 my ($self,$out) = @_;
518 $self->{'-output'} = $out;
519 }
520
521 # date_normalize は Date クラスに変換するので、Reader は必ず
522 # 呼ぶ必要がある。少し汎用すぎるか?
523
524 sub date_normalize {
525 my ($self,$keys,$record) = @_;
526 my ($sday,$stime,$eday,$etime);
527
528 if ($record->{'birth'}) {
529 $record->{'birth'} = $self->make_date($record->{'birth'});
530 }
531 if ($record->{'modify-date'}) {
532 $record->{'modify-date'} = $self->make_date($record->{'modify-date'});
533 }
534 return if (! $record->{'date'}); # internal error
535 # print ">**$record->{'date'}***\n";
536 # print ">**$record->{'end-date'}***\n";
537 # print ">**$record->{'time'}***\n";
538 # print ">**$record->{'end-time'}***\n";
539
540 if ($record->{'time'} =~ /(\d+:\d+)\s*-\s*(\d+:\d+)/) {
541 $stime = $1; $etime = $2;
542 } elsif ($record->{'time'} =~ /(\d+:\d+)/) {
543 $stime = $1;
544 }
545 if ($record->{'date'} =~ /(\d+\/\d+\/\d+).*\s+(\d+:\d+)\s*-(\d+\/\d+\/\d+).*\s*(\d+:\d+)/) {
546 $sday = $1; $stime = $2; $eday = $3; $etime = $4;
547 } elsif ($record->{'date'} =~ /(\d+\/\d+\/\d+).*\s+(\d+:\d+)\s*-\s*(\d+:\d+)/) {
548 $sday = $1; $stime = $2; $etime = $3;
549 } elsif ($record->{'date'} =~ /(\d+\/\d+\/\d+).*\s+(\d+:\d+)/) {
550 $sday = $1; $stime = $2;
551 } elsif ($record->{'date'} =~ /(\d+\/\d+\/\d+)/) {
552 $sday = $1;
553 }
554
555 # これらのチェックで end-time などが作られてしまうみたい。本来は、
556 # defined で避けるべきなんだろうが...
557
558 if ($record->{'end-time'} =~ /(\d+:\d+)/) {
559 $etime = $1;
560 }
561 if ($record->{'end-date'} =~ /(\d+\/\d+\/\d+).*\s+(\d+:\d+)/) {
562 $eday = $1; $etime = $2;
563 } elsif ($record->{'end-date'} =~ /(\d+\/\d+\/\d+)/) {
564 $eday = $1;
565 } elsif ( $etime ) {
566 $eday = $sday;
567 }
568
569 $sday = $self->make_date("$sday $stime");
570 if ($eday) {
571 $eday = $self->make_date("$eday $etime");
572 if ($eday->date_after($sday)) {
573 undef $eday;
574 }
575 }
576
577 # いったん消しておいて、
578 foreach my $key ('end-date','date', 'time','end-time') {
579 undef $record->{$key};
580 }
581 @$keys = grep(!/^end-date|^date|^time|^end-time/,@$keys);
582
583 # もう一回作る。まったくね。
584
585 # print "@$keys\n";
586 if ($eday) {
587 $record->{'end-date'} = $eday;
588 unshift(@$keys,'end-date');
589 }
590 $record->{'date'} = $sday;
591 unshift(@$keys,'date');
592
593 # print "@$keys\n";
594 # print "***$record->{'date'}***\n";
595 # print "***$record->{'end-date'}***\n";
596 }
597
598 #######################################################################/
599
600 package Calcon::Writer ;
601 use strict;
602 # use warnings;
603 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
604 @ISA = ( 'Calcon::Basic' );
605 use Carp;
606
607 # Writer の基底クラス
608
609 # Why this class is necessary?
610 sub initialize {
611 my ($self) = @_;
612
613 # 書き出しファイルの切替え
614 # directory などに出力する場合は、-file を undef する。
615 if (defined $self->{'-file'}) {
616 open(OUT,">".$self->{'-file'}) or
617 croak("Can't open $self->{'-file'}:$!\n");
618 select OUT;
619 }
620 # いらないのは知っているが、拡張するかも知れないので。
621 $self->SUPER::initialize();
622 }
623
624 # Writer の基本インタフェース (必ず上書きされる)
625 # Perl にもインタフェースが欲しいよね。
626
627 sub start_file {
628 my ($self,$type) = @_;
629 }
630
631 sub end_file {
632 my ($self,$type) = @_;
633 }
634
635 sub record {
636 my ($self,$record,$key) = @_;
637 }
638
639
640 #######################################################################/
641
642 package Calcon::File_write ;
643
644 # ファイル形式への書き出し
645 # key: データ
646 # レコードのセパレータは "\n\n"
647
648 use strict;
649 # use warnings;
650 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
651 @ISA = ('Calcon::Writer');
652
653 sub record {
654 my ($self,$keys,$items) = @_;
655 my @keys = @$keys;
656 my %items = %$items;
657 # should be override
658 if ($items->{'date'}) { return if ($self->{'-future-only'} && ! $items->{'date'}->future()); }
659 foreach my $key (@keys) {
660 my $value = $items{$key};
661 if (ref $value) {
662 $value = $value->value();
663 }
664 print "$key: $value\n" if (defined($value) && $value ne '');
665 }
666 print "\n";
667 }
668
669 #######################################################################/
670
671 package Calcon::Print_write ;
672
673 # 印刷形式。login時に表示するコンパクトな形式。
674
675 use strict;
676 # use warnings;
677 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
678 use NKF;
679 @ISA = ('Calcon::Writer');
680
681 sub initialize {
682 my ($self) = @_;
683 $self->SUPER::initialize();
684 if ($self->{'-tomorrow'}) {
685 $self->{'-count'} = 5;
686 } else {
687 $self->{'-count'} = -1;
688 }
689 }
690
691 sub record {
692 my ($self,$keys,$items) = @_;
693 my @keys = @$keys;
694 my %items = %$items;
695 # should be override
696 if (defined $items->{'date'}) {
697 my $date = $items->{'date'};
698 return if ($self->{'-future-only'} && ! $date->future());
699 return if ($self->{'-tomorrow'} && ! $date->tomorrow());
700 return if ($self->{'-count'} == 0);
701 $self->{'-count'} --;
702 $date = $date->date();
703 my $memo = $items->{'memo'};
704 $memo =~ s/\n+$//;
705 if ($self->{'-tomorrow'}) {
706 print nkf('-e',"$date:\t$memo\n");
707 } else {
708 $memo =~ s/^/$date:\t/mg;
709 print nkf('-e',"$memo\n");
710 }
711 } else {
712 foreach my $key (@keys) {
713 my $value = $items{$key};
714 if (ref $value) {
715 $value = $value->value();
716 }
717 print nkf('-e',"$key: $value\n") if (defined($value) && $value ne '');
718 }
719 print "\n";
720 }
721 }
722
723 #######################################################################/
724
725 package Calcon::Zaurus;
726 use strict;
727 # use warnings;
728 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
729 @ISA = ();
730
731 # ザウルス関連の基底クラス
732 # フレーバとして使うので new がない。
733 # 使用するクラスはZaurus_initialize を呼び出す必要がある。
734
735 my %item_type = (
736 'ADR1'=>'s', 'ADR2'=>'s', 'ALRM'=>'d', 'ANN1'=>'d', 'ANN2'=>'d', 'ATSC'=>'u',
737 'ATTM'=>'u', 'ATTR'=>'u', 'BRTH'=>'d', 'CFIP'=>'s', 'CHK1'=>'b', 'CHK2'=>'b',
738 'CHK3'=>'b', 'CHK4'=>'b', 'CLAS'=>'s', 'CLSC'=>'u', 'CNTC'=>'u', 'COLR'=>'u',
739 'CPS1'=>'s', 'CTGR'=>'u', 'DB01'=>'u', 'DB02'=>'u', 'DB03'=>'u', 'DB04'=>'u',
740 'DB05'=>'u', 'DB06'=>'u', 'DB07'=>'u', 'DB08'=>'u', 'DB09'=>'u', 'DB10'=>'u',
741 'DB11'=>'u', 'DB12'=>'u', 'DB13'=>'u', 'DB14'=>'u', 'DB15'=>'u', 'DB16'=>'u',
742 'DB17'=>'u', 'DB18'=>'u', 'DB19'=>'u', 'DB20'=>'u', 'DB21'=>'u', 'DB22'=>'u',
743 'DB23'=>'u', 'DB24'=>'u', 'DB25'=>'u', 'DB26'=>'u', 'DB27'=>'u', 'DB28'=>'u',
744 'DBFN'=>'u', 'DBID'=>'u', 'DBIT'=>'u', 'DBSI'=>'u', 'DBST'=>'u', 'DNS1'=>'s',
745 'DNS2'=>'s', 'ECDT'=>'u', 'EDDY'=>'d', 'EDTM'=>'d', 'ETDY'=>'d', 'FAX1'=>'s',
746 'FAX2'=>'s', 'FINF'=>'b', 'FNDY'=>'d', 'HOL1'=>'d', 'HTXT'=>'h', 'IMG1'=>'i',
747 'IMGF'=>'g', 'IMJG'=>'j', 'IORR'=>'b', 'LKDT'=>'d', 'LKIF'=>'u', 'LTDY'=>'d',
748 'MAL1'=>'s', 'MARK'=>'u', 'MEM1'=>'s', 'MLAD'=>'s', 'MLCC'=>'s', 'MLFM'=>'s',
749 'MLID'=>'u', 'MLRP'=>'s', 'MLTO'=>'u', 'MPFB'=>'s', 'NAME'=>'s', 'NAPR'=>'s',
750 'NMSK'=>'s', 'OFCE'=>'s', 'OFPR'=>'s', 'OPT1'=>'u', 'OPT2'=>'u', 'PGR1'=>'s',
751 'POPA'=>'s', 'POPP'=>'s', 'PRBD'=>'u', 'PRF1'=>'u', 'PRTY'=>'u', 'PSTN'=>'s',
752 'PSWD'=>'s', 'RCCK'=>'b', 'RDCK'=>'b', 'RMRK'=>'s', 'RVTM'=>'u', 'SBJT'=>'u',
753 'SCCP'=>'s', 'SCTG'=>'u', 'SCTN'=>'s', 'SDDT'=>'d', 'SDTM'=>'u', 'SPKS'=>'s',
754 'STDY'=>'d', 'SVAD'=>'s', 'TCPS'=>'u', 'TEL1'=>'s', 'TEL2'=>'s', 'TIM1'=>'d',
755 'TIM2'=>'d', 'TITL'=>'s', 'TMNL'=>'u', 'USID'=>'s', 'XLIF'=>'u', 'ZCCP'=>'s',
756 'ZIP2'=>'s', 'ZIPC'=>'s', 'ZPKS'=>'s', 'ZRTF'=>'u', 'ZXLS'=>'u', 'mDTM'=>'d',
757 'mISC'=>'u', 'tPID'=>'u',
758 );
759
760 my %item_name = (
761 'FNDY'=>'finish-date',
762 'ETDY'=>'start-date',
763 'LTDY'=>'deadline',
764 'STDY'=>'start-date',
765 'ADR1'=>'home-address',
766 'ADR2'=>'address',
767 'ANN1'=>'anniversary',
768 'BRTH'=>'birth',
769 'CLAS'=>'class',
770 'CPS1'=>'mobile-tel',
771 'DNS1'=>'DNS 1',
772 'DNS2'=>'DNS 2',
773 'EDTM'=>'edit-time',
774 'FAX1'=>'home-fax',
775 'FAX2'=>'fax',
776 'HTXT'=>'hand-text',
777 'IMG1'=>'image',
778 'IMGF'=>'gif',
779 'IMJG'=>'jpg',
780 'LKDT'=>'link-date',
781 'MAL1'=>'mail',
782 'MEM1'=>'memo',
783 'MLAD'=>'mail-adderess',
784 'MLTO'=>'mail-to',
785 'NAME'=>'name',
786 'NAPR'=>'name-yomi',
787 'NMSK'=>'mask',
788 'OFCE'=>'office',
789 'OFPR'=>'office-yomi',
790 'POPA'=>'pop 1',
791 'POPP'=>'pop p',
792 'PSTN'=>'position',
793 'PSWD'=>'password',
794 'RMRK'=>'remark',
795 'SCCP'=>'sccp',
796 'SCTN'=>'section',
797 'SDTM'=>'sdtm',
798 'SPKS'=>'spks',
799 'SVAD'=>'cvad',
800 'TEL1'=>'home-tel',
801 'TEL2'=>'tel',
802 'TIM1'=>'date',
803 'TIM2'=>'end-date',
804 'TITL'=>'title',
805 'USID'=>'user id',
806 'ZCCP'=>'zccp',
807 'ZIP2'=>'home-zip',
808 'ZIPC'=>'zip',
809 'ZPKS'=>'packats',
810 'mDTM'=>'modify-date',
811 );
812
813
814 sub Zaurus_initialize {
815 my ($self) = @_;
816 $self->{'-item_type'} = \%item_type;
817 $self->{'-item_name'} = \%item_name;
818 $self->{'-offset'} = 8;
819 }
820
821 # ザウルスのBOX形式に格納されている属性名リストの取出
822
823 sub item_list {
824 my ($self,$data) = @_;
825 my ($value,@index);
826 my ($debug) = $self->{'-debug'};
827
828 my $title_offset;
829 my $title_len = 0;
830 my $field_offset;
831
832 my $version = unpack("n",substr($data,2,2));
833 $self->{'-zaurus-version'} = $version;
834 # $title_offset += ($version < 0x1030)?2:0;
835
836 if ($version <= 0x1002 ) {
837 $title_offset = 0x15;
838 $self->{'-title-begin'} = $title_offset;
839 $field_offset = 1;
840 } elsif ($version < 0x1030 ) {
841 $title_offset = unpack("V",substr($data,0x8,4));
842 $self->{'-title-begin'} = $title_offset;
843 $title_offset += 2;
844 $field_offset = 2;
845 } else {
846 $title_offset = unpack("V",substr($data,0x8,4));
847 $self->{'-title-begin'} = $title_offset;
848 $field_offset = 2;
849 }
850
851 my $title_count = ord(substr($data,$title_offset,1));
852 my $ptr = $title_offset+1;
853 my $i = 0;
854 print "\n\nfile:",$self->{'-file'},"\n\n"
855 if ($debug && defined ($self->{'-file'}));
856 while($title_count-->0) {
857 my $item_len = ord(substr($data,$ptr,1));
858 $ptr += 2;
859 # print "item: ",unpack("H*",substr($data,$ptr,$item_len)) if ($debug);
860 my $id = $self->{'-item_id'}->[$i] = substr($data,$ptr+$field_offset,4);
861 my $name = $self->{'-item_name1'}->[$i] =
862 substr($data,$ptr+5,$item_len-5);
863 print "list:\t$i:$id:$item_len:$name\n" if ($debug);
864 $ptr += $item_len;
865 $i++;
866 }
867 print "title-len: $version $title_len ",$ptr - $title_offset,"\n" if ($debug);
868 $self->{'-item_name_count'} = $i;
869 $self->{'-title-length'} = $ptr-$title_offset;
870 }
871
872
873 #######################################################################/
874
875 package Calcon::Zaurus_read ;
876
877 # BOX 形式からの読み込み
878
879 use strict;
880 # use warnings;
881 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
882 @ISA = ('Calcon::Zaurus', 'Calcon::Reader');
883
884 sub initialize {
885 my ($self) = @_;
886 $self->SUPER::initialize();
887 $self->Zaurus_initialize();
888
889 $self->{'-debug'} = 0;
890 $self->{'-offset'} = 8;
891 $self->{'-all'} = 0;
892 $self->{'-item_list'} = ''; # '' or 'original' or 'id'
893 }
894
895 sub read {
896 my ($self,$file) = @_;
897
898 $self->{'-file'} = $file;
899 open(F,"<".$file);
900
901 local($/) ;
902 undef $/;
903 my $data = <F>;
904 $data;
905 }
906
907 sub decode {
908 my ($self,$file) = @_;
909 my ($debug) = $self->{'-debug'};
910 my $out = $self->{'-output'};
911
912 my $data = $self -> read($file);
913 $self -> item_list($data);
914 $out->start_file($file);
915 print "Zaurus version: $self->{'-zaurus-version'}\n" if ($debug);
916 if ($self->{'-zaurus-version'} <= 0x1002) {
917 $self->decode_old_data($data);
918 } elsif ($self->{'-zaurus-version'} == 0x1030) {
919 $self->{'-offset'} = 10;
920 $self->decode_data($data);
921 } else {
922 $self->decode_data($data);
923 }
924 $out->end_file($file);
925 }
926
927 # 複雑なIndexの処理
928
929 sub decode_index {
930 my ($self,$data) = @_;
931 my ($debug) = $self->{'-debug'};
932
933 my ($length) = unpack("V",substr($data,0x10,4));
934 if ($self->{'-zaurus-version'} eq 0x1030) {
935 $length = unpack("V",substr($data,0x8,4));
936 }
937 my $offset = 0x50;
938 my ($value,@index);
939 my $i;
940 my $flag;
941
942 do {
943 for($i=$offset;$i<$length;$i+=4) {
944 $value = unpack("V",substr($data,$i,4));
945 next if ($value == 0xffffffff);
946 push(@index,$value) if ($value);
947 }
948 $offset = $value;
949 $flag = unpack("v",substr($data,$offset,2));
950
951 printf "next index %0x: %0x\n",$offset,"" if ($debug);
952 printf "flag: %0x\n",$flag if ($debug);
953
954 if ($self->{'-zaurus-version'} eq 0x1030) {
955 $length = unpack("V",substr($data,$offset+2,4));
956 $offset = $offset+6;
957 $length += $offset;
958 } else {
959 $length = unpack("v",substr($data,$offset+2,2));
960 $offset = $offset+5;
961 $length += $offset;
962 }
963 printf "next index length %0x\n",$length if ($debug);
964
965 } while ($flag == 0xfff0);
966
967 return @index;
968 }
969
970 # BOX形式の中のレコードの処理
971
972 sub decode_data {
973 my ($self,$data) = @_;
974 my ($offset) = $self->{'-offset'};
975 my ($debug) = $self->{'-debug'};
976
977 my(@index) = $self->decode_index($data);
978
979 foreach my $index (@index) {
980 printf "index %0x: %s\n",$index,"" if ($debug);
981
982 last if (length(substr($data,$index,2))<2);
983 next if (substr($data,$index,2) eq "\xf0\xff");
984
985 my $record_number=ord(substr($data,$index,1)) +
986 ord(substr($data,$index+1,1))*256;
987 my $record_len=ord(substr($data,$index+2,1)) +
988 ord(substr($data,$index+3,1))*256;
989
990 my $item_count=ord(substr($data,$index+6,1));
991 my $item_dummy=ord(substr($data,$index+10,1));
992
993 my @len = ();
994 my $ptr = $index + $offset;
995 my $total_len = 0;
996 my $k = 1;
997 for(my $j=$self->{'-item_name_count'}; $j>0; $j--) {
998 my $i=ord(substr($data,$ptr,1));
999 if ($i>=0x80) {
1000 $ptr++;
1001 $i = ord(substr($data,$ptr,1))+($i-0x80)*256;
1002 }
1003 print "len:$k: $i\n" if ($debug);
1004 $k++;
1005 push(@len,$i);
1006 $total_len += $i;
1007 $ptr++;
1008 }
1009 printf "offset: %x\n",$ptr-$index if ($debug);
1010
1011 # $ptr = $index+40+$item_dummy; should be this kind of method...
1012 # $ptr = $index+$record_len-$total_len+5;
1013 # $ptr = $index+8+$item_count;
1014
1015
1016 print "head: ",unpack("H*",substr($data,$index,50)),"\n" if ($debug);
1017 print "body: ",unpack("H*",substr($data,$ptr,50)),"\n" if ($debug);
1018
1019 my $i = 0;
1020 my $record = $self->make_record;
1021 my @key_list = ();
1022 foreach my $len (@len) {
1023 my ($key,$item,$type) =
1024 $self->decode_item($i,substr($data,$ptr,$len));
1025 if ($item) {
1026 if ($type eq 's' || $type eq 'd') {
1027 push(@key_list,$key);
1028 $record->{$key} = $item;
1029 } elsif ($self->{'-all'}) {
1030 push(@key_list,$key);
1031 $record->{$key} = $type.":".unpack("H*",$item);
1032 }
1033 }
1034 $i++;
1035 $ptr += $len;
1036 }
1037 $self->date_normalize(\@key_list,$record);
1038 $self->{'-output'}->record(\@key_list,$record);
1039 print "\n" if ($debug);;
1040 }
1041 }
1042
1043 # たぶん、PI-7000以前の形式
1044
1045 sub decode_old_data {
1046 my ($self,$data) = @_;
1047 my $debug = $self->{'-debug'};
1048 my @len = ();
1049 my $ptr = $self->{'-title-begin'} + $self->{'-title-length'};
1050
1051 my $old_number = 0;
1052 while(1) {
1053 my $record = $self->make_record;
1054 my @key_list = ();
1055
1056 # my $record_number = ord(substr($data,$ptr++,1));
1057 my $record_number = unpack("v",substr($data,$ptr,2));
1058 my $optr = $ptr;
1059 while ($record_number != $old_number+1) {
1060 # $record_number = ord(substr($data,$ptr++,1));
1061 $ptr += 1;
1062 $record_number = unpack("v",substr($data,$ptr,2));
1063 return if ($ptr>length($data));
1064 }
1065 print "offset: ",$ptr-$optr,"\n" if ($debug && $optr<$ptr);
1066 $ptr += 2;
1067 my $record_length = unpack("v",substr($data,$ptr,2));
1068 $ptr += 2;
1069 print "record_number: $record_number\n" if ($debug);
1070 print "record_length: $record_length\n" if ($debug);
1071 $old_number = $record_number;
1072 # last if ($record_length == 0);
1073 my $record_end = $optr + $record_length+4; # - 3;
1074 my $i = 0;
1075 $ptr+=2;
1076 for(my $j=$self->{'-item_name_count'}; $j>0; $j--) {
1077 # while($ptr < $record_end) {
1078 my $len=ord(substr($data,$ptr++,1));
1079 if ($len>=0x80) {
1080 $len = ord(substr($data,$ptr,1))+($len-0x80)*256;
1081 $ptr++;
1082 }
1083 print "len: $len\n" if ($debug);
1084 print "data: ",substr($data,$ptr,$len),"\n" if ($debug);
1085 my ($key,$item,$type) =
1086 $self->decode_item($i,substr($data,$ptr,$len));
1087 if ($item) {
1088 if ($type eq 's' || $type eq 'd') {
1089 push(@key_list,$key);
1090 $record->{$key} = $item;
1091 } elsif ($self->{'-all'}) {
1092 push(@key_list,$key);
1093 $record->{$key} = $type.":".unpack("H*",$item);
1094 }
1095 }
1096 $i++;
1097 $ptr += $len;
1098 }
1099 if ($debug && $ptr != $record_end) {
1100 print "record_end: $ptr $record_end\n";
1101 }
1102 $ptr = $record_end;
1103 print "\n" if ($debug);;
1104 $self->date_normalize(\@key_list,$record);
1105 $self->{'-output'}->record(\@key_list,$record);
1106 # }
1107 }
1108 }
1109
1110 sub decode_time {
1111 my ($self,$t) = @_;
1112
1113 return '' if (! $t);
1114 # print unpack("H*",substr($t,1,4)),"\n";
1115
1116 $t = hex(unpack("H*",substr($t,1,4)));
1117 my $year = ($t&0x0000000f)*16 ;
1118 $year += (($t&0x0000f000)>>12) + 1900;
1119 my $month = ($t&0x00000f00)>>8;
1120 my $day = ($t&0x00f80000)>>19;
1121 my $min = ($t&0x3f000000)>>24;
1122 my $hour =((($t&0xc0000000)>>30)&0x3)<<0;
1123 $hour += (($t&0x00070000)>>16)<<2;
1124 if ($year == 2155) { # unspecified case
1125 $t = sprintf("%d/%d",$month,$day);
1126 } else {
1127 $t = sprintf("%04d/%d/%d",$year,$month,$day);
1128 }
1129 if($min!=63) {
1130 $t .= sprintf(" %02d:%02d",$hour,$min);
1131 }
1132 $t;
1133 }
1134
1135 # Zaurus レコード中の可変長データを属性名とともに変換する。
1136
1137 sub decode_item {
1138 my ($self,$i,$item) = @_;
1139 my $all = $self->{'-all'};
1140 my $debug = $self->{'-debug'};
1141
1142 return if (! $item);
1143 # print $self->{'-item_id'}->[$i],": ",unpack("H*",$item),"\n";
1144 my $id_name = $self->{'-item_id'}->[$i];
1145 my $id_type = $self->{'-item_type'}->{$id_name};
1146
1147 if ($self->{'-item_list'} eq 'original') {
1148 $id_name = $self->{'-item_name1'}->[$i];
1149 } elsif ($self->{'-item_list'} eq 'id') {
1150 } elsif (defined $self->{'-item_name'}->{$id_name}) {
1151 $id_name = $self->{'-item_name'}->{$id_name};
1152 }
1153
1154 if ( $id_type eq 'd' ) {
1155 $item = $self->decode_time($item);
1156 }
1157 return ($id_name,$item,$id_type);
1158 }
1159
1160 #######################################################################/
1161
1162 package Calcon::Pool;
1163
1164 # 差分などを取るための中間的なレコードバッファ
1165 # Unix の pipe みたいに使う
1166 # Writer/Reader を両方継承すべきかも知れない。けど、今のところ、Reader
1167 # を継承する利点は無い。decode ではなく、output を呼ぶ。
1168
1169 use strict;
1170 # use warnings;
1171 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
1172 use NKF;
1173 @ISA = ('Calcon::Writer');
1174 # This also has Reader interface.
1175
1176 sub record {
1177 my ($self,$keys,$record) = @_;
1178
1179 if(defined($record->{'name'})) {
1180 $self->address($keys,$record);
1181 } elsif(defined($record->{'date'})) {
1182 if ($record->{'date'}) { return if ($self->{'-future-only'} && ! $record->{'date'}->future()); }
1183 $self->calendar($keys,$record);
1184 } else {
1185 # I don't know.
1186 }
1187 }
1188
1189 sub address {
1190 my ($self,$keys,$record) = @_;
1191 push(@{$self->{'-address-index'}->{$record->{'name'}}},$record);
1192 }
1193
1194 sub calendar {
1195 my ($self,$keys,$record) = @_;
1196 push(@{$self->{'-date-index'}->{$record->{'date'}->unix_time()}},$record);
1197 }
1198
1199 sub set_contents {
1200 my ($self,$address,$calendar) = @_;
1201 $self->{'-date-index'} = $calendar;
1202 $self->{'-address-index'} = $address;
1203 }
1204
1205 sub contents {
1206 my ($self) = @_;
1207 return ( $self->{'-date-index'}, $self->{'-address-index'});
1208 }
1209
1210 # Reader インターフェースの部分
1211
1212 sub set_output {
1213 my ($self,$out) = @_;
1214 $self->{'-output'} = $out;
1215 }
1216
1217 sub output {
1218 my ($self,$out) = @_;
1219
1220 $self->{'-output'} = $out;
1221 $self->{'-output'}->start_file();
1222 $self->write_datebook();
1223 $self->write_addressbook();
1224 $self->{'-output'}->end_file();
1225 }
1226
1227 sub write_datebook {
1228 my ($self) = @_;
1229 for my $date ( sort {$a<=>$b} keys %{$self->{'-date-index'}} ) {
1230 for my $record ( @{$self->{'-date-index'}->{$date}} ) {
1231 my @keys = keys %{$record};
1232 $self->{'-output'}->record(\@keys,$record);
1233 }
1234 }
1235 }
1236
1237 sub write_addressbook {
1238 my ($self) = @_;
1239 for my $adr ( keys %{$self->{'-address-index'}} ) {
1240 for my $record ( @{$self->{'-address-index'}->{$adr}} ) {
1241 my @keys = keys %{$record};
1242 $self->{'-output'}->record(\@keys,$record);
1243 }
1244 }
1245 }
1246
1247 # 自分自身のクラスを切替えることで動作モードを切替える
1248
1249 sub delete_mode {
1250 my ($self) = @_;
1251 bless $self,'Calcon::Pool::delete';
1252 }
1253
1254 sub merge_mode {
1255 my ($self) = @_;
1256 bless $self,'Calcon::Pool::merge';
1257 }
1258
1259 sub input_mode {
1260 my ($self) = @_;
1261 bless $self,'Calcon::Pool';
1262 }
1263
1264 # 以下のルーチンは、たぶん、Record クラスにあるべき
1265
1266 sub same_record_in_list {
1267 my ($self,$list,$record) = @_;
1268 # print "\nCampare: ";$record->value;
1269 record:
1270 for (my $i = 0; $i<=$#{$list}; $i++) {
1271 my $r = $list->[$i];
1272 # print "\nList: ";$r->value;
1273 next if (! $record->equal($r));
1274 # print "\nResult: $i\n";
1275 return $i;
1276 }
1277 # print "\nResult: -1\n";
1278 return -1;
1279 }
1280
1281 #######################################################################/
1282
1283 package Calcon::Pool::delete;
1284
1285 # 自分のPoolから、与えれたレコードを削除する。差分計算。
1286
1287 use strict;
1288 # use warnings;
1289 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
1290 use NKF;
1291 @ISA = ('Calcon::Pool');
1292
1293 sub address {
1294 my ($self,$keys,$record) = @_;
1295 my $name = $record->{'name'};
1296 if (my $list = $self->{'-address-index'}->{$name}) {
1297 my $i;
1298 if (($i = $self->same_record_in_list($list,$record)) >= 0 ) {
1299 splice(@{$list},$i,1);
1300 if (! @$list) {
1301 delete $self->{'-address-index'}->{$name};
1302 }
1303 }
1304 }
1305 }
1306
1307 sub calendar {
1308 my ($self,$keys,$record) = @_;
1309 my $date = $record->{'date'}->unix_time();
1310 if (my $list = $self->{'-date-index'}->{$date}) {
1311 my $i;
1312 if (($i = $self->same_record_in_list($list,$record)) >= 0 ) {
1313 splice(@{$list},$i,1);
1314 if (! @$list) {
1315 delete $self->{'-date-index'}->{$date};
1316 }
1317 }
1318 }
1319 }
1320
1321 #######################################################################/
1322
1323 package Calcon::Pool::merge;
1324
1325 # Pool にないレコードだったら、そのレコードを付け加える。
1326 # 中身を見て、必要な情報のみを付け加える方が良い。
1327
1328 use strict;
1329 # use warnings;
1330 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
1331 use NKF;
1332 @ISA = ('Calcon::Pool');
1333
1334 sub address {
1335 my ($self,$keys,$record) = @_;
1336 my $name = $record->{'name'};
1337 if (my $list = $self->{'-address-index'}->{$name}) {
1338 my $i;
1339 if (($i = $self->same_record_in_list($list,$record)) >= 0 ) {
1340 return;
1341 }
1342 push(@$list,$record);
1343 } else {
1344 push(@{$self->{'-address-index'}->{$name}},$record);
1345 }
1346 }
1347
1348 sub calendar {
1349 my ($self,$keys,$record) = @_;
1350 my $date = $record->{'date'}->unix_time();
1351 my $list = $self->{'-date-index'}->{$date};
1352 if ($list) {
1353 my $r;
1354 return unless ($r = $self->new_info($list,$record));
1355 push(@$list,$r);
1356 } else {
1357 push(@{$self->{'-date-index'}->{$date}},$record);
1358 }
1359 }
1360
1361
1362 #######################################################################/
1363
1364 package Calcon::Buffered_Writer;
1365
1366 # 変換前にすべてを読み込む必要がある形式のために使うクラス。
1367 # データの先頭に総レコード数を持つ形式とか。
1368
1369 use strict;
1370 # use warnings;
1371 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
1372 use NKF;
1373 @ISA = ('Calcon::Writer');
1374
1375 #
1376 # Some format requires whole record before write, because of
1377 # record count or sorted order. This plugin class perform
1378 # reading and queueing.
1379 #
1380 # write_datebook or write_address_book should be overwrited.
1381 #
1382
1383 sub record {
1384 my ($self,$keys,$record) = @_;
1385
1386 if(defined($record->{'name'})) {
1387 $self->{'-adr-max'}++;
1388 $self->address($keys,$record);
1389 } elsif(defined($record->{'date'})) {
1390 if ($record->{'date'}) { return if ($self->{'-future-only'} && ! $record->{'date'}->future()); }
1391 $self->{'-date-max'}++;
1392 $self->calendar($keys,$record);
1393 } else {
1394 # I don't know.
1395 }
1396 }
1397
1398 sub address {
1399 my ($self,$keys,$record) = @_;
1400 push(@{$self->{'-address-records'}}, $record);
1401 }
1402
1403 sub calendar {
1404 my ($self,$keys,$record) = @_;
1405 push(@{$self->{'-date-records'}}, $record);
1406 }
1407
1408 sub end_file {
1409 my ($self) = @_;
1410 $self->write_datebook() if ( $self->{'-date-max'} > 0);
1411 $self->write_addressbook() if ( $self->{'-adr-max'} > 0);
1412 }
1413
1414 sub write_datebook {
1415 my ($self) = @_;
1416 my $count = $self->{'-date-max'};
1417 for my $dates ( @{$self->{'-date-records'}} ) {
1418 }
1419 }
1420
1421 sub write_addressbook {
1422 my ($self) = @_;
1423 my $count = $self->{'-adr-max'};
1424 for my $adr ( @{$self->{'-address-records'}} ) {
1425 }
1426 }
1427
1428
1429 #######################################################################/
1430
1431 package Calcon::Zaurus_backup_read ;
1432
1433 # ザウルスのバックアップ形式
1434
1435 use strict;
1436 # use warnings;
1437 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
1438 @ISA = ( 'Calcon::Zaurus_read' );
1439
1440 sub decode {
1441 my ($self,$backup) = @_;
1442 my $out = $self->{'-output'};
1443
1444 my $data = $self->backup_read($backup);
1445
1446 foreach my $file ( $self->backup_files($data) ) {
1447 next if ($file !~ /BOX$/);
1448 $self->SUPER::decode($file);
1449 }
1450 }
1451
1452 sub backup_files {
1453 my ($self,$data) = @_;
1454 if ($data =~ /^\032*PABAK/) {
1455 return $self->text_backup($data);
1456 } else {
1457 return $self->ztar($data);
1458 }
1459 }
1460
1461 sub initialize {
1462 my ($self) = @_;
1463 $self->SUPER::initialize();
1464
1465 # alphabet encoding
1466 #
1467 # 0-5 "0".."5"
1468 # 6-0x1f "A".."Z"
1469 # 0x20-0x25 "6"..";"
1470 # 0x26-0x3f "a".."z"
1471 #
1472 # make character replacement code
1473 #
1474 my $ya = '';
1475 my $yb = '';
1476 for(my $i=0;$i<0x40;$i++) {
1477 if( $i <= 0x05 ) { $ya .= pack("C",($i + 0x30));}
1478 elsif( $i <= 0x1f ) { $ya .= pack("C",($i + 0x3b));}
1479 elsif( $i <= 0x25 ) { $ya .= pack("C",($i + 0x16));}
1480 else { $ya .= pack("C",($i + 0x3b)); }
1481 # since . never matches \n, 0x40 is added
1482 $yb .= sprintf("\\%03o",$i+0x40);
1483 }
1484 eval "sub a_decode \{ y\/" . $ya . "/" . $yb . "/;}\n";
1485 eval "sub a_encode \{ y\/" . $yb . "/" . $ya . "/;}\n";
1486
1487 }
1488
1489 sub read {
1490 my ($self,$file) = @_;
1491 return $self->{'-files'}->{$file};
1492 }
1493
1494 ##########################################################
1495 #
1496 # Zaurus Binary Encoding
1497 #
1498 ##########################################################
1499
1500 # bit encoding
1501 # s/..../&decode($&)/eg;
1502 # 76543210765432107654321076543210
1503 # 00 11 22 001122
1504 # 33221100332211003322110033221100
1505 # 00 11 22 001122
1506
1507 sub bit_decode {
1508 my $bit = substr($_[0],0,3);
1509 vec($bit, 3,2) = vec($_[0],14,2);
1510 vec($bit, 7,2) = vec($_[0],13,2);
1511 vec($bit,11,2) = vec($_[0],12,2);
1512 return $bit;
1513 }
1514
1515 sub bit_encode {
1516 my $bit = $_[0];
1517 vec($bit,14,2) = vec($bit, 3,2);
1518 vec($bit,13,2) = vec($bit, 7,2);
1519 vec($bit,12,2) = vec($bit,11,2);
1520 # since . never matches \n, 0x40 is added
1521 vec($bit,11,2) = vec($bit,7,2) = vec($bit,3,2) = 1;
1522 return $bit;
1523 }
1524
1525 sub z_encode {
1526 my ($i);
1527 $i = (length()%3);
1528 $_ .= "\0" x (3-$i) if($i);
1529 s/.../&bit_encode($&)/eg;
1530 &a_encode;
1531 }
1532
1533 sub z_decode {
1534 my ($i);
1535 s/\s//g;
1536 &a_decode;
1537 $i = (length()%4);
1538 $_ .= "\0" x (4-$i) if($i);
1539 s/..../&bit_decode($&)/eg;
1540 }
1541
1542 sub text_backup {
1543 my ($self,$data) = @_;
1544 my $debug = $self->{'-debug'};
1545 my (@names,@size);
1546
1547 print("\nBackup Directory\n") if ($debug);
1548
1549 $data =~ s/^\032*PABAK.*\n([^\032]*\032)//;
1550 $_ = $1;
1551 &z_decode;
1552 my @title = (); my @attr = ();
1553 my $len = length($_) - 20; my $j = 0;
1554 for(my $i=6;$i<$len;$i+=20) {
1555 $title[$j] = substr($_,$i,12);
1556 $attr[$j] = unpack("H*",substr($_,$i+12,5));
1557 $size[$j] = (ord(substr($_,$i+17,1))
1558 +ord(substr($_,$i+18,1))*0x100
1559 +ord(substr($_,$i+19,1))*0x10000);
1560 print($title[$j]."\t") if ($debug);
1561 print($attr[$j]."\t") if ($debug);
1562 print($size[$j]."\n") if ($debug);
1563 $j++;
1564 }
1565 my $i = 0;
1566 foreach (split(/\032/,$data)) {
1567 s/^PABAK.*\n//;
1568 &z_decode;
1569 $self->{'-files'}->{$title[$i++]} = $_;
1570 }
1571 return @title;
1572 }
1573
1574 sub ztar {
1575 my ($self,$data) = @_;
1576 my $debug = $self->{'-debug'};
1577 my (@names,@size);
1578 my $ptr = 0;
1579
1580 $_ = substr($data,0,16);
1581 $ptr += 16;
1582 my $count = unpack("V",substr($_,4,4));
1583
1584 print unpack("H*",substr($_,0,8)),"\n" if ($debug);
1585 for ( my $i = 0; $i<$count ; $i++ ) {
1586 $_ = substr($data,$ptr,24); $ptr+=24;
1587 last if (substr($_,0,1) eq "\xff");
1588 my $name = substr($_,0,12); $name =~ s/\0.*//;
1589 print "name: $name\n" if ($debug);
1590 push(@names,$name);
1591 my $size = unpack("V",substr($_,12,4));
1592 print "size: $size\n" if ($debug);
1593 push(@size,$size);
1594 print unpack("H*",substr($_,12)),"\n" if ($debug);
1595 }
1596
1597 for ( my $i = 0; $i<$count ; $i++ ) {
1598 $_ = substr($data,$ptr,$size[$i]); $ptr+=$size[$i];
1599 my $name = $names[$i];
1600 $self->{'-files'}->{$name} = $_;
1601 }
1602 return @names;
1603 }
1604
1605 sub backup_read {
1606 my ($self,$file) = @_;
1607
1608 $self->{'-file'} = $file;
1609 open(F,"<".$file);
1610 local($/) ;
1611 undef $/;
1612 my $data = <F>;
1613 $data;
1614 }
1615
1616
1617
1618 #######################################################################/
1619
1620 package Calcon::iApp_read;
1621
1622 # iCal/AddressBook からAppleScript 経由で読み込む。なので、
1623 # Mac::AppleScript が必要。
1624
1625 use strict;
1626 # use warnings;
1627 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
1628 use Mac::AppleScript qw(RunAppleScript);
1629 use NKF;
1630 @ISA = ( 'Calcon::File_read' ) ;
1631
1632 # We use Applescript, but it is very slow.
1633
1634 my $tell;
1635
1636 my %record_keys = (
1637 "phone電話"=>"tel",
1638 "phoneファックス"=>"fax",
1639 "emailメール"=>"mail",
1640 "address住所"=>"address",
1641 );
1642
1643 sub initialize {
1644 my ($self) = @_;
1645 $self->SUPER::initialize();
1646 $self->{'-labels'} = \%record_keys;
1647 }
1648
1649 sub decode {
1650 my ($self,$file) = @_;
1651 my ($debug) = $self->{'-debug'};
1652 my $out = $self->{'-output'};
1653 my $record;
1654 my $keys;
1655
1656 $out->start_file('');
1657 $self->get_all_event() if (! $self->{'-address-only'});
1658 $self->get_all_contact() if (! $self->{'-calendar-only'});
1659 $out->end_file('');
1660
1661 }
1662
1663 sub date {
1664 my ($self,$date)=@_;
1665 my @date = ($date =~ /(\d+)/g);
1666 if ($date =~ /PM$/) {
1667 if ($date[3]==12) { $date[3]=0;}
1668 $date[3]+=12;
1669 }
1670 return $self->make_date("$date[0]/$date[1]/$date[2] $date[3]:$date[4]");
1671 }
1672
1673
1674 sub property {
1675 my ($self,$contact,$id,$property,$record,$key) = @_;
1676 my $result;
1677 $result = RunAppleScript("${tell}${property} of ${contact} $id\nend tell\n");
1678 # it looks like apple event returns some garbage
1679 $result =~ s/^\"//; $result =~ s/\"$//; $result =~ s/\001.*$//;
1680 if (defined($record) && $result ne '') {
1681 if ($key =~ /date/ || $key =~ /birth/) {
1682 $record->{$key} = $self->date($result);
1683 } else {
1684 $record->{$key} = nkf('-eS',$result);
1685 }
1686 } else {
1687 nkf('-eS',$result);
1688 }
1689 }
1690
1691 sub address {
1692 my($self,$id,$vid,$phone,$record) = @_;
1693
1694 my ($street , $zip , $state , $country , $city);
1695 my $address = RunAppleScript("${tell}properties of ${phone} $vid of person $id\nend tell\n");
1696
1697 # {zip:missing value, label:"住所", state:missing value, street:"那覇市久茂地3-21-1", country code:missing value, country:missing value, id:"AFBD61FE-FB17-11D6-A84E-0003936AC938", city:missing value, class:address}
1698
1699 $address =~ s/^\"//; $address =~ s/\"$//; $address =~ s/\001.*$//;
1700 $address = nkf('-eS',$address);
1701
1702 # my ($street , $zip , $state , $country , $city);
1703 $address =~ /street:"([^"]*)"/ && ($street = $1);
1704 $zip =~ /zip:"([^"]*)"/ && ($zip = $1);
1705 $state =~ /state:"([^"]*)"/ && ($state = $1);
1706 $city =~ /city:"([^"]*)"/ && ($city = $1);
1707 $country =~ /country:"([^"]*)"/ && ($country = $1);
1708
1709 my ($label) = ($address =~ /label:"(.*?)"/);
1710 if (! defined($self->{'-labels'}->{$phone.$label})) {
1711 print "## $phone$label not defined\n";
1712 }
1713 $record->{$self->{'-labels'}->{$phone.$label}} = "$state $city $street $country"
1714 if ($state||$city||$street||$country);
1715 if ($zip && $self->{'-labels'}->{$phone.$label} =~ /home/) {
1716 $record->{'home-zip'} = $zip;
1717 } else {
1718 $record->{'zip'} = $zip if ($zip);
1719 }
1720 }
1721
1722 sub value {
1723 my($self,$id,$vid,$phone,$record) = @_;
1724 my $result = RunAppleScript("${tell}properties of ${phone} $vid of person $id\nend tell\n");
1725 $result =~ s/^\"//; $result =~ s/\"$//; $result =~ s/\001.*$//;
1726 $result = nkf('-eS',$result);
1727 my ($value,$label) = ($result =~ /value:"(.*?)".*label:"(.*?)"/);
1728 if (! defined($self->{'-labels'}->{$phone.$label})) {
1729 print "## $phone$label not defined\n";
1730 }
1731 $record->{$self->{'-labels'}->{$phone.$label}} = $value;
1732 }
1733
1734
1735 sub get_all_contact {
1736 my ($self) = @_;
1737 $tell = "tell application \"Address Book\"\n";
1738
1739 my $count = RunAppleScript("${tell}count of person\nend tell\n");
1740 foreach my $id ( 1..$count ) {
1741 $self->person($id);
1742 }
1743 }
1744
1745 sub person {
1746 my ($self,$id) = @_;
1747 my $record = {};
1748
1749 my $phone_count = RunAppleScript("${tell}count of phone of person $id\nend tell\n");
1750 foreach my $phone_id ( 1..$phone_count ) {
1751 $self->value($id,$phone_id,'phone',$record);
1752 }
1753
1754 my $email_count = RunAppleScript("${tell}count of email of person $id\nend tell\n");
1755 foreach my $email_id ( 1..$email_count ) {
1756 $self->value($id,$email_id,'email',$record);
1757 }
1758
1759 my $address_count = RunAppleScript("${tell}count of address of person $id\nend tell\n");
1760 foreach my $address_id ( 1..$address_count ) {
1761 $self->address($id,$address_id,'address',$record);
1762 }
1763
1764 my $name = $self->property('person',$id,'last name');
1765 my $first_name = $self->property('person',$id,'first name');
1766 $record->{'name'} = ($name && $first_name)?"$name $first_name":
1767 ($name)?$name:$first_name;
1768
1769 my $name_p = $self->property('person',$id,'phonetic last name');
1770 my $first_name_p = $self->property('person',$id,'phonetic first name');
1771 $record->{'name-yomi'} = ($name_p && $first_name_p)?"$name_p $first_name_p":
1772 ($name_p)?$name_p:$first_name_p;
1773
1774 $self->property('person',$id,'job title',$record,'section');
1775 $self->property('person',$id,'title',$record,'title');
1776
1777 # $self->property('person',$id,'birth date',$record,'birth');
1778 $self->property('person',$id,'organization',$record,'office');
1779 my $keys = [];
1780 push(@$keys,keys %{$record});
1781
1782 my $out = $self->{'-output'};
1783 $out->record($keys,$record);
1784 }
1785
1786
1787 sub get_all_event {
1788 my ($self) = @_;
1789
1790 $tell = "tell application \"iCal\"\n";
1791 if ($self->{'-future-only'}) {
1792 my $today = $self->today();
1793 my ($year,$mon,$mday,$hour,$min) = $today->localtime();
1794 my $count = RunAppleScript("${tell}uid of every event of last calendar whose start date > date \"$year/$mon/$mday\"\nend tell\n");
1795 for my $id ($count =~ /("[^"]*")/g) {
1796 $self->uid_event($id);
1797 }
1798 } else {
1799 my $count = RunAppleScript("${tell}count of event of last calendar\nend tell\n");
1800 for(my $id=1; $id <= $count ;$id++) {
1801 $self->event($id);
1802 }
1803 }
1804 }
1805
1806 sub uid_event {
1807 my ($self,$id) = @_;
1808 my $record = $self->make_record;
1809
1810 # $self->property('event',$id,'all day event',$record,'all-day');
1811 $self->property('some event of last calendar whose uid is',$id,'start date',$record,'date');
1812 $self->property('some event of last calendar whose uid is',$id,'end date',$record,'end-date');
1813 $self->property('some event of last calendar whose uid is',$id,'summary',$record,'summary');
1814 $self->property('some event of last calendar whose uid is',$id,'description',$record,'memo');
1815 my $keys = [];
1816 push(@$keys,keys %{$record});
1817
1818 my $out = $self->{'-output'};
1819 $out->record($keys,$record);
1820 }
1821
1822 sub event {
1823 my ($self,$id) = @_;
1824 my $record = $self->make_record;
1825
1826 # $self->property('event',$id,'all day event',$record,'all-day');
1827 $self->property('event',$id." of last calendar",'start date',$record,'date');
1828 $self->property('event',$id." of last calendar",'end date',$record,'end-date');
1829 $self->property('event',$id." of last calendar",'summary',$record,'summary');
1830 $self->property('event',$id." of last calendar",'description',$record,'memo');
1831 my $keys = [];
1832 push(@$keys,keys %{$record});
1833
1834 my $out = $self->{'-output'};
1835 $out->record($keys,$record);
1836 }
1837
1838
1839
1840 #######################################################################/
1841
1842 package Calcon::iApp_write ;
1843
1844 # AppleScript 経由で iCal/AddressBook に書き出す。この実装では、
1845 # Mac::AppleScript はいらない
1846
1847 use strict;
1848 # use warnings;
1849 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
1850 use NKF;
1851 use Carp;
1852
1853 @ISA = ( 'Calcon::Writer' );
1854
1855 sub initialize {
1856 my ($self) = @_;
1857
1858 $self->SUPER::initialize();
1859 if (defined $self->{'-file'}) {
1860 $self->{'-file-out'} = 1;
1861 } else {
1862 if (defined $self->{'-file-out'}) {
1863 $self->{'-file'} = "script-out";
1864 }
1865 }
1866
1867 $self->{'-fake-allday'} = 1;
1868 $self->{'-time-for-allday'} = 12*3600;
1869 $self->{'-add-time-for-allday'} = 2*3600;
1870
1871 $self->{'-check-script'} = 1;
1872 $self->{'-check-group'} = 20;
1873 $self->{'-do-grouping'} = 1;
1874
1875 # | perl -pe 's/[\177-\377]/sprintf "\\%03o",ord($&)/eg;'
1876 # | perl -pe 's/\\(\d\d\d)/sprintf "%c",oct($&)/eg;'
1877
1878 $self->{"-phone-labels"} = {
1879 "tel"=>"電話",
1880 "tel-home"=>"自宅電話",
1881 "mobile-tel"=>"携帯",
1882 "home-fax"=>"自宅ファックス",
1883 "fax"=>"ファックス",
1884
1885 };
1886 $self->{"-mail-labels"} = {
1887 "mail"=>"メール",
1888 "mail-to"=>"メール2",
1889 "mail-address"=>"メール3",
1890
1891 };
1892 $self->{"-address-labels"} = {
1893 "address"=>"住所",
1894 "home-address"=>"自宅住所",
1895 };
1896 $self->{"-zip-labels"} = {
1897 "zip"=>"郵便番号",
1898 "home-zip"=>"自宅郵便番号",
1899 };
1900 $self->{'-groups'} = {};
1901 $self->{'-init-file'} = "s000000";
1902 $self->{'-check-script-count'} = 0;
1903 $self->{'-script-name'} = $self->{'-init-file'};
1904
1905 }
1906
1907 sub start_file {
1908 my ($self,$type) = @_;
1909 undef $self->{'-application'};
1910 if ($self->{'-file-out'}) {
1911 mkdir $self->{'-file'};
1912 }
1913 }
1914
1915 sub end_file {
1916 my ($self,$type) = @_;
1917 $self->close();
1918 $self->{'-telling'} = 0;
1919 if ($self->{'-file-out'}) {
1920 $self->make_group();
1921 while(<script-out/*.script>) {
1922 my $out = $_;
1923 $out =~ s/\.script$/.compile/;
1924 print STDERR "osacompile -o $out $_\n";
1925 # system "osacompile -o $out $_";
1926 # system "osascript $out";
1927 }
1928 }
1929 }
1930
1931 sub start_record {
1932 my ($self,$type) = @_;
1933
1934 if ($self->{'-check-script'}) {
1935 my $i = $self->{'-check-script-count'}++;
1936 if ($i % $self->{'-check-group'}==0) {
1937 my $d = $self->{'-script-name'}++;
1938 $self->close() if ( $self->{'-telling'} );
1939 $self->{'-telling'} = 0;
1940 if ($self->{'-file-out'}) {
1941 open OUT,"> script-out/$d.script" or croak($!);
1942 } else {
1943 print STDERR "doing $i\n";
1944 open OUT,"| osascript " or cloak($!);
1945 }
1946 select OUT;
1947 }
1948 }
1949 }
1950
1951 sub print {
1952 my ($self,@data) = @_;
1953 foreach (@data) {
1954 my $data = nkf('-s -Z',$_);
1955 $data =~ s/\354\276/\203_/g;
1956 $data =~ s/\356\276/ /g;
1957 $data =~ s/\356\277/ /g;
1958 $data =~ s/([^\200-\377])\\/$1\200/g;
1959 # $data =~ s/\201/\/g;
1960 print $data;
1961 }
1962 }
1963
1964 sub record {
1965 my ($self,$keys,$record) = @_;
1966 my ($application);
1967
1968 $self->start_record('');
1969
1970 # check proper application
1971 if (defined $record->{'name'}) {
1972 $application = 'Address Book';
1973 $self->set_application($application);
1974 $self->address_book($keys,$record);
1975 $self->print("end tell\n") if (! $self->{'-check-script'}) ;
1976 } elsif (defined $record->{'date'}) {
1977 if ($record->{'date'}) { return if ($self->{'-future-only'} && ! $record->{'date'}->future()); }
1978 $application = 'iCal';
1979 $self->set_application($application);
1980 $self->ical($keys,$record);
1981 $self->print("end tell\n") if (! $self->{'-check-script'}) ;
1982 } else {
1983 # nothing to do
1984 }
1985 $self->print("\n");
1986 }
1987
1988 sub close {
1989 my ($self) = @_;
1990 my $application = $self->{'-application'};
1991 if ($self->{'-check-script'}) {
1992 if ($application eq "Address Book") {
1993 $self->print("--close address\n");
1994 $self->print("--close group\n");
1995 # $self->print("with transaction\n");
1996 $self->print("save addressbook\n");
1997 # $self->print("end transaction\n");
1998 }
1999 $self->print("quit saving yes\n")
2000 if (0 && $self->{'-check-script-count'} % 5 == 4);
2001 $self->print("end tell\n");
2002 undef $self->{'-application'};
2003 }
2004 $self->{'-telling'} = 0;
2005 }
2006
2007 sub set_application {
2008 my ($self,$application) = @_;
2009
2010 if ($application ne $self->{'-application'}) {
2011 $self->print("end tell\n") if ($self->{'-telling'} );
2012 $self->{'-application'} = $application;
2013 $self->print("\ntell Application \"$application\"\n");
2014 $self->{'-telling'} = 1;
2015 }
2016 }
2017
2018 sub address_book {
2019 my ($self,$keys,$record) = @_;
2020 my @keys = @$keys;
2021 my %record = %$record;
2022 my ($tab) = '';
2023
2024
2025 return if(! defined $record{'name'});
2026 $tab .= ' ';
2027
2028 $self->print("with transaction\n");
2029 if(defined $record{'office'}) {
2030 my $group = $record{'office'};
2031 $self->print($tab,"if not exists some group whose name is ");
2032 $tab .= ' ';
2033 $self->print("\"$group\" then \n");
2034 $self->print($tab,"make new group with properties ");
2035 $self->print("{name:\"$group\"}\n");
2036 $tab =~ s/ $//;
2037 $self->print($tab,"end\n\n");
2038 }
2039 $self->print($tab,"set aPerson to make new person with properties {");
2040 $tab .= ' ';
2041
2042 my @names;
2043 my $data = $record{'name'};
2044 @names = split(/ +/,$data);
2045
2046 $self->print("last name: \"",shift(@names),"\",");
2047 $self->print("first name: \"@names\"}\n");
2048
2049 $self->print($tab,"tell aPerson\n");
2050 if(defined $record{'name-yomi'}) {
2051 if($record{'name-yomi'} =~ /\201H/) { # ?
2052 } else {
2053 my $data = $record{'name-yomi'};
2054 if ($data =~ /,/) {
2055 @names = split(/,/,$data);
2056 $data = $names[1].' '.$names[0];
2057 }
2058 $data = nkf('-sIZ --hiragana',$data);
2059 $data = $self->check_2byte($data);
2060 @names = split(/ +/,$data);
2061 # put one space to prevent a problem of incomplete Shift JIS
2062 $self->print($tab,"set phonetic last name to \"",shift(@names)," \"\n");
2063 $self->print($tab,"set phonetic first name to \"@names \"\n")
2064 if (@names);
2065 }
2066 }
2067
2068 if(defined $record{'section'}) {
2069 $self->print($tab,"set job title to \"$record{'section'}\"\n");
2070 }
2071 if(defined $record{'title'}) {
2072 $self->print($tab,"set title to \"$record{'title'}\"\n");
2073 }
2074 foreach my $address ('','home-') {
2075 my @data = ();
2076 if(defined $record{$address."address"}) {
2077 my $adr = nkf('-s -Z',$record{$address."address"});
2078 if($adr=~ s/\201\247\s*(\d+)//) {
2079 $record{$address.'zip'} = $1;
2080 }
2081 if($record{$address.'zip'}) {
2082 push(@data,",zip:\"$self->{'-zip-labels'}->{$record{$address.'zip'}}\"");
2083 }
2084 $self->add_address($tab,$adr,$address."address",\@data);
2085 }
2086 }
2087 foreach my $phone ('tel','tel-home','mobile-tel','home-fax','fax') {
2088 if(defined $record{$phone}) {
2089 $self->add_phone($tab,$record{$phone},$phone);
2090 }
2091 }
2092 foreach my $mail ('mail','mail-to','mail-address') {
2093 if(defined $record{$mail}) {
2094 $self->add_mail($tab,$record{$mail},$mail);
2095 }
2096 }
2097
2098 if(defined $record{'birth'}) {
2099 # it looks like Address Book's apple script has trouble with birth date
2100 # $self->print($tab,"set birth date to ",$self->date($record{'birth'}),"\n");
2101 }
2102 if(defined $record{'office'}) {
2103 $self->print($tab,"set organization to \"$record{'office'}\"\n");
2104 if ($self->{'-do-grouping'}) {
2105 $tab =~ s/ $//;
2106 $self->print($tab,"end tell\n");
2107 $self->print($tab,"try\n");
2108 $tab .= ' ';
2109 $self->print($tab,"add aPerson to some group whose name is \"");
2110 $self->print($record{'office'},"\"\n");
2111 $tab =~ s/ $//;
2112 $self->print($tab,"end\n");
2113 $self->print("end transaction\n");
2114 $self->{'-groups'}->{$record{'office'}} = 1;;
2115 return;
2116 }
2117 }
2118 $tab =~ s/ $//;
2119 $self->print($tab,"end tell\n");
2120 $self->print("end transaction\n");
2121 }
2122
2123 sub check_2byte {
2124 my ($self,$data) = @_;
2125 my $new = '';
2126 my $tmp;
2127
2128 while($data) {
2129 if ($data =~ s/^([\000-\177]*)([\200-\377])//) {
2130 $new .= $1; $tmp = $2;
2131 if (! $data ) {
2132 } elsif ($data =~ /^[!-\376]/) {
2133 $data =~ s/^.//;
2134 $new .= $tmp . $&
2135 }
2136 } else {
2137 $new .= $data;
2138 last;
2139 }
2140 }
2141 $new;
2142 }
2143
2144
2145 sub date {
2146 my ($self,$date) = @_;
2147 my ($year,$month,$day,$hour,$min) = $date->localtime();
2148
2149 $date = "date \"${year}N $month $day j";
2150 if ($hour) { $date .= " $hour:$min";}
2151 $date .= "\"";
2152 return $date;
2153 }
2154
2155 sub add_address {
2156 my ($self,$tab,$data,$label,$option) = @_;
2157
2158 $label = nkf('-s',$self->{'-address-labels'}->{$label});
2159 $self->print($tab,"make new address at end of address of aPerson ");
2160 $self->print("with properties {street:\"$data\", label:\"$label\"@$option}\n");
2161 }
2162
2163 sub add_phone {
2164 my ($self,$tab,$data,$label) = @_;
2165
2166 $label = nkf('-s',$self->{'-phone-labels'}->{$label});
2167 $self->print($tab,"make new phone at end of phone of aPerson with properties {value:\"$data\", label:\"$label\"}\n");
2168 }
2169
2170 sub add_mail {
2171 my ($self,$tab,$data,$label) = @_;
2172
2173 $label = nkf('-s',$self->{'-mail-labels'}->{$label});
2174 $self->print($tab,"make new email at end of email of aPerson with properties {value:\"$data\", label:\"$label\"}\n");
2175 }
2176
2177 sub make_group {
2178 my ($self) = @_;
2179 my (%groups) = %{$self->{'-groups'}};
2180 my $tab = ' ';
2181
2182 return if (! %groups);
2183 open OUT,"> script-out/group.script" or cloak($!);
2184 select OUT;
2185 $self->print("tell application \"Address Book\"\n");
2186 foreach my $group (keys %groups) {
2187 $self->print($tab,"if not exists some group whose name is ");
2188 $tab .= ' ';
2189 $self->print("\"$group\" then \n");
2190 $self->print($tab,"make new group with properties ");
2191 $self->print("{name:\"$group\"}\n");
2192 $tab =~ s/ $//;
2193 $self->print($tab,"end\n");
2194 }
2195 $self->print("close group\n");
2196 $self->print("with transaction\n");
2197 $self->print("save addressbook\n");
2198 $self->print("end transaction\n");
2199 $self->print("quit saving yes\n");
2200 $self->print("end tell\n");
2201 }
2202
2203 sub ical {
2204 my ($self,$keys,$record) = @_;
2205 my @keys = @$keys;
2206 my %record = %$record;
2207 my ($tab) = '';
2208
2209 # $self->print("with transaction\n");
2210 # $self->print($tab,"set aDay to ");
2211 $self->print("make new event at end of event of last calendar with properties {");
2212 if ($record{'date'}->is_allday() && $self->{'-fake-allday'} ) {
2213 $record{'date'} = $record{'date'}->add($self->{'-time-for-allday'});
2214 $record{'end-date'} =
2215 $record{'date'}->add($self->{'-add-time-for-allday'});
2216 }
2217 $self->print($tab,"start date:",$self->date($record{'date'}));
2218 if (defined $record{'end-date'}) {
2219 if ($record{'date'}->value() == $record{'end-date'}->value()) {
2220 $record{'end-date'} =
2221 $record{'date'}->add($self->{'-add-time-for-allday'});
2222 }
2223 $self->print($tab,",end date:",$self->date($record{'end-date'}))
2224 }
2225 $self->print($tab,",stamp date:",$self->date($record{'modify-date'}))
2226 if (defined $record{'modify-date'});
2227 if (defined($record{'memo'})) {
2228 my ($summary,$memo);
2229 if (defined($record{'summary'})) {
2230 $summary = $record{'summary'};
2231 $memo = $record{'memo'};
2232 } else {
2233 $summary = $record{'memo'};
2234 # if this contains double quote we have a problem. But
2235 # I cannot fix it without decoding shift JIS and backslash/0x80
2236 # conversion.
2237 $summary =~ s/"//g; # oops
2238 $summary =~ s/[\r\n].*$//; $memo = $&;
2239 }
2240
2241 $self->print($tab,",summary:\"",$summary,"\"") if ($summary);
2242 $self->print($tab,",description:\"",$memo,"\"") if ($memo);
2243 }
2244 $self->print($tab,"}\n");
2245
2246 # $self->print($tab,"tell aDay\n");
2247 # $self->print($tab,"if start date = end date then\n");
2248 # $self->print($tab," set end date to start date + ".
2249 # int($self->{'-add-time-for-allday'}/60)." * minutes\n");
2250 # $self->print($tab,"end if\n");
2251 # $self->print($tab,"end\n");
2252 # $self->print("end transaction\n");
2253 }
2254
2255 #######################################################################/
2256
2257 package Calcon::Entourage_write ;
2258
2259 # Mac のEntrourage に AppleScript 経由で書き出す。ここでも Mac::AppleScript
2260 # は使わない。
2261
2262 use strict;
2263 # use warnings;
2264 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
2265 use NKF;
2266 @ISA = ( 'Calcon::iApp_write' );
2267
2268 sub initialize {
2269 my ($self) = @_;
2270 $self->SUPER::initialize();
2271
2272 $self->{'-fake-allday'} = 0;
2273 $self->{'-time-for-allday'} = 12*3600;
2274 $self->{'-add-time-for-allday'} = 2*3600;
2275
2276 $self->{'-check-script'} = 1;
2277 $self->{'-check-group'} = 20;
2278
2279 $self->{'-init-file'} = "s000000";
2280 $self->{'-check-script-count'} = 0;
2281 $self->{'-japanese-format'} = 1;
2282 $self->{'-script-name'} = $self->{'-init-file'};
2283
2284 $self->{"-phone-labels"} = {
2285 "tel"=>"business phone number",
2286 "tel-home"=>"home phone number",
2287 "mobile-tel"=>"mobile phone number",
2288 "home-fax"=>"home fax phone number",
2289 "fax"=>"business fax phone number",
2290
2291 };
2292 }
2293
2294 sub record {
2295 my ($self,$keys,$record) = @_;
2296
2297 $self->start_record('');
2298
2299 # check proper application
2300 if (defined $record->{'name'}) {
2301 my $application = 'Microsoft Entourage';
2302 $self->set_application($application);
2303 $self->contact($keys,$record);
2304 $self->print("end tell\n") if (! $self->{'-check-script'}) ;
2305 } elsif (defined $record->{'date'}) {
2306 if ($record->{'date'}) { return if ($self->{'-future-only'} && ! $record->{'date'}->future()); }
2307 my $application = 'Microsoft Entourage';
2308 $self->set_application($application);
2309 $self->event($keys,$record);
2310 $self->print("end tell\n") if (! $self->{'-check-script'}) ;
2311 } else {
2312 # nothing to do
2313 }
2314 $self->print("\n");
2315 }
2316
2317 sub close {
2318 my ($self) = @_;
2319 my $application = $self->{'-application'};
2320 if ($self->{'-check-script'}) {
2321 $self->print("quit saving yes\n")
2322 if (0 && $self->{'-check-script-count'} % 5 == 4);
2323 $self->print("end tell\n");
2324 undef $self->{'-application'};
2325 }
2326 $self->{'-telling'} = 0;
2327 }
2328
2329 sub make_group {
2330 }
2331
2332 sub contact {
2333 my ($self,$keys,$record) = @_;
2334 my @keys = @$keys;
2335 my %record = %$record;
2336 my ($tab) = '';
2337 my @names;
2338 my $data = $record{'name'};
2339 @names = split(/ +/,$data);
2340
2341 $self->print("with transaction\n");
2342 $tab .= ' ';
2343
2344
2345 # $self->print("${tab}try\n${tab}set aPerson to some contact whose last name is \"$names[0]\" and first name is \"$names[1]\"\n${tab}on error\n";
2346
2347 $self->print($tab,"set aPerson to make new contact with properties {");
2348
2349 $tab .= ' ';
2350
2351 $self->print($tab,"last name: \"",shift(@names),"\",");
2352 $self->print($tab,"first name: \"@names\"}\n");
2353 $tab =~ s/ //;
2354 # $self->print($tab,"end\n");
2355
2356
2357 $self->print($tab,"tell aPerson\n");
2358 if(defined $record{'name-yomi'}) {
2359 if($record{'name-yomi'} =~ /\201H/) { # ?
2360 } else {
2361 my $data = $record{'name-yomi'};
2362 if ($data =~ /,/) {
2363 @names = split(/,/,$data);
2364 $data = $names[1].' '.$names[0];
2365 }
2366 $data = nkf('-sIZ --hiragana',$data);
2367 $data = $self->check_2byte($data);
2368 @names = split(/ +/,$data);
2369 # put one space to prevent a problem of incomplete Shift JIS
2370 $self->print($tab,"set last name furigana to \"",shift(@names)," \"\n");
2371 $self->print($tab,"set first name furigana to \"@names \"\n")
2372 if (@names);
2373 }
2374 }
2375
2376 $self->print($tab,"set japanese format to true\n") if ($self->{'-japanese-format'});
2377 if(defined $record{'section'}) {
2378 $self->print($tab,"set department to \"$record{'section'}\"\n");
2379 }
2380 if(defined $record{'title'}) {
2381 $self->print($tab,"set job title to \"$record{'title'}\"\n");
2382 }
2383 if(defined $record{'address'}) {
2384 $self->print($tab,"set business address to {",
2385 "zip:\"$record{'zip'}\",",
2386 "street address:\"$record{'address'}\"",
2387 "}\n"
2388 );
2389 }
2390 if(defined $record{'home-address'}) {
2391 $self->print($tab,"set home address to {",
2392 "zip:\"$record{'home-zip'}\",",
2393 "street address:\"$record{'home-address'}\"",
2394 "}\n"
2395 );
2396 }
2397
2398 foreach my $phone ('tel','tel-home','mobile-tel','home-fax','fax') {
2399 if(defined $record{$phone}) {
2400 $self->print($tab,"set ",$self->{'-phone-labels'}->{$phone},
2401 " to \"",$record{$phone},"\"\n"
2402 );
2403 }
2404 }
2405
2406 # $self->print($tab,"delete every email address of aPerson\n");
2407 foreach my $mail ('mail','mail-to','mail-address') {
2408 if(defined $record{$mail}) {
2409 foreach my $m (split(/,/,$record{$mail})) {
2410 $self->print($tab,"make new email address of aPerson with data \"$m\"\n");
2411 }
2412 }
2413 }
2414
2415 if(defined $record{'birth'}) {
2416 $self->print($tab,"set birthday to \"",$self->birth_date($record{'birth'}),"\"\n");
2417 }
2418 if(defined $record{'office'}) {
2419 $self->print($tab,"set company to \"$record{'office'}\"\n");
2420 }
2421 if(defined $record{'office-yomi'}) {
2422 $self->print($tab,"set company furigana to \"$record{'office-yomi'}\"\n");
2423 }
2424 $tab =~ s/ $//;
2425 $self->print($tab,"end tell\n");
2426 $self->print("end transaction\n");
2427 }
2428
2429 sub birth_date {
2430 my ($self,$date) = @_;
2431 my ($year,$month,$day,$hour,$min) = $date->localtime();
2432
2433 if (!$year) { $year = '';} else { $year = "$year/"; }
2434 $date = "$year$month/$day";
2435 if ($hour) { $date .= " $hour:$min";}
2436 return $date;
2437 }
2438
2439
2440 sub event {
2441 my ($self,$keys,$record) = @_;
2442 my @keys = @$keys;
2443 my %record = %$record;
2444 my ($tab) = '';
2445
2446 # $self->print("with transaction\n");
2447 # $self->print($tab,"set aDay to ");
2448 $self->print("make new event with properties {");
2449
2450 # make new event with properties {subject:"", location:"", content:
2451 # "", start time:date "2002N 11 13 j 0:00:00 PM", end time:date
2452 # "2002N 11 13 j 0:30:00 PM", all day event:false, recurring:false,
2453 # category:{}, links:{}, remind time:1440, recurrence:""}
2454
2455 if ( $record{'date'}->is_allday()) {
2456 $self->print($tab,"all day event: true,");
2457 $self->print($tab,"start time:",$self->date($record{'date'}));
2458 } else {
2459 $self->print($tab,"all day event: false,");
2460 $self->print($tab,"start time:",$self->date($record{'date'}));
2461 if (defined $record{'end-date'}) {
2462 $self->print($tab,",end time:",$self->date($record{'end-date'}))
2463 }
2464 }
2465 # $self->print($tab,",stamp date:",$self->date($record{'modify-date'}))
2466 # if (defined $record{'modify-date'});
2467 if (defined($record{'memo'})) {
2468 my ($summary,$memo);
2469 if (defined($record{'summary'})) {
2470 $summary = $record{'summary'};
2471 $memo = $record{'memo'};
2472 } else {
2473 $summary = $record{'memo'};
2474 # if this contains double quote we have a problem. But
2475 # I cannot fix it without decoding shift JIS and backslash/0x80
2476 # conversion.
2477 $summary =~ s/"//g; # oops
2478 $summary =~ s/[\r\n].*$//; $memo = $&;
2479 }
2480
2481 $self->print($tab,",subject:\"",$summary,"\"") if ($summary);
2482 $self->print($tab,",content:\"",$memo,"\"") if ($memo);
2483 }
2484 $self->print($tab,"}\n");
2485
2486 }
2487
2488
2489 #######################################################################/
2490
2491 package Calcon::Sla300_read;
2492
2493 # Linux Zaurus SLA300 の XML形式
2494 # でもなんか新しくなって、これではなくなったらしい。しくしく。
2495
2496 use strict;
2497 # use warnings;
2498 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
2499 @ISA = ( 'Calcon::Reader') ;
2500
2501 use NKF;
2502 use Time::Local;
2503
2504 my %keys = (
2505 'birthday'=>'birth',
2506 'businessfax'=>'fax',
2507 'businessmobile'=>'keitai',
2508 'businessphone'=>'tel',
2509 'businessstate'=>'state',
2510 'businessstreet'=>'address',
2511 'businesszip'=>'zip',
2512 'categories'=>'categories',
2513 'company'=>'office',
2514 'companypronunciation'=>'office-yomi',
2515 'department'=>'section',
2516 'description'=>'memo',
2517 'emails'=>'email',
2518 'end'=>'end-date',
2519 'firstname'=>'first-name',
2520 'firstnamepronunciation'=>'first-name-yomi',
2521 'homefax'=>'home-fax',
2522 'homemobile'=>'home-keitai',
2523 'homephone'=>'home-tel',
2524 'homestate'=>'home_state',
2525 'homestreet'=>'home-address',
2526 'homezip'=>'home-zip',
2527 'jobtitle'=>'title',
2528 'lastname'=>'name',
2529 'lastnamepronunciation'=>'name-yomi',
2530 'notes'=>'memo',
2531 'rid'=>'rid',
2532 'rinfo'=>'rinfo',
2533 'start'=>'date',
2534 'uid'=>'uid',
2535 );
2536
2537 sub initialize {
2538 my ($self) = @_;
2539 $self->SUPER::initialize();
2540 $self->{'-keywords'} = \%keys;
2541 }
2542
2543 sub decode {
2544 my ($self,$file) = @_;
2545 my $out = $self->{'-output'};
2546
2547 $self->{'-file'} = $file;
2548 open(F,"<".$file);
2549
2550 $out->start_file('');
2551
2552 local($/) = ">";
2553 while(<F>) {
2554 $self->xml_decode($_);
2555 }
2556 $out->end_file('');
2557 }
2558
2559 sub xml_decode {
2560 my($self,$xml) = @_;
2561 my($out) = $self->{'-output'};
2562 my($convert) = $self->{'-keywords'};
2563
2564 $xml =~ s/^\s*<([^ ]*) // or return;
2565 my $type = $1;
2566 $xml =~ s=/>\s*$== or return;
2567 $type =~ tr/A-Z/a-z/;
2568 return if ($type ne 'contact' && $type ne 'event');
2569 my $record = $self->make_record;
2570 my $keys = [];
2571 $_ = $xml;
2572 while($_) {
2573 if (s/^\s*([^\s]*)\s*\=\s*\"(.*?)\"\s*//) {
2574 my $key = $1;
2575 my $data = $2;
2576 $key =~ tr/A-Z/a-z/;
2577 $key = $convert->{$key} if ( $convert->{$key} );
2578 if ($key =~ /birth$/) {
2579 my (@data) = ($data =~ /(\d+)/g);
2580 $data = $self->make_date(join("/",@data));
2581 } elsif ($key =~ /date$/) {
2582 $data = $self->make_date_unix($data);
2583 } else {
2584 $data = nkf('-eZ -W',$data);
2585 }
2586 $record->{$key} = $data;
2587 push(@$keys,$key);
2588 } else {
2589 s/^[^\s]*\s*//;
2590 }
2591 }
2592 if ($record->{'type'} =~ /Allday/i) {
2593 undef $record->{'end-date'};
2594 @$keys = grep(!/^end-date/,@$keys);
2595 }
2596 $out->record($keys,$record);
2597 }
2598
2599
2600 #######################################################################/
2601
2602 package Calcon::Sla300_write;
2603
2604 # Linux Zaurus SLA300 の XML形式
2605 # でもなんか新しくなって、これではなくなったらしい。しくしく。
2606
2607 use strict;
2608 # use warnings;
2609 use Time::Local;
2610 use NKF;
2611 use Carp;
2612
2613 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
2614 @ISA = ('Calcon::Buffered_Writer');
2615
2616 # Mac OS X 10.2 's Address Book requires utf-16
2617 # | nkf -Z -e|iconv -c -f EUC-JP -t UTF-16
2618 #
2619
2620 sub initialize {
2621 my ($self) = @_;
2622 $self->SUPER::initialize();
2623 $self->{'-fake-allday'} = 0;
2624 $self->{'-time-for-allday'} = 12*3600;
2625 $self->{'-add-time-for-allday'} = 2*3600;
2626 }
2627
2628 sub write_datebook {
2629 my ($self) = @_;
2630 my $count = $self->{'-date-max'};
2631
2632 # open(CAL,"|nkf --utf8 >datebook.xml") or croak($!);
2633 open(CAL,">datebook.xml") or croak($!);
2634 $self->print ( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
2635 $self->print ( "<!DOCTYPE DATEBOOK><DATEBOOK>\n");
2636 $self->print ( "<RIDMax>$count</RIDMax>\n");
2637 my $uid = -1032244274;
2638 my $rid = 11;
2639
2640 for my $dates ( @{$self->{'-date-records'}} ) {
2641
2642 my $end_date = $dates->{'end-date'};
2643 if (! $end_date) {
2644 if ($dates->{'date'}->is_allday()) {
2645 if ($self->{'-fake-allday'}) {
2646 $dates->{'date'}=
2647 $dates->{'date'}->add($self->{'-time-for-allday'});
2648 $dates->{'end-date'} =
2649 $dates->{'date'}->add($self->{'-add-time-for-allday'});
2650 $dates->{'date'} = $self->unix_time($dates->{'date'});
2651 } else {
2652 $end_date = $dates->{'date'}->add(23*3600+59*60);
2653 $dates->{'type'} = "AllDay";
2654 $dates->{'date'} = $self->unix_time($dates->{'date'});
2655 $dates->{'end-date'} = $self->unix_time($end_date);
2656 }
2657 } else {
2658 $end_date =
2659 $dates->{'date'}->add($self->{'-add-time-for-allday'});
2660 $dates->{'date'} = $self->unix_time($dates->{'date'});
2661 $dates->{'end-date'} = $self->unix_time($end_date);
2662 }
2663 } else {
2664 $dates->{'date'} = $self->unix_time($dates->{'date'});
2665 $dates->{'end-date'} = $self->unix_time($dates->{'end-date'})
2666 }
2667 $dates->{'memo'} = nkf('-w -Z3',$dates->{'summary'}.$dates->{'memo'});
2668
2669 my $memo = $dates->{'memo'};
2670 my $start_time = $dates->{'date'};
2671 my $end_time = $dates->{'end-date'};
2672 $self->print("<event description=\"$memo\" categories=\"\" uid=\"$uid\" rid=\"$rid\" rinfo=\"1\" start=\"$start_time\"");
2673 if ($dates->{'end-date'}) {
2674 $self->print(" end=\"$end_time\"");
2675 }
2676 if ($dates->{'type'}) {
2677 $self->print(" type=\"$dates->{'type'}\"");
2678 }
2679 $self->print("/>\n");
2680 $uid++;
2681 $rid++;
2682 $count--;
2683 }
2684 $self->print("<events>\n");
2685 $self->print("</events>\n");
2686 $self->print("</DATEBOOK>\n");
2687 }
2688
2689 sub write_addressbook {
2690 my ($self) = @_;
2691 my $count = $self->{'-adr-max'};
2692
2693 open(CAL,">addressbook.xml") or croak($!);
2694
2695 $self->print ( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
2696 $self->print ( "<!DOCTYPE Addressbook ><AddressBook>\n");
2697 $self->print ( "<RIDMax>$count</RIDMax>\n");
2698 $self->print ( "<Groups></Groups>\n");
2699 for my $adr ( @{$self->{'-address-records'}} ) {
2700
2701 if (defined $adr->{'birth'}){
2702 $adr->{'birth'} = $self->birth_date($adr->{'birth'}) ;}
2703 foreach my $key ( keys %$adr ) {
2704 $adr->{$adr} = nkf('-w -Z3',$adr->{$adr});
2705 }
2706
2707 my ($address) = $adr->{'address'};
2708 my ($birth) = $adr->{'birth'};
2709 my ($company) = $adr->{'office'};
2710 my ($email) = $adr->{'email'};
2711 my ($fax) = $adr->{'fax'};
2712 my ($first_name) = $adr->{'first-name'};
2713 my ($first_name_yomi) = $adr->{'first-name-yomi'};
2714 my ($home_address) = $adr->{'home-address'};
2715 my ($home_fax) = $adr->{'home-fax'};
2716 my ($home_keitai) = $adr->{'home-keitai'};
2717 my ($home_state) = $adr->{'home_state'};
2718 my ($home_tel) = $adr->{'home-tel'};
2719 my ($home_zip) = $adr->{'home-zip'};
2720 my ($keitai) = $adr->{'keitai'};
2721 my ($last_name) = $adr->{'name'};
2722 my ($memo) = $adr->{'memo'};
2723 my ($name_yomi) = $adr->{'name-yomi'};
2724 my ($name) = $adr->{'name'};
2725 my ($office_yomi) = $adr->{'office-yomi'};
2726 my ($section) = $adr->{'section'};
2727 my ($state) = $adr->{'state'};
2728 my ($tel) = $adr->{'tel'};
2729 my ($title) = $adr->{'title'};
2730 my ($zip) = $adr->{'zip'};
2731
2732 $self->print ( "<Contact ");
2733 $self->print ( "LastName=\"$last_name\" " ) if ($last_name);
2734 $self->print ( "FirstName=\"$first_name\" " ) if ($first_name);
2735 $self->print ( "JobTitle=\"$title\" " ) if ($title);
2736 $self->print ( "Department=\"$section\" " ) if ($section);
2737 $self->print ( "Company=\"$company\" " ) if ($company);
2738 $self->print ( "Birthday=\"$birth\" " ) if ($birth);
2739 $self->print ( "BusinessPhone=\"$tel\" " ) if ($tel);
2740 $self->print ( "BusinessFax=\"$fax\" " ) if ($fax);
2741 $self->print ( "BusinessStreet=\"$address\" " ) if ($address);
2742 $self->print ( "BusinessState=\"$state\" " ) if ($state);
2743 $self->print ( "BusinessZip=\"$zip\" " ) if ($zip);
2744 $self->print ( "BusinessMobile=\"$keitai\" " ) if ($keitai);
2745 $self->print ( "HomePhone=\"$home_tel\" " ) if ($home_tel);
2746 $self->print ( "HomeMobile=\"$home_keitai\" " ) if ($home_keitai);
2747 $self->print ( "HomeFax=\"$home_fax\" " ) if ($home_fax);
2748 $self->print ( "HomeStreet=\"$home_address\" " ) if ($home_address);
2749 $self->print ( "HomeState=\"$home_state\" " ) if ($home_state);
2750 $self->print ( "HomeZip=\"$home_zip\" " ) if ($home_zip);
2751 $self->print ( "Emails=\"$email\" " ) if ($email);
2752 $self->print ( "Notes=\"$memo\" " ) if ($memo);
2753 $self->print ( "rid=\"$count\" ");
2754 $self->print ( "rinfo=\"1\" ");
2755 $self->print ( "LastNamePronunciation=\"$name_yomi\" " ) if ($name_yomi);
2756 $self->print ( "FirstNamePronunciation=\"$first_name_yomi\" " ) if ($first_name_yomi);
2757 $self->print ( "CompanyPronunciation=\"$office_yomi\" " ) if ($office_yomi);
2758 $self->print ( "/>\n");
2759
2760 $count--;
2761 }
2762 $self->print ( "</Contact>\n");
2763 $self->print ( "</AddressBook>\n");
2764 }
2765
2766 sub birth_date {
2767 my ($self,$date) = @_;
2768 my ($year,$month,$day,$hour,$min) = $date->localtime();
2769
2770 if ($date->is_day()) {
2771 return "$month/$day";
2772 }
2773 return "$year/$month/$day";
2774 }
2775
2776 sub print {
2777 my ($self,@data) = @_;
2778 print CAL nkf("--utf8",@data);
2779 }
2780
2781 #######################################################################/
2782
2783 package Calcon::Vcard_write;
2784 use strict;
2785 # use warnings;
2786 use NKF;
2787
2788 # VCARD 形式
2789
2790 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
2791 @ISA = ( 'Calcon::Writer' );
2792
2793 # Mac OS X 10.2 's Address Book requires utf-16
2794 # | nkf -Z -e|iconv -c -f EUC-JP -t UTF-16
2795 #
2796
2797 sub initialize {
2798 my ($self) = @_;
2799 $self->SUPER::initialize();
2800 $self->{'-fake-allday'} = 1;
2801 $self->{'-time-for-allday'} = 12*3600;
2802 $self->{'-add-time-for-allday'} = 2*3600;
2803 }
2804
2805 sub record {
2806 my ($self,$keys,$record) = @_;
2807 my ($application);
2808
2809 if(defined($record->{'name'})) {
2810 $self->vcard($keys,$record);
2811 } elsif(defined($record->{'date'})) {
2812 if ($record->{'date'}) { return if ($self->{'-future-only'} && ! $record->{'date'}->future()); }
2813 $self->vcal($keys,$record);
2814 } else {
2815 # I don't know.
2816 }
2817 }
2818
2819 sub end_file {
2820 my ($self) = @_;
2821 if ($self->{'-vcal-opening'}) {
2822 print "END:VCALENDAR\n";
2823 $self->{'-vcal-opening'} = 0;
2824 }
2825 }
2826
2827 sub print {
2828 my ($self,@data) = @_;
2829 foreach (@data) {
2830 my $data = nkf('-s -Z',$_);
2831 $data =~ s/\354\276/\203_/g;
2832 $data =~ s/\356\276/ /g;
2833 $data =~ s/\356\277/ /g;
2834 $data =~ s/([^\200-\377])\\/$1\200/g;
2835 # $data =~ s/\201/\/g;
2836 $data = nkf('-w',$_);
2837 $data =~ s/\000/ /g;
2838 print $data;
2839 }
2840 }
2841
2842 sub vcal {
2843 my ($self,$keys,$record) = @_;
2844 my (%record) = %{$record};
2845 my $data;
2846
2847 my $timezone = "Asia/Tokyo";
2848
2849 if (! $self->{'-vcal-opening'}) {
2850 print(<<"EOFEOF");
2851 BEGIN:VCALENDAR
2852 CALSCALE:GREGORIAN
2853 X-WR-TIMEZONE;VALUE=TEXT:Asia/Tokyo
2854 METHOD:PUBLISH
2855 VERSION:2.0
2856 EOFEOF
2857 $self->{'-vcal-opening'} = 1;
2858 }
2859
2860 if ($record{'date'}->is_allday() && $self->{'-fake-allday'}) {
2861 $record{'date'}=$record{'date'}->add($self->{'-time-for-allday'});
2862 }
2863 my $dtstart = "\nDTSTART;TZID=$timezone:".$self->date($record{'date'});
2864 my ($dtend,$dtstamp);
2865
2866 if (! defined( $record{'end-date'}) || $record{'end-date'} == $record{'date'} ) {
2867 # $dtend = "\nDURATION:PT2H"; this is useless for iCal
2868 $record{'end-date'} = $record{'date'}->add(
2869 $self->{'-add-time-for-allday'});
2870 $dtend = "\nDTEND;TZID=$timezone:".$self->date($record{'end-date'});
2871 } else {
2872 $dtend = "\nDTEND;TZID=$timezone:".$self->date($record{'end-date'});
2873 }
2874 if (defined( $record{'modify-date'})) {
2875 $dtstamp = "\nDTSTAMP;TZID=$timezone:".$self->date($record{'modify-date'});
2876 }
2877
2878 my $summary;
2879 my $description;
2880 if (defined($record{'memo'})) {
2881 $summary = $record{'memo'};
2882 $summary =~ s/[\r\n].*$//; $description = $&;
2883
2884 $description =~ s/[\n\r]/\n /mg;
2885 $description =~ s/\s*$//;
2886 $summary =~ s/[\n\r]/ /mg;
2887 $summary =~ s/\s*$//;
2888 }
2889
2890 if ($description eq $summary) {
2891 $description = "";
2892 } else {
2893 if ($description) {
2894 $description = "\nDESCRIPTION: $description";
2895 }
2896 }
2897 return if (! $description && ! $summary );
2898
2899 # DURATION:PT1H = "DURATION:PT1H";
2900 # X-WR-CALNAME;VALUE=TEXT:ホーム
2901 # X-WR-TIMEZONE;VALUE=TEXT:Asia/Tokyo
2902 # SEQUENCE:$i
2903
2904 $self->print(<<"EOFEOF");
2905 BEGIN:VEVENT
2906 SUMMARY:$summary$dtstart$dtend$description$dtstamp
2907 END:VEVENT
2908 EOFEOF
2909 # print "\n";
2910 }
2911
2912 sub date {
2913 my ($self,$date) = @_;
2914 my ($year,$month,$day,$hour,$min,$sec) = $self->localtime($date);
2915
2916 $date = sprintf("%04d%02d%02dT%02d%02d%02d",
2917 $year,$month,$day,$hour,$min,$sec);
2918 return $date;
2919 }
2920
2921 sub vcard {
2922 my ($self,$keys,$record) = @_;
2923 my (%record) = %{$record};
2924 my $data;
2925
2926 if(defined($record{'office'})) {
2927 $record{'office'} = 'etc' if(! $record{'office'}) ;
2928 }
2929 if(defined($record{'name-yomi'})) {
2930 $record{'name-yomi'} =~ s/^ *//;
2931 }
2932 if(defined($record{'office-yomi'})) {
2933 $record{'office-yomi'} =~ s/^ *//;
2934 }
2935 $record{'secret'} = ' ' if(! $record{'secret'});
2936 $record{'alarm'} = ' ' if(! $record{'alarm'}) ;
2937 $record{'class'} = ' ' if(! defined($record{'class'}));
2938 $record{'print-format'} = '2220' if(! defined($record{'print-format'}));
2939 $record{'mark'} = '00' if(! defined($record{'mark'}));
2940 $record{'priority'} = '01' if(! defined($record{'priority'}));
2941 if ($record{'time'} =~ /(.*)-(.*)/) {
2942 $record{'time'} = $1;
2943 $record{'end-time'} = $2;
2944 }
2945
2946 print "begin:vcard\n";
2947 print "version:3.0\n";
2948 if(defined $record{'name'}) {
2949 $data = $record{'name'};
2950 print "FN:$data\n" if($data);
2951 if(0 && defined $record{'name-yomi'}) {
2952 $data = join(";",split(/ /,$record{'name-yomi'}));
2953 print "N:$data\n" if($data);
2954 } else {
2955 $data = join(";",split(/ /,$data));
2956 print "N:$data\n" if($data);
2957 }
2958 if(defined $record{'name-yomi'}) {
2959 my ($last , $first , $last_yomi , $first_yomi );
2960 $last = $first = $last_yomi = $first_yomi = '';
2961 ($last,$first) = split(/ /,$record{'name'});
2962 ($last_yomi,$first_yomi) = split(/ /,$record{'name-yomi'}),
2963 print YOMI $last,"\n";
2964 print YOMI $last_yomi,"\n";
2965 print YOMI $first,"\n";
2966 print YOMI $first_yomi,"\n";
2967 }
2968
2969 # print "fn:$data\n" if($data);
2970 # if(defined $record{'office'}) {
2971 # $data = $data.";".$record{'office'};
2972 # }
2973 # print "n:$data\n" if($data);
2974 }
2975 if(defined $record{'office'}) {
2976 $data = "$record{'office'}";
2977 if(defined $record{'section'}) {
2978 $data .= ";".$record{'section'};
2979 }
2980 print "org:$data\n" if($data);
2981 }
2982 if(defined $record{'title'}) {
2983 $data = "$record{'title'}";
2984 print "title:$data\n" if($data);
2985 }
2986 if(defined $record{'address'}) {
2987 my ($adr1 , $adr2 , $adr_state , $adr_zip , $adr_country );
2988 $adr1 = $adr2 = $adr_state = $adr_zip = $adr_country = '';
2989 $data = $record{'address'};
2990 $adr1 = $record{'address'};
2991 # ADD:番地;;町村;沖縄;903-0213;日本
2992 if(defined $record{'zip'}) {
2993 $adr_zip = $record{'zip'};
2994 }
2995 # print "adr;type=work;type=pref:$data\n" if($data);
2996 print "adr;type=work;type=pref:;;$adr1;$adr2;$adr_state;$adr_zip;$adr_country\n" if ($data);
2997 print "label;type=work;type=pref:$adr_zip $data\n" if($data);
2998 }
2999 if(defined $record{'tel'}) {
3000 $data = $record{'tel'};
3001 print "tel;type=work:$data\n" if($data);
3002 }
3003 if(defined $record{'tel2'}) {
3004 $data = $record{'tel2'};
3005 print "tel;type=cell:$data\n" if($data);
3006 }
3007 if(defined $record{'fax'}) {
3008 $data = $record{'fax'};
3009 print "tel;type=fax:$data\n" if($data);
3010 }
3011 if(defined $record{'mail'}) {
3012 $data = $record{'mail'};
3013 print "email;internet:$data\n" if($data);
3014 }
3015 if(defined $record{'birth'}) {
3016 $data = $record{'birth'};
3017 print "bday:$data\n" if($data);
3018 }
3019 if(defined $record{'name-yomi'}) {
3020 $data = $record{'name-yomi'};
3021 print "x-custom1:$data\n" if($data);
3022 }
3023 if(defined $record{'office-yomi'}) {
3024 $data = $record{'office-yomi'};
3025 print "x-custom2:$data\n" if($data);
3026 }
3027 print "end:vcard\n";
3028 print "\n";
3029 }
3030
3031 #######################################################################/
3032
3033 package Calcon::File_read;
3034 use strict;
3035 # use warnings;
3036 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
3037 @ISA = ( 'Calcon::Reader') ;
3038
3039 # File 形式の読み込み。かなりいいかげんなものでも読み込むが...
3040
3041 use NKF;
3042
3043 sub initialize {
3044 my ($self) = @_;
3045 $self->SUPER::initialize();
3046 $self->{'-email-extract'} = 1;
3047 }
3048
3049 sub decode {
3050 my ($self,$file) = @_;
3051 my $out = $self->{'-output'};
3052
3053 $self->{'-file'} = $file;
3054 open(F,"<".$file);
3055
3056 $out->start_file('');
3057
3058 local($/) = "\n\n";
3059 while(<F>) {
3060 $self->buffer_decode($_);
3061 }
3062 $out->end_file('');
3063 }
3064
3065 # いいかげんなものでも読み込むためのルーチン
3066
3067 sub buffer_decode {
3068 my ($self,$buf,%initial) = @_;
3069 my @data;
3070 my $key;
3071 my ($debug) = $self->{'-debug'};
3072 my $i = 0;
3073 my $out = $self->{'-output'};
3074
3075 # $_ =~ s/\n\s+/ /g;
3076 # s/\n[ \t]/\037/g;
3077
3078 $buf =~ s/^\s*//;
3079 @data = split(/\n/,$buf);
3080 my $record = $self->make_record;
3081 my $keys = [];
3082
3083 foreach my $key (keys %initial) {
3084 $record->{$key} = $initial{$key};
3085 push(@$keys,$key);
3086 }
3087 foreach $_ (@data) {
3088 if (s/^([A-Za-z][-A-Za-z0-9_]*):\s*//) {
3089 $key = $1;
3090 } else {
3091 $key = 'memo';
3092 }
3093 if ($key eq 'Subject') {
3094 $key = 'memo';
3095 }
3096 s/^(\201\100)*//;
3097 $_ = nkf('-sZ',$_);
3098 if($key eq 'time' || $key eq 'end-time') {
3099 $record->{$key} = $_;
3100 next;
3101 }
3102 if(!($key eq 'date' || $key eq 'end-date')) {
3103 my $save = $_;
3104 my $savekey = $key;
3105
3106 my $stime;
3107 my $etime;
3108 # use extra . to avoid regex bug
3109 if (/(\d+:\d+).*[-~].*?(\d+:\d+)/) {
3110 $stime = $1;
3111 $etime = $2;
3112 # print "*0** $stime $etime\n";
3113 } elsif (/(\d+:\d+).*\201\140.*?(\d+:\d+)/) { # 〜
3114 $stime = $1;
3115 $etime = $2;
3116 # print "*1** $stime $etime\n";
3117 } elsif (/(\d+:\d+).*\201\250.*?(\d+:\d+)/) { # →
3118 $stime = $1;
3119 $etime = $2;
3120 # print "*2** $stime $etime\n";
3121 } elsif (/(\d+:\d+)/) {
3122 $stime = $1;
3123 }
3124 if ($stime) {
3125 my $date = $record->{'date'};
3126 if ($date) {
3127 if ($record->{'memo'}) {
3128
3129 $self->date_normalize($keys,$record);
3130 $out->record($keys,$record);
3131
3132 $record = $self->make_record; $keys = [];
3133 foreach my $key (keys %initial) {
3134 $record->{$key} = $initial{$key};
3135 push(@$keys,$key);
3136 }
3137 $record->{'date'} = $date;
3138 push(@$keys,'date');
3139 }
3140 if (! $record->{'time'}) {
3141 $record->{'time'} = $stime;
3142 push(@$keys,'time');
3143 }
3144 if (! $record->{'end-time'}) {
3145 $record->{'end-time'} = $etime;
3146 push(@$keys,'end-time');
3147 }
3148 $_ = $save;
3149 $key = $savekey;
3150 }
3151 }
3152 } else {
3153 # don't append time field
3154 push(@$keys,$key);
3155 $record->{$key} = $_;
3156 next;
3157 }
3158 if ($self->{'-email-extract'}) {
3159 if(s/[-a-zA-Z0-9.]+@[-a-zA-Z0-9.]+//) {
3160 if (defined($record->{'mail'})) {
3161 $record->{'mail'} .= ",".$&;
3162 } else {
3163 $record->{'mail'} = $&;
3164 push(@$keys,'mail');
3165 }
3166 }
3167 }
3168 next if (! $_);
3169 if(defined $record->{$key}) {
3170 $record->{$key} .= "\n" . $_; # append for duplicated field
3171 } else {
3172 push(@$keys,$key);
3173 $record->{$key} = $_;
3174 }
3175 }
3176 $self->date_normalize($keys,$record);
3177 $out->record($keys,$record);
3178 }
3179
3180 #######################################################################/
3181
3182 package Calcon::Xcalendar_read;
3183
3184 # XCalendar 形式の読み込み。かなりいいかげんなものでも読み込むが...
3185
3186 use strict;
3187 # use warnings;
3188 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
3189 use Time::Local;
3190 use NKF;
3191
3192 @ISA = ( 'Calcon::File_read' ) ;
3193
3194 sub decode {
3195 my ($self,$file) = @_;
3196 my @data;
3197 my $key;
3198 my ($debug) = $self->{'-debug'};
3199 my $i = 0;
3200 my $out = $self->{'-output'};
3201
3202 $self->{'-file'} = $file;
3203 my $calendar = $file;
3204
3205 # my $i = 0;
3206 my $found = 1;
3207 my $today = time;
3208 my $daytime = 60*60*24*2;
3209
3210 my $all = 1;
3211 my $tomorrow = $self->{'-tomorrow'};
3212
3213 my %xcal;
3214
3215 while(<$calendar/xc*>) {
3216 my $file = $_;
3217 my $date = $self->make_xcalendar_date($file);
3218 next if (! defined $date->unix_time);
3219 next if ($self->{'-tomorrow'} && ! $date->tomorrow());
3220 next if ($self->{'-future-only'} && ! $date->future());
3221 $xcal{$date->unix_time()} = $file;
3222 }
3223
3224 $out->start_file('');
3225
3226 $i= $all ? -1 : 4;
3227 foreach my $key ( sort {$a <=> $b;} keys(%xcal) ) {
3228 $found = 0;
3229 open(XCAL,$xcal{$key}) || next;
3230 my ($sec,$min,$hour,$day,$month,$year,$wday,$date_,$isdst) =
3231 localtime($key);
3232 my $date;
3233 $date = ($year+1900)."/".($month+1)."/$day";
3234 local($/) = "\n\n";
3235 while(<XCAL>) {
3236 $self->buffer_decode($_,'date'=>$date);
3237 }
3238 last if($i-- == 0);
3239 }
3240 $out->end_file('');
3241 }
3242
3243 #######################################################################/
3244
3245 # 別に Xcalendar class のメソッドでもいいんだけど。
3246
3247 package Calcon::Date ;
3248
3249 use vars qw(%monthname);
3250
3251 sub make_xcalendar_date {
3252 my ($self,$name) = @_;
3253
3254 my $date;
3255 if ($name =~ m^xc([0-9]+)([A-Za-z]+)([0-9]+)$^) {
3256 my $day = $1 ;my $month = $monthname{$2}; my $year = $3;
3257 # if($year > 1900) { $year -= 1900; }
3258 $date = &timelocal(0,0,0,$day,$month,$year,0,0,0);
3259 }
3260 bless \$date;
3261 }
3262
3263 #######################################################################/
3264
3265 package Calcon::Basic ;
3266
3267 sub make_xcalendar_date {
3268 my ($self,$name) = @_;
3269 $date_class->make_xcalendar_date($name);
3270 }
3271
3272 #######################################################################/
3273
3274 package Calcon::Xcalendar_write ;
3275
3276 # Xcalendar 形式の書き出し
3277
3278 use strict;
3279 # use warnings;
3280 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
3281 @ISA = ('Calcon::Writer');
3282 use NKF;
3283
3284 sub initialize {
3285 my ($self) = @_;
3286 if (defined $self->{'-file'}) {
3287 $self->{'-directory'} = defined $self->{'-file'};
3288 undef $self->{'-file'};
3289 } else {
3290 $self->{'-directory'} = "$ENV{'HOME'}/Calendar.new";
3291 }
3292 $self->SUPER::initialize();
3293 mkdir $self->{'-directory'};
3294 }
3295
3296 sub record {
3297 my ($self,$keys,$record) = @_;
3298 my @keys = @$keys;
3299 my %record = %$record;
3300 # should be override
3301 return if (! $record->{'date'} );
3302 return if ($self->{'-future-only'} && ! $record->{'date'}->future());
3303 $self->open($record->{'date'});
3304 foreach my $key (@keys) {
3305 my $value = $record{$key};
3306 if (ref $value) {
3307 $value = $value->value();
3308 }
3309 print nkf('-e',"$key: $value\n") if ($value);
3310 }
3311 print "\n";
3312 $self->close();
3313 }
3314
3315 sub open {
3316 my ($self,$date) = @_;
3317 my $name = $self->{'-directory'}."/".
3318 $date->xcalendar_file_name;
3319 open(OUT,">>".$name);
3320 select OUT;
3321 }
3322
3323 sub close {
3324 close OUT;
3325 }
3326
3327 #######################################################################/
3328
3329 package Calcon::Date;
3330
3331 sub xcalendar_file_name {
3332 my ($self) = @_;
3333 my ($year,$month,$day,$hour,$min) = $self->localtime();
3334 sprintf("xc%02d%s%04d",$day,$monthname[$month-1],$year);
3335 }
3336
3337 #######################################################################/
3338
3339 package Calcon::Entourage_read;
3340
3341 # Mac のEntourage から AppleScript 経由で読み込む
3342 # ファイルからでも読み込み可能
3343 # Zaurus のCSVも読めた方が良いね
3344 # 日本語専用
3345
3346 use strict;
3347 # use warnings;
3348 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
3349 use Mac::AppleScript qw(RunAppleScript);
3350 use NKF;
3351 use Carp;
3352 @ISA = ( 'Calcon::File_read' ) ;
3353
3354 # We use Applescript, but it is very slow.
3355 # get_all_event is slightly faster.
3356 # To convert contact, it is better to use export address in Entourage Menu.
3357 # If it has a file name other than '/dev/stdin', it assumes export file.
3358
3359 my %item_keys = (
3360 "名"=>"first name",
3361 "姓"=>"last name",
3362 "敬称"=>"sir name",
3363 "Suffix"=>"suffix",
3364 "ニックネーム"=>"nick name",
3365 "会社名"=>"company",
3366 "役職"=>"title",
3367 "部署"=>"department",
3368 "番地 (勤務先)"=>"business address street address",
3369 "市区町村 (勤務先)"=>"business address city",
3370 "都道府県 (勤務先)"=>"business address state",
3371 "郵便番号 (勤務先)"=>"business address zip",
3372 "国/地域 (勤務先)"=>"business address country",
3373 "Web ページ (勤務先)"=>"www",
3374 "番地 (自宅)"=>"home address street address",
3375 "市区町村 (自宅)"=>"home address city",
3376 "都道府県 (自宅)"=>"home address state",
3377 "郵便番号 (自宅)"=>"home address zip",
3378 "国/地域 (自宅)"=>"home address country",
3379 "Web ページ (自宅)"=>"home www",
3380 "電話 1 (自宅)"=>"home phone number",
3381 "電話 2 (自宅)"=>"home tel2",
3382 "FAX (自宅)"=>"home fax number",
3383 "電話 1 (勤務先)"=>"business phone number",
3384 "電話 2 (勤務先)"=>"tel2",
3385 "FAX (勤務先)"=>"business fax number",
3386 "ポケットベル"=>"pager",
3387 "携帯電話"=>"mobile phone number",
3388 "電話 (メイン)"=>"main phone number",
3389 "電話 (アシスタント)"=>"sub tel",
3390 "電話 (ユーザー設定 1)"=>"tel 1",
3391 "電話 (ユーザー設定 2)"=>"tel 2",
3392 "電話 (ユーザー設定 3)"=>"tel 3",
3393 "電話 (ユーザー設定 4)"=>"tel 4",
3394 "電子メール アドレス 1"=>"mail-address",
3395 "電子メール アドレス 2"=>"business mail",
3396 "電子メール アドレス 3"=>"mail",
3397 "電子メール アドレス 4"=>"mail-to",
3398 "電子メール アドレス 5"=>"mail 5",
3399 "電子メール アドレス 6"=>"mail 6",
3400 "電子メール アドレス 7"=>"mail 7",
3401 "電子メール アドレス 8"=>"mail 8",
3402 "電子メール アドレス 9"=>"mail 9",
3403 "電子メール アドレス 10"=>"mail 10",
3404 "電子メール アドレス 11"=>"mail 11",
3405 "電子メール アドレス 12"=>"mail 12",
3406 "電子メール アドレス 13"=>"mail 13",
3407 "メモ 1"=>"memo",
3408 "メモ 2"=>"memo 2",
3409 "メモ 3"=>"memo 3",
3410 "メモ 4"=>"memo 4",
3411 "メモ 5"=>"memo 5",
3412 "メモ 6"=>"memo 6",
3413 "メモ 7"=>"memo 7",
3414 "メモ 8"=>"memo 8",
3415 "日付 1 :"=>"date",
3416 "日付 2 :"=>"date 2",
3417 "配偶者"=>"spouse",
3418 "誕生日"=>"birthday",
3419 "記念日"=>"aniversary",
3420 "備考"=>"note",
3421 "年齢"=>"age",
3422 "星座"=>"astology sign",
3423 "血液型"=>"blood type",
3424 "会社名 (ふりがな)"=>"company furigana",
3425 "名 (ふりがな)"=>"first name furigana",
3426 "姓 (ふりがな)"=>"last name furigana",
3427 "配偶者名 (ふりがな)"=>"spouse furigana",
3428 "趣味"=>"play",
3429 );
3430
3431 $| = 0;
3432 # my $tell = "tell application \"Microsoft Entourage\"\n";
3433 $tell = "tell application \"Microsoft Entourage\"\n";
3434
3435 sub decode {
3436 my ($self,$file) = @_;
3437 my ($debug) = $self->{'-debug'};
3438 my $out = $self->{'-output'};
3439 if (! $file || $file ne '/dev/stdin') {
3440 $self->read_export($file);
3441 }
3442
3443 $out->start_file('');
3444 $self->get_all_event() if (! $self->{'-address-only'});
3445 $self->get_all_contact() if (! $self->{'-calendar-only'});
3446 $out->end_file('');
3447
3448 }
3449
3450 sub date {
3451 my ($self,$date)=@_;
3452 my @date = ($date =~ /(\d+)/g);
3453 if ($date =~ /PM$/) {
3454 if ($date[3]==12) { $date[3]=0;}
3455 $date[3]+=12;
3456 }
3457 return $self->make_date("$date[0]/$date[1]/$date[2] $date[3]:$date[4]");
3458 }
3459
3460 sub read_export {
3461 my ($self,$file) = @_;
3462
3463 open(IN,"<$file") or cloak("$@");
3464 local($/) = "\r";
3465
3466 my $title = <IN>;
3467 chop($title);
3468
3469 return if (eof(IN));
3470
3471 my @keys = split(/\t/,nkf('-eS',$title));
3472 my $i = 0;
3473 my %keys;
3474 foreach my $key (@keys) {
3475 $keys{$item_keys{$key}} = $i++;
3476 }
3477 # foreach my $key (@keys) {
3478 # print "$key:$item_keys{$key}:$keys{$item_keys{$key}}\n";
3479 # }
3480
3481 $self->{'-input-keys'} = \%keys;
3482 my $i0 = 0;
3483 while(<IN>) {
3484 my @items;
3485 chop;
3486 @items = split(/\t/,$_);
3487 $self->{'-input'}->[$i0++] = \@items;
3488 }
3489 $self->{'-input-count'} = $i0;
3490 }
3491
3492 sub property {
3493 my ($self,$contact,$id,$property,$record,$key) = @_;
3494 my $result;
3495 if ($self->{'-input-count'}) {
3496 $result = $self->{'-input'}->[$id]->[$self->{'-input-keys'}->{$property}];
3497 if (! defined($self->{'-input-keys'}->{$property}) ) {
3498 print "$property not found\n";
3499 }
3500 } else {
3501 $result = RunAppleScript("${tell}${property} of ${contact} $id\nend tell\n");
3502 }
3503 $result =~ s/^\"//;
3504 $result =~ s/\"$//;
3505 if (defined($record) && $result ne '') {
3506 if ($key =~ /date/ || $key =~ /birth/) {
3507 $record->{$key} = $self->date($result);
3508 } else {
3509 $record->{$key} = nkf('-eS',$result);
3510 }
3511 } else {
3512 nkf('-eS',$result);
3513 }
3514 }
3515
3516 sub address {
3517 my ($self,$id,$property,$record,$key) = @_;
3518 my $address;
3519 my ($street , $zip , $state , $country , $city);
3520
3521 if ($self->{'-input-count'}) {
3522 my $l = $self->{'-input'}->[$id];
3523 my $k = $self->{'-input-keys'};
3524 $address = $l->[$k->{"$property street address"}];
3525 $zip = $l->[$k->{"$property zip"}];
3526 $state = $l->[$k->{"$property state"}];
3527 $city = $l->[$k->{"$property city"}];
3528 $country = $l->[$k->{"$property country"}];
3529 } else {
3530 $address = RunAppleScript("${tell}${property} of contact $id\nend tell\n");
3531 $address =~ /street address:"([^"]*)"/ && ($street = $1);
3532 $zip =~ /zip:"([^"]*)"/ && ($zip = $1);
3533 $state =~ /state:"([^"]*)"/ && ($state = $1);
3534 $city =~ /city:"([^"]*)"/ && ($city = $1);
3535 $country =~ /country:"([^"]*)"/ && ($country = $1);
3536 }
3537
3538
3539 $record->{$key} = nkf('-eS',"$state $city $street $country")
3540 if ($state||$city||$street||$country);
3541 if ($zip && $key =~ /home/) {
3542 $record->{'home-zip'} = $zip;
3543 } else {
3544 $record->{'zip'} = $zip if ($zip);
3545 }
3546 }
3547
3548 sub get_all_contact {
3549 my ($self) = @_;
3550 my $out = $self->{'-output'};
3551 my $count;
3552 if ($self->{'-input-count'}) {
3553 $count = $self->{'-input-count'};
3554 } else {
3555 $count = RunAppleScript("${tell}count of contact\nend tell\n") or croak("$@");
3556 }
3557
3558 foreach my $id ( 1..$count ) {
3559 $self->contact($id);
3560 }
3561 }
3562
3563 sub contact {
3564 my ($self,$id) = @_;
3565 my $record = $self->make_record;
3566
3567 $self->property('contact',$id,'business phone number',$record,'tel');
3568 $self->property('contact',$id,'home phone number',$record,'tel-home');
3569 $self->property('contact',$id,'mobile phone number',$record,'mobile-tel');
3570 $self->property('contact',$id,'main phone number',$record,'tel');
3571 $self->property('contact',$id,'home fax number',$record,'home-fax');
3572 $self->property('contact',$id,'business fax number',$record,'fax');
3573
3574 my $name = $self->property('contact',$id,'last name');
3575 my $first_name = $self->property('contact',$id,'first name');
3576 $record->{'name'} = ($name && $first_name)?"$name $first_name":
3577 ($name)?$name:$first_name;
3578
3579 my $name_p = $self->property('contact',$id,'last name furigana');
3580 my $first_name_p = $self->property('contact',$id,'first name furigana');
3581 $record->{'name-yomi'} = ($name_p && $first_name_p)?"$name_p $first_name_p":
3582 ($name_p)?$name_p:$first_name_p;
3583
3584 $self->property('contact',$id,'department',$record,'section');
3585 $self->property('contact',$id,'title',$record,'title');
3586
3587 $self->address($id,'business address',$record,'address');
3588 $self->address($id,'home address',$record,'home-address');
3589
3590 my $mail = $self->property('contact',$id,'mail');
3591 my $mail1 = $self->property('contact',$id,'mail-to');
3592 if ($mail) { $mail .= ",".$mail1 ;} else { $mail = $mail1;}
3593 my $mail2 = $self->property('contact',$id,'mail-address');
3594 if ($mail) { $mail .= ",".$mail1 ;} else { $mail = $mail2;}
3595
3596 $self->property('contact',$id,'birthday',$record,'birth');
3597 $self->property('contact',$id,'company',$record,'office');
3598 $self->property('contact',$id,'company furigana',$record,'office-yomi');
3599
3600 my $keys = [];
3601 push(@$keys,keys %{$record});
3602
3603 # $self->date_normalize($keys,$record);
3604 my $out = $self->{'-output'};
3605 $out->record($keys,$record);
3606 }
3607
3608 sub get_all_event {
3609 my ($self) = @_;
3610 my $out = $self->{'-output'};
3611 my $count ;
3612 if ($self->{'-input-count'}) {
3613 for(my $id=1; $id <= $count ;$id++) {
3614 $self->event($id);
3615 }
3616 return;
3617 } elsif ($self->{'-future-only'}) {
3618 my $today = $self->today();
3619 my ($year,$mon,$mday,$hour,$min) = $today->localtime();
3620
3621 $_ = "${tell}id of every event whose start time > date \"$year/$mon/$mday\"\nend tell\n";
3622 $count = RunAppleScript($_) or cloak("$@ $_");
3623 for my $id ($count =~ /(\d+)/g) {
3624 $self->event_id($id);
3625 }
3626 } else {
3627 $count = RunAppleScript("${tell}count of event\nend tell\n") or croak("$@");
3628 for(my $id=1; $id <= $count ;$id++) {
3629 $self->event($id);
3630 }
3631 }
3632 }
3633
3634 sub event {
3635 my ($self,$id) = @_;
3636 my $record = $self->make_record;
3637
3638 $self->property('event',$id,'all day event',$record,'all-day');
3639 $self->property('event',$id,'start time',$record,'date');
3640
3641 if ($record->{'all-day'} ne "true") {
3642 $self->property('event',$id,'end time',$record,'end-date');
3643 }
3644 $self->property('event',$id,'subject',$record,'summary');
3645 $self->property('event',$id,'content',$record,'memo');
3646
3647 my $keys = [];
3648 push(@$keys,keys %{$record});
3649
3650 my $out = $self->{'-output'};
3651 $out->record($keys,$record);
3652 }
3653
3654 sub event_id {
3655 my ($self,$id) = @_;
3656 my $record = $self->make_record;
3657
3658 $self->property('event id',$id,'all day event',$record,'all-day');
3659 $self->property('event id',$id,'start time',$record,'date');
3660
3661 if ($record->{'all-day'} ne "true") {
3662 $self->property('event id',$id,'end time',$record,'end-date');
3663 }
3664 $self->property('event id',$id,'subject',$record,'summary');
3665 $self->property('event id',$id,'content',$record,'memo');
3666
3667 my $keys = [];
3668 push(@$keys,keys %{$record});
3669
3670 my $out = $self->{'-output'};
3671 $out->record($keys,$record);
3672 }
3673
3674
3675 #######################################################################/
3676
3677 package Calcon::Vcard_read;
3678
3679 # Vcard / Vcal 形式を読み込む
3680 # Vcard に読みがないのが日本語向きじゃないね
3681
3682 use strict;
3683 # use warnings;
3684 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
3685
3686 @ISA = ( 'Calcon::File_read' ) ;
3687
3688 sub decode {
3689 my ($self,$file) = @_;
3690 my ($debug) = $self->{'-debug'};
3691 my $out = $self->{'-output'};
3692 my $record;
3693 my $keys;
3694
3695 $self->{'-file'} = $file;
3696 open(F,"<".$file);
3697
3698 $out->start_file('');
3699
3700 while(<F>) {
3701 if (/^begin:\s*vcalendar/i) {
3702 } elsif (/^adr(.*):\s*(.*)/i) { $self->items($record,'address',$1,$2);
3703 } elsif (/^bday:\s*(.*)/i) { $record->{'birth'} = $self->make_date($1);
3704 } elsif (/^begin:\s*vcard/i) { $record = $self->make_record;
3705 } elsif (/^begin:\s*vevent/i) { $record = $self->make_record;
3706 } elsif (/^calscale:\s*(.*)/i) {
3707 } elsif (/^uid:\s*(.*)/i) {
3708 } elsif (/^description:\s*/i) { $record->{'memo'} .= $1;
3709 } elsif (/^dtend(.*):\s*(.*)/i) { $record->{'end-date'} = $self->date($2,$1?$1:$record->{'timezone'});
3710 } elsif (/^dtstamp(.*):\s*(.*)/i) { $record->{'modify-date'} = $self->date($2,$1?$1:$record->{'timezone'});
3711 } elsif (/^dtstart(.*):\s*(.*)/i) { $record->{'date'} = $self->date($2,$1?$1:$record->{'timezone'});
3712 } elsif (/^duration:\s*(.*)/i) { $self->duration($record,$1);
3713 } elsif (/^email(.*):\s*(.*)/i) { $self->items($record,'email',$1,$2);
3714 } elsif (/^end:\s*vcard/i) { $self->vcard($record);
3715 } elsif (/^end:\s*vevent/i) { $self->event($record);
3716 } elsif (/^fn:\s*(.*)/i) { $self->name($record,$1);
3717 } elsif (/^label(.*):\s*(.*)/i) { $self->items($record,'address',$1,$2);
3718 } elsif (/^method:\s*(.*)/i) { $record->{'publish'} = $1;
3719 } elsif (/^n:\s*(.*)/i) { $self->name($record,split(/;/,$1));
3720 } elsif (/^org:\s*(.*)/i) { $record->{'office'} = $1;
3721 } elsif (/^sequence:\s*(.*)/i) { $record->{'sequence'} = $1;
3722 } elsif (/^summary:\s*(.*)/i) { $record->{'summary'} = $1;
3723 } elsif (/^tel(.*):\s*(.*)/i) { $self->items($record,'tel',$1,$2);
3724 } elsif (/^title:\s*/i) { $record->{'title'} = $1;
3725 } elsif (/^version:\s*(.*)/i) { $record->{'version'} = $1;
3726 } elsif (/^x-custom1:\s*(.*)/i) { $record->{'name-yomi'} = $1;
3727 } elsif (/^x-custom2:\s*(.*)/i) { $record->{'office-yomi'} = $1;
3728 } elsif (/^x-wr-calname.*:\s*(.*)/i) { $record->{'calendar'} = $1;
3729 } elsif (/^x-wr-timezone.*:\s*(.*)/i) { $record->{'timezone'} = $1;
3730 } else { $record->{'extra'} .= $_;
3731 }
3732 }
3733 $out->end_file('');
3734 }
3735
3736 sub duration {
3737 my ($self,$record,$duration)=@_;
3738 if ($duration =~ /pt(\d+)h/) {
3739 $record->{'duration'} = "$1:00";
3740 }
3741 }
3742
3743 sub date {
3744 my ($self,$date,$timezone)=@_;
3745 if ($date =~ /(\d\d\d\d)(\d\d)(\d\d)t(\d\d)(\d\d)(\d\d)/i) {
3746 return $self->make_date("$1/$2/$3 $4:$5");
3747 } elsif ($date =~ /(\d\d\d\d)(\d\d)(\d\d)/i) {
3748 return $self->make_date("$1/$2/$3");
3749 } else {
3750 return "";
3751 }
3752 }
3753
3754 sub event {
3755 my ($self,$record)=@_;
3756 my $out = $self->{'-output'};
3757 my $keys = [];
3758 push(@$keys,keys %{$record});
3759 $out->record($keys,$record);
3760 }
3761
3762 sub vcard {
3763 my ($self,$record)=@_;
3764 my $out = $self->{'-output'};
3765 my $keys = [];
3766 push(@$keys,keys %{$record});
3767 $out->record($keys,$record);
3768 }
3769
3770 sub items {
3771 my ($self,$record,$label,$type,$value)=@_;
3772 # $record->{''} = $1;;type=work;type=pref;
3773 # $adr1;$adr2;$adr_state;$adr_zip;$adr_country
3774 if ($type =~ /home/i) {
3775 $label = "home-".$label;
3776 } elsif ($type =~ /voice/i) {
3777 } elsif ($type =~ /internet/i) {
3778 } elsif ($type =~ /fax/i) {
3779 $label = "fax";
3780 } elsif ($type =~ /work/i) {
3781 }
3782 $record->{$label} = $value;
3783 }
3784
3785 sub name {
3786 my ($self,$record,@names)=@_;
3787 $record->{'name'} = "@names";
3788 }
3789
3790 1;
3791
3792 __END__
3793
3794 =cut
3795
3796 =head1 NAME
3797
3798 Calcon.pm -- Convert Various Calendar/Address data format
3799
3800 =head1 SYNOPSIS
3801
3802 use Calcon;
3803
3804 =head1 ABSTRACT
3805
3806 =head1 DESCRIPTION
3807
3808 =head2 EXPORT
3809
3810 =head1 SEE ALSO
3811
3812 =head1 AUTHOR
3813
3814 Shinji KONO, E<lt>kono@ie.u-ryukyu.ac.jpE<gt>
3815
3816 =head1 COPYRIGHT AND LICENSE
3817
3818 #######################################################################/
3819 ##
3820 ## Calendar/Address Format Converter
3821 ##
3822 ## Copyright (C) 2002 Shinji Kono
3823 ##
3824 ## このソースのいかなる複写,改変,修正も許諾します。ただし、
3825 ## その際には、誰が貢献したを示すこの部分を残すこと。
3826 ## 再配布や雑誌の付録などの問い合わせも必要ありません。
3827 ## 営利利用も上記に反しない範囲で許可します。
3828 ## バイナリの配布の際にはversion messageを保存することを条件とします。
3829 ## このプログラムについては特に何の保証もしない、悪しからず。
3830 ##
3831 ## Everyone is permitted to do anything on this program
3832 ## including copying, modifying, improving,
3833 ## as long as you don't try to pretend that you wrote it.
3834 ## i.e., the above copyright notice has to appear in all copies.
3835 ## Binary distribution requires original version messages.
3836 ## You don't have to ask before copying, redistribution or publishing.
3837 ## THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE.
3838 ##
3839 ##
3840 ## $Id$
3841 #######################################################################/
3842
3843
3844 =cut