File Coverage

blib/lib/Net/ICal/Component.pm
Criterion Covered Total %
statement 114 209 54.5
branch 37 88 42.0
condition 5 11 45.4
subroutine 15 23 65.2
pod 8 8 100.0
total 179 339 52.8


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -w
2             # -*- Mode: perl -*-
3             #======================================================================
4             #
5             # This package is free software and is provided "as is" without
6             # express or implied warranty. It may be used, redistributed and/or
7             # modified under the same terms as perl itself. ( Either the Artistic
8             # License or the GPL. )
9             #
10             # $Id: Component.pm,v 1.48 2001/08/04 04:59:36 srl Exp $
11             #
12             # (C) COPYRIGHT 2000-2001, Reefknot developers.
13             #
14             # See the AUTHORS file included in the distribution for a full list.
15             #======================================================================
16              
17             =head1 NAME
18              
19             Net::ICal::Component -- the base class for ICalender components
20              
21             =cut
22              
23             package Net::ICal::Component;
24 1     1   6 use strict;
  1         2  
  1         29  
25              
26 1     1   952 use UNIVERSAL;
  1         13  
  1         5  
27 1     1   27 use base qw(Class::MethodMapper);
  1         3  
  1         880  
28              
29 1     1   52659 use Net::ICal::Util qw(add_validation_error);
  1         4  
  1         3864  
30             =head1 SYNOPSIS
31              
32             You never create an instance of this class directly, so we'll assume
33             $c is an already created component.
34              
35             # returns an ICal string for this component.
36             $c->as_ical;
37              
38             =head1 DESCRIPTION
39              
40             This is the base class we derive specific ICal components from.
41             It contains a map of properties which can be set and accessed at will;
42             see the docs for Class::MethodMapper for more on how it works.
43              
44             =begin testing
45             use lib "./lib";
46             use Net::ICal;
47              
48             $comp = new Net::ICal::Alarm(
49             action => 'DISPLAY',
50             trigger => "20000101T073000",
51             description => "Wake Up!"
52             );
53              
54             =end testing
55              
56             =head1 CONSTRUCTORS
57              
58             =head2 new($name, $map, %args)
59              
60             Creates a new ICal component of type C<$name>, with Class::MethodMapper
61             map C<$map> and arguments C<%args>. You never call this directly, but
62             you use the specific component's new constructor instead, which in turn
63             calls this.
64              
65             =begin testing
66             TODO: {
67             local $TODO = "write tests for the new method, please";
68             ok(0, "need tests here");
69             };
70             =end testing
71             =cut
72              
73             sub _param_set {
74             #TODO: allow things like $foo->description ("blah blah", altrep => 'foo');
75 0     0   0 my ($self, $key, $val) = @_;
76 0         0 my ($class) = $self =~ /^(.*?)=/g;
77              
78 0         0 my @params = @{$self->get_meta ('options', $key)};
  0         0  
79 0 0       0 if (ref($val) eq 'HASH') {
80 0         0 foreach my $param (keys %$val) {
81 0 0       0 unless (grep { $_ eq lc($param) } ('content', @params)) {
  0         0  
82 0         0 warn "${class}->$key has no $param parameter. skipping.\n";
83 0         0 delete $val->{$param};
84             }
85             }
86 0         0 $self->{$key}->{value} = $val;
87             } else {
88 0         0 $self->{$key}->{value} = { content => $val };
89             }
90             }
91              
92             sub new {
93 44     44 1 92 my ($classname, $name, $map, %args) = @_;
94              
95             #TODO: WTF is a type 'volatile' and why are we using it?
96             # The Class::MethodMapper docs say that "Generally, a
97             # `parameter' is something that can be saved and restored,
98             # whereas a `volatile' is not serialized at save-time."
99             # Can someone clarify this? --srl
100             #BUG: 424107
101 44         218 $map->{'type'} = {
102             type => 'volatile',
103             doc => 'type of the component',
104             value => $name
105             };
106             # So we can keep a list of validation errors for Net::ITIP
107 44         209 $map->{'errlog'} = {
108             type => 'volatile', # we don't want to see this in serialized data
109             doc => 'list of (ITIP) validation errors',
110             domain => 'ref',
111             options => 'ARRAY',
112             value => [],
113             };
114              
115              
116             # FIXME: handle X-properties here.
117             # BUG: 411196
118              
119 44         163 my $self = new Class::MethodMapper;
120 44         408 bless $self, $classname;
121 44         357 $self->set_map (%$map);
122 44         2286 $self->set (%args);
123              
124 44         257 return $self;
125             }
126              
127              
128             =head2 new_from_ical($icaldata)
129            
130             Creates a new Net::ICal::Component from a string containing iCalendar
131             data. Use this to read in a new object before you do things with it.
132              
133             Returns a Net::ICal::Component object on success, or undef on failure.
134              
135             =cut
136              
137             sub new_from_ical {
138 17     17 1 20091 my ($class, $ical) = @_;
139              
140             # put the string into something for the callback function below to use
141 17         279 my @lines = split (/\015?\012/, $ical); # portability
142              
143             #FIXME: this should return undef if the ical is invalid
144             #BUG: 424109
145 17         67 return _parse_lines (\@lines);
146             }
147              
148             =pod
149              
150             =head1 METHODS
151              
152             =head2 type ([$string])
153              
154             Get or set the type of this component. You aren't supposed to ever
155             set this directly. To create a component of a specific type, use
156             the new method of the corresponding class.
157              
158              
159             =head2 validate
160              
161             Returns 1 if the component is valid according to RFC 2445. If it isn't
162             undef is returned, and $@ contains a listref of errors
163              
164             =cut
165              
166             sub validate {
167 44     44 1 61 my ($self) = @_;
168              
169 44 100       52 if (@{$self->errlog}) {
  44         185  
170 7         216 $@ = $self->errlog;
171 7         193 return undef;
172             } else {
173 37         953 return 1;
174             }
175             }
176              
177             =head2 as_ical
178              
179             Returns an ICal string that represents this component
180              
181             =begin testing
182             TODO: {
183             local $TODO = 'write tests for as_ical';
184             ok(0, "need tests here");
185             };
186             =end testing
187             =cut
188              
189             sub as_ical {
190 0     0 1 0 my ($self) = @_;
191              
192             # make the BEGIN: VALARM line, or whatever
193 0         0 my $ical = "BEGIN:" . $self->type . "\015\012";
194              
195             # this is a callback that Class::MethodMapper will use
196             # to generate the ical text.
197             my $cb = sub {
198 0     0   0 my ($self, $key, $value) = @_;
199 0         0 my $line;
200              
201 0         0 $key =~ s/_/-/g;
202 0         0 $key = uc ($key);
203              
204 0 0       0 return unless $value->{value};
205              
206             # if this object is just a reference to something, look at that object.
207 0 0       0 if (not defined $value->{domain}) {
    0          
    0          
208 0         0 $line .= $key . ":" . $value->{value} . "\015\012";
209             } elsif ($value->{domain} eq 'ref') {
210 0 0       0 if ($value->{options} eq 'ARRAY') {
    0          
211             # for every line in this array, if it's a ref, call the
212             # referenced object's as_ical method; otherwise output a
213             # key:value pair.
214 0         0 foreach my $val (@{$value->{value}}) {
  0         0  
215 0 0       0 if (ref ($val)) {
216 0 0       0 if (UNIVERSAL::isa ($val, 'Net::ICal::Property')) {
217 0         0 $line .= $key . $val->as_ical . "\015\012";
218             } else {
219 0         0 $line .= $val->as_ical();
220             }
221             } else {
222 0         0 $line .= $key . ":$val\015\012";
223             }
224             }
225             } elsif ($value->{options} eq 'HASH') {
226             } else {
227             # assume it's a class, and call its as_ical method
228 0         0 $line .= $key . $value->{value}->as_ical . "\015\012";
229             }
230              
231             # if this is a thing without its own subclass, it's a hashref.
232             # output the key value (DESCRIPTION, for example) and then
233             # the hash's keys and values like ";key=value".
234              
235             } elsif ($value->{domain} eq 'param') {
236 0         0 my $xhash = $value->{value};
237 0         0 $line = $key;
238            
239             # the 'content' key is the name of this property.
240 0         0 foreach my $xkey (keys %$xhash) {
241 0 0       0 next if ($xkey eq 'content');
242 0         0 $line .= ';' . uc ($xkey) . "=" . $xhash->{$xkey};
243             }
244 0         0 $line .= ":" . $xhash->{content} . "\015\012";
245              
246             # otherwise just output a key-value pair.
247             } else {
248 0         0 $line .= $key . ":" . $value->{value} . "\015\012";
249             }
250 0         0 $ical .= $line;
251 0         0 };
252            
253             # call the Class::MethodMapper callback.
254 0         0 $self->save ('parameter', $cb);
255              
256             # OUTPUT END:VALARM or whatever.
257 0         0 $ical .= "END:" . $self->type . "\015\012";
258 0         0 return $ical;
259             }
260              
261             =head2 has_one_of (@propertynames)
262              
263             returns a true value if one of the listed property names is present
264             on the component and undef if not
265              
266             =for testing
267             ok($comp->has_one_of ('action', 'attendee'), "we have action, so pass");
268             ok(not($comp->has_one_of ('summary', 'attendee')), "we have neither summary nor attendee so fail");
269              
270             =cut
271              
272             sub has_one_of {
273 0     0 1 0 my ($self, @props) = @_;
274              
275 0         0 foreach my $prop (@props) {
276 0 0       0 return 1 if defined ($self->get ($prop));
277             }
278 0         0 return undef;
279             }
280              
281             =head2 has_required_property (name, [value])
282              
283             checks whether the component has a value for property 'name' and
284             optionally checks whether it is value 'value'
285              
286             =for testing
287             ok($comp->has_required_property ('action'), "we have action, so pass");
288             ok(not($comp->has_required_property ('summary')), "we don't have summary so fail");
289             ok($comp->has_required_property ('action','DISPLAY'), "action contains 'DISPLAY', so pass");
290             ok(not($comp->has_required_property ('action','nonsense')), "action doesn't contain 'nonsense', so fail");
291              
292             =cut
293              
294             sub has_required_property {
295 0     0 1 0 my ($self, $property, $value) = @_;
296              
297 0 0       0 do {
298 0         0 $@ = $self->type . " needs a " .
299             $property . " property for this method";
300 0         0 return undef;
301             } unless (defined $self->get ($property));
302              
303 0 0       0 if (defined $value) {
304 0 0       0 do {
305 0         0 $@ = "$property needs to be set to $value for this method";
306 0         0 return undef;
307             } unless ($self->get ($property) eq $value);
308             }
309              
310 0         0 return 1;
311             }
312              
313             =head2 has_illegal_property (name)
314              
315             checks whether the component has a value for property 'name' and
316             returns a true value if it has, and undef if it doesn't
317              
318             =for testing
319             ok($comp->has_illegal_property ('action'), "we have action, so fail");
320             ok(not($comp->has_illegal_property ('attendee')), "we don't have attendee so pass");
321              
322             =cut
323              
324             sub has_illegal_property {
325 0     0 1 0 my ($self, $property) = @_;
326              
327 0 0       0 do {
328 0         0 $@ = "$property not allowed for this method";
329 0         0 return 1;
330             } if defined ($self->get ($property));
331            
332 0         0 return undef;
333             }
334              
335             =head2 has_only_one_of (name1, name2)
336              
337             returns undef if both the properties name1 and name2 are present. Otherwise,
338             it returns a true value. On error, it sets $@.
339              
340             =for testing
341             ok($comp->has_only_one_of ('action', 'summary'), "we have action, and not summary, so pass");
342             ok(not($comp->has_only_one_of ('action', 'trigger')), "we have both action and trigger, so fail");
343             ok($comp->as_only_one_of ('foo', 'bar'), "we have neither, so pass");
344              
345             =cut
346              
347             sub has_only_one_of {
348 0     0 1 0 my ($self, $prop1, $prop2) = @_;
349              
350 0         0 my $val1 = $self->get ($prop1);
351 0         0 my $val2 = $self->get ($prop2);
352 0 0 0     0 do {
353 0         0 $@ = "Properties $prop1 and $prop2 are mutually exclusive for this method";
354 0         0 return undef;
355             } if (defined ($val1) and defined ($val2));
356              
357             #return (defined ($val1) or defined ($val2));
358 0         0 return 1;
359             }
360              
361             =pod
362              
363             =head1 INTERNAL METHODS
364              
365             These are for internal use only, and are included here for the benefit
366             of Net::ICal developers.
367              
368              
369             =head2 _identify_component($line)
370              
371             the first line of the iCal will look like BEGIN:VALARM or something.
372             we need to know what comes after the V, because that's what
373             sort of component we'll be creating.
374              
375             Returns ALARM, EVENT, TODO, JOURNAL, FREEBUSY, etc, or undef for
376             failure.
377              
378             =for testing
379             ok(&Net::ICal::Component::_identify_component("BEGIN:VTODO") eq "TODO", "Identify TODO component");
380             ok(Net::ICal::Component::_identify_component("BeGiN:vToDo") eq "TODO", "Identify mixed case component");
381             ok(not(Net::ICal::Component::_identify_component("BEGIN:xyzzy")), "can't identify nonsense component");
382             ok(not(Net::ICal::Component::_identify_component("")), "can't identify component in empty string");
383             ok(not(Net::ICal::Component::_identify_component()), "can't identify component in undef");
384             ok(not(Net::ICal::Component::_identify_component(123)), "can't identify component in number");
385             =cut
386              
387             sub _identify_component {
388 71     71   94 my ($line) = @_;
389              
390 71         395 my ($bogus, $comp) = $line =~ /^BEGIN:(V)?(\w+)$/gi;
391              
392 71   50     296 return uc($comp) || undef;
393             }
394              
395             =pod
396              
397             =head2 _create_component($comp)
398              
399             $comp is "ALARM" or something. We generate the name of a type of object
400             we want to create, and call the _create method on that object.
401              
402             =for testing
403             ok(Net::ICal::Event::_create_component("TODO"), "Create TODO component");
404             ok(not(Net::ICal::Event::_create_component("xyzzy")), "Can't create nonsense component");
405             ok(not(Net::ICal::Event::_create_component("")), "Can't create component from empty string");
406             ok(not(Net::ICal::Event::_create_component()), "Can't create component from undef");
407              
408             =cut
409              
410             sub _create_component {
411 44     44   58 my ($comp) = @_;
412              
413 44         94 $comp = "Net::ICal::" . ucfirst (lc ($comp));
414 44         2492 eval "require $comp";
415 44 50       163 if ($@) {
416 0         0 $@ = "Unknown component $comp";
417 0         0 return undef;
418             }
419              
420 44         226 return $comp->_create;
421             }
422              
423              
424             =pod
425              
426             =head2 _unfold(@lines)
427              
428             Handle multiline fields; see "unfolding" in RFC 2445. Make all the
429             multiple fields we've been handed into single-line fields.
430              
431             =for testing
432             my $unfoldlines = [];
433             ok(Net::ICal::Event::_unfold($unfoldlines), "Unfold valid iCal lines");
434             ok(not(Net::ICal::Event::_unfold("x\ny\nz\n")), "Can't unfold invalid iCal lines");
435              
436             =cut
437              
438             sub _unfold {
439 141     141   181 my ($lines) = @_;
440              
441 141         192 my $line = shift @$lines;
442 141   66     758 while (@$lines and $lines->[0] =~ /^ /) {
443 0         0 chomp $line;
444 0         0 $line .= substr (shift @$lines, 1);
445             }
446 141         294 return $line;
447             }
448              
449             =pod
450              
451             =head2 _fold($line)
452              
453             =cut
454              
455             sub _fold {
456 0     0   0 my ($line) = @_;
457 0         0 my $folded;
458              
459 0         0 while (length $line > 76) {
460             # don't break lines in the middle of words
461 0         0 $line =~ s/(.{1,76}\W)//;
462             # when we wrap a line, use this as a newline
463 0         0 $folded .= $1 . "\015\012 ";
464             }
465 0         0 return $folded;
466             }
467              
468             =pod
469              
470             =head2 _parse_lines(\@lines)
471              
472             Parse and validate the lines of iCalendar data we got to make sure it
473             looks iCal-like.
474              
475             =cut
476              
477             sub _parse_lines {
478 44     44   62 my ($lines) = @_;
479              
480 44         96 my $comp = _identify_component(shift @$lines);
481 44 50       98 unless ($comp) {
482 0         0 warn "Not a valid ical stream\n";
483 0         0 return undef;
484             }
485              
486 44         86 my $self = _create_component($comp);
487 44 50       119 unless ($self) {
488 0         0 while (shift @$lines) {
489 0 0       0 last if /^END/;
490             }
491 0         0 return undef;
492             }
493              
494             # give a callback for Class::MethodMaker to call when it
495             # restores the data from @lines.
496              
497             my $cb = sub {
498 141 50   141   3561 return undef unless @$lines;
499              
500 141         266 my $line = _unfold($lines);
501              
502 141 100       537 if ($line =~ /^BEGIN:/) {
    100          
503 27         53 unshift (@$lines, $line);
504 27         58 my $foo = _parse_lines ($lines);
505              
506             # Calendar.pm has alarms/todos/etc methods, so add the s
507 27         7989 my $name = lc (_identify_component ($line)) . 's';
508              
509             # see if there's already an existing list
510 27   66     98 my $ref = $self->get ($name) || ();
511              
512             # move to parse errors from child components to our log
513 27 100       467 if ($foo) {
514 20         23 push (@{$self->errlog}, @{$foo->errlog});
  20         103  
  20         506  
515 20         449 push (@$ref, $foo);
516             } else {
517 7 50       17 if (ref ($@)) {
518 7         96 push (@{$self->errlog}, @{$@});
  7         38  
  7         178  
519             } else {
520 0         0 add_validation_error ($self, $@);
521             }
522             }
523 27         103 return ($name, $ref);
524              
525             } elsif ($line =~ /^END:(\w+)$/) {
526 44         128 return undef;
527             } else {
528             # parse out the iCalendar lines.
529 70         134 my ($key, $value) = _parse_property($line);
530 70         149 my ($class, $paramstr) = _parse_parameter($key);
531              
532 70         127 $class = lc ($class);
533             # make sure we have a valid function name
534 70         116 $class =~ s/-/_/g;
535              
536             # FIXME: handle X-properties here.
537             # BUG: 411196
538              
539 70 100       296 if (not defined $self->get_meta ('type', $class)) {
540 2         24 add_validation_error ($self, "There is no $class method");
541 2         49 return ('type', $self->get_meta ('value', 'type'));
542             }
543             # avoid warnings for doing eq with undef below
544             # no domain means simple string/integer, so only
545             # one of them is allowed
546 68 100       872 if (not defined $self->get_meta ('domain', $class)) {
    50          
    0          
547 30         322 my $old = $self->get_meta ('value', lc($key));
548 30 50       258 if ($old) {
549 0         0 add_validation_error ($self, "Only one $key allowed; skipping");
550 0         0 return ($class, $old);
551             }
552 30         114 return ($class, $value);
553             # we either have an array of values, or a class for the
554             # property
555             } elsif ($self->get_meta ('domain', $class) eq 'ref') {
556            
557             # set up the array to refer to. It may be an array of objects
558             # or just an array of values; _load_property will do either.
559 38 100       760 if ($self->get_meta ('options', $class) eq 'ARRAY') {
560             # the array elements can be refs too
561 6         59 my $prop = _load_property ($class, $value, $line);
562 6 50       17 unless (defined $prop) {
563 0         0 add_validation_error ($self, "Error loading property $key");
564             }
565 6         22 my $val = $self->get_meta ('value', $class);
566 6 100       59 if (defined $val) {
567 3         5 push (@$val, $prop);
568 3         12 return ($class, $val);
569             } else {
570 3         16 return ($class, [$prop]);
571             }
572             } else {
573             # if this thing we're looking at needs to be made a
574             # Net::ICal::subclass object, load that module and call that
575             # subclass's new_from_ical method on this line of ical text.
576 32         331 my $prop = _load_property ($self->get_meta ('options', $class),
577             $value, $line);
578 32 50       3868 unless (defined $prop) {
579 0         0 add_validation_error ($self, "Error loading property $key");
580             }
581 32         135 return ($class, $prop);
582             }
583              
584             # if there are parameters for this thing, but not an actual subclass,
585             # build a hash and return a reference to it. See, for example,
586             # DESCRIPTION fields, which can have an ALTREP (like a URL) or a
587             # LANGUAGE. We don't need a separate class for it; a hash will suffice.
588              
589             } elsif ($self->get_meta ('domain', $class) eq 'param') {
590 0 0       0 my @params = $paramstr ? split (/;/, $paramstr) : ();
591 0         0 my %foo = (content => $value);
592              
593 0         0 foreach my $keyvalue (@params) {
594 0         0 my ($pkey, $pvalue) = split (/=/, $keyvalue);
595 0         0 $foo{$pkey} = $pvalue;
596             }
597 0         0 return ($class, \%foo);
598             }
599             }
600 44         289 };
601 44         180 $self->restore($cb);
602              
603 44         175 my $warnings;
604 44 100       49 if (@{$self->errlog}) {
  44         269  
605             # save parse errors
606 8         222 $warnings = $self->errlog;
607             # empty the errlog, since parse errors don't have to be fatal
608 8         208 $self->errlog ([]);
609             }
610              
611 44 100       1325 if ($self->validate) {
612             # if we passed, put back the parse errors, which apparently
613             # really were non-fatal
614 37 100       103 $self->errlog ($warnings) if (defined $warnings);
615 37         1019 return $self;
616             } else {
617             # oops, we didn't validate. Might be because of those parse
618             # errors. put those at the start.
619 7 50       20 unshift (@{$@}, @$warnings) if (defined $warnings);
  0         0  
620 7         165 return undef;
621             }
622             }
623              
624             =pod
625              
626             =head2 _parse_property($property)
627              
628             Given a property line from an iCalendar file, parses it and returns the
629             name and the value of that property.
630              
631             =for testing
632             ok(0, "need tests here");
633              
634             =cut
635              
636              
637             #FIXME: these will break if there's a : in a parameter value. We're also
638             # not handling FOO:value1,value2 properly.
639             #BUG: 233739
640             sub _parse_property {
641 70     70   95 my ($prop) = @_;
642              
643 70         368 my ($name, $value) = $prop =~ /^(.*?):(.*)$/g;
644              
645 70         183 return ($name, $value);
646             }
647              
648             =pod
649              
650             =head2 _parse_parameter($propname)
651              
652             Given a property name/key section, parses it and returns the param name and
653             the parameter string.
654              
655             =for testing
656             ok(0, "need tests here");
657              
658             =cut
659              
660             sub _parse_parameter {
661 70     70   103 my ($propname) = @_;
662              
663 70         386 my ($paramname, $paramstr) = $propname =~ /^(.*?)(?:;(.*)|$)/g;
664 70         182 return ($paramname, $paramstr);
665             }
666              
667             =pod
668              
669             =head2 _load_property($class, $value, $line)
670              
671             If a new ICal subclass object needs to be created, load the module
672             and return a new instance of it. Otherwise, just return the value
673             of the property.
674              
675             =for testing
676             ok(0, "need tests here");
677              
678             =cut
679              
680             sub _load_property {
681 38     38   347 my ($class, $value, $line) = @_;
682              
683             #FIXME: How do we want to handle this? Do we really want
684             # separate packages for Rrule and Exrule, and subclass them?
685 38         182 $class =~ s/\b(?:rrule|exrule)$/recurrence/i;
686 38 100       118 unless ($class =~ /::/) {
687 6         16 $class = "Net::ICal::" . ucfirst (lc ($class));
688             }
689 38         46 my $prop;
690 38         2001 eval "require $class";
691 38 100       146 unless ($@) {
692 32 50       209 if ($class->can ('new_from_ical')) {
693 0         0 return $class->new_from_ical($line);
694             } else {
695             # for things like Time, which are just a value, not a Property,
696             # so they don't have new_from_ical
697 32         133 return $class->new (ical => $value);
698             }
699             } else {
700 6         16 return $value;
701             }
702             }
703              
704             1;
705              
706             =head1 SEE ALSO
707              
708             =head2 Net::ICal
709              
710             More documentation pointers can be found in L.
711              
712             =head2 Class::MethodMapper
713              
714             Most of the internals of this code are built on C::MM. You need to
715             understand what it does first.
716              
717             =cut