File Coverage

blib/lib/Net/vFile.pm
Criterion Covered Total %
statement 113 143 79.0
branch 33 58 56.9
condition 8 14 57.1
subroutine 11 17 64.7
pod 11 12 91.6
total 176 244 72.1


line stmt bran cond sub pod time code
1             package Net::vFile;
2              
3 6     6   38 use strict;
  6         11  
  6         175  
4 5     5   24 use warnings;
  5         7  
  5         297  
5 5     5   5818 use Encode qw(decode is_utf8);
  5         73157  
  5         36182  
6              
7             # This stuff will be in Net::vCalendar
8             # use DateTime::Format::ICal;
9              
10             =head1 NAME
11              
12             Net::vFile - Generic module which can read and write "vFile" files such as vCard (RFC 2426) and vCalendar (RFC 2445).
13             The result of loading this data is a collection of objects which will grant you easy access to the properties. Then
14             the module can write your objects back to a data file.
15              
16             =head1 SYNOPIS
17              
18             use Net::vCard;
19              
20             my $cards = Net::vCard->loadFile( "foo.vCard", "blort.vCard", "whee.vCard" );
21              
22             foreach my $card (@$cards) {
23             spam ( $card->EMAIL('default') );
24             }
25              
26             =head1 DETAILS
27              
28             The way this processor works is that it reads the vFile line by line.
29              
30             1 - BEGIN:(.*) tag
31              
32             $1 is looked up in classMap; class is loaded; new object of this class is created
33             ie/ $Net::vFile::classMap{'VCARD'}="Net::vCard";
34             $object=$classMap{'VCARD'}->new;
35              
36             n.b. classMap is a package variable for Net::vFile
37              
38             2 - All lines are read and stored until a BEGIN tag (goto 1) or END tag (goto 3) is reached
39              
40             3 - END:(.*) tag
41              
42             Signals that all entry data has been obtained and now the rows of data are processed
43              
44             4 - Data is concatenated - thanks to Net::iCal for the strategy; the tag label and data are obtained
45              
46             5 - The data handler is identified via $object->varHandler->{$label}
47              
48             There are some generic handlers for common data types such as simple strings, dates, etc. More
49             elaborate data types such as N, ADR, etc. need special treatment and are declared explititly
50             in classes as "load_XXX" such as "load_N"
51              
52             You should be able to override and extend the processing by taking Net::vCard.pm as your example
53             and adjusting as necessary.
54              
55             The resulting data structure is a bit bulky - but is such that it can express vCard data completely and
56             reliably
57              
58             Put in a dump of a vCard here
59              
60             =head1 DEPENDENCIES
61              
62             DateTime::Format::ICal
63              
64             =over 4
65              
66             =item \@objects = loadFile( filename [, filename ... ] )
67              
68             Loads the vFiles and returns an array of objects
69              
70             =cut
71              
72             sub loadFile {
73              
74 4     4 1 75 my $self=shift;
75 4 50       46 $self=$self->new unless ref($self);
76              
77 4         15 foreach my $fn (@_) {
78 5         10 my $fh;
79 5 50       299 if (open $fh, $fn) {
80 5         140 until (eof $fh) {
81 5         40 $self->load($fh, $fn);
82             }
83             } else {
84 0         0 warn "Cannot open $fn\n";
85 0         0 next;
86             }
87 5         96 close $fh;
88             }
89              
90             # Usually people only load one type - VCARD
91             # - which will be represented by a single top level
92             # hash entry
93             # However it is not impossible to load VCARD, VCALENDAR and more all at once
94             # - in which case they get a scalar hashref
95            
96 4 50       24 if ( scalar values %$self == 0 ) {
97 0         0 return undef;
98             }
99              
100 4 50       20 if ( scalar values %$self == 1 ) {
101 4         27 return (values %$self)[0];
102             } else {
103 0         0 return $self;
104             }
105              
106             }
107              
108             # Classes inject their desired mappings
109             our %classMap=(
110              
111             # VCARD => "Net::vCard",
112              
113             # VCALENDAR => "Net::vCalendar",
114             # VALARM => "Net::vCalendar::vAlarm",
115             # VEVENT => "Net::vCalendar::vEvent",
116             # VTODO => "Net::vCalendar::vTodo",
117              
118             );
119              
120             =item $object = class->new
121              
122             Make a new object
123              
124             =cut
125              
126             sub new {
127              
128 9 50   9 1 39 my $class = ref($_[0]) ? ref(shift) : shift;
129              
130 9         29 my $self=bless {}, $class;
131              
132 9         65 return $self;
133              
134             }
135              
136             =item \@objects = Class->load( filehandle )
137              
138             Loads data from file handle and creates objects as necessary
139              
140             =cut
141              
142              
143             sub load {
144              
145 10     10 1 17 my ($class, $self);
146 10 50       35 if (ref ($_[0])) {
147 10         21 $self=shift;
148 10         20 $class=ref($self);
149             }
150             else {
151 0         0 $class=shift;
152 0         0 $self=$class->new;
153             }
154 10         14 my $fh =shift;
155 10         20 my $parent=shift;
156              
157 10 100       49 $self->{'_parent'}=$parent if ref $parent;
158              
159 10         23 my @lines=();
160 10         58 my $varHandler=$class->varHandler;
161              
162 10         16 my $decoder;
163 10         127 $_ = <$fh>;
164 10         52 $decoder=Encode::find_encoding("UTF-8");
165 10 100       1025 if ( /^\000/ ) {
166 4         10 $decoder=Encode::find_encoding("UTF-16BE");
167             }
168 10 50       99822 if ( /^[^\000]\000/ ) {
169 0         0 $decoder=Encode::find_encoding("UTF-16LE");
170             }
171              
172 10         23 my $thing="";
173 10         29 while ( $_ ) {
174              
175 81         414 my $line = $decoder->decode($_);
176 81         797 $line =~ s/[\r\n]+$//;
177              
178 81 100       240 if ($line =~ /^BEGIN:(.+)/) {
179 5         15 $thing=$1;
180 5   50     62 my $subclass= $classMap{uc $thing} || die "Don't know how to load ${thing}s\n";
181 5 50   4   521 eval "use $subclass"; die $@ if $@;
  5         23  
  4         31  
  4         9  
  4         59  
182 5         9 push @{$self->{$thing}}, $subclass->new->load($fh, $self);
  5         51  
183 5         19 next;
184             }
185              
186 76 100       408 last if $line =~ /^END:${thing}/;
187              
188 66         113 push @lines, $line;
189              
190 66         192 $_ = <$fh>;
191             }
192              
193 10         53 while ( @lines ) {
194              
195 66         108 my $line = shift @lines;
196 66   66     400 while ( @lines && $lines[0] =~ /^\s(.*)/ ) {
197 0         0 $line .= $1;
198 0         0 shift @lines;
199             }
200              
201             # Non-typed line data
202 66 100 66     467 if ( $line =~ /^([\w\-]+):(.*)/ && exists $varHandler->{$1} ) {
203 27         74 my $h="load_$varHandler->{$1}";
204 27         139 $self->$h($1, undef, $2);
205 27         73 next;
206             }
207              
208             # Typed line data
209 39 100 66     392 if ( $line =~ /^([\w\-]+);([^:]*):(.*)/ && exists $varHandler->{$1} ) {
210              
211 24         89 my $h="load_$varHandler->{$1}";
212 24         70 my ($var, $data)=($1, $3);
213              
214 24         41 my %attr=();
215 24         105 map { /([^=]+)=(.*)/; push @{$attr{uc $1}}, $2 } split (/(?
  39         133  
  39         50  
  39         197  
216              
217             # XXX I want to split up the attributes here
218 24         125 $self->$h($var, \%attr, $data);
219 24         87 next;
220             }
221              
222             # X-values
223 15 50       99 if ( $line =~ /^(X-[\w\-]+);?([^:]*):(.*)/ ) {
224              
225 15         40 my ($var, $data)=($1, $3);
226              
227 15         26 my %attr=();
228 15         79 map { /([^=]+)=(.*)/; push @{$attr{uc $1}}, $2 } split (/(?
  30         97  
  30         41  
  30         157  
229              
230 15         49 $self->load_singleTextTyped($var, \%attr, $data);
231 15         48 next;
232             }
233              
234 0         0 $self->error( $line );
235              
236             }
237              
238 10         143 return $self;
239              
240             }
241              
242             =item $object->error
243              
244             Called when a line cannot be successfully decoded
245              
246             =back
247              
248             =cut
249              
250 0     0 1 0 sub error { warn ref($_[0]) . " ERRORLINE: $_[1]\n"; }
251              
252             =head1 DATA HANDLERS
253              
254             =over 4
255              
256             =item varHandler
257              
258             Returns a hash ref mapping the item label to a handler name. Ie:
259              
260             {
261             'FN' => 'singleText',
262             'N' => 'N',
263             'NICKNAME' => 'multipleText',
264             'PHOTO' => 'singleBinary',
265             'BDAY' => 'singleText',
266             'ADR' => 'ADR',
267             };
268              
269             =cut
270            
271             sub varHandler {
272 0     0 1 0 return {};
273             }
274              
275             =item typeDefault
276              
277             Additional information where handlers require type info. Such as ADR - is this
278             a home, postal, or whatever? If not supplied the RFC specifies what types they should
279             default to.
280              
281             from vCard:
282              
283             {
284             'ADR' => [ qw(intl postal parcel work) ],
285             'LABEL' => [ qw(intl postal parcel work) ],
286             'TEL' => [ qw(voice) ],
287             'EMAIL' => [ qw(internet) ],
288             };
289              
290             =cut
291              
292             sub typeDefault {
293 0     0 1 0 return {};
294             }
295              
296             =item load_singleText
297              
298             Loads a single text item with no processing other than unescape text
299              
300             =cut
301              
302             sub load_singleText {
303              
304 22     22 1 53 my $val=$_[3];
305 22         58 $val=~s/\\([\n,])/$1/gs;
306             # $val=~s/\\n/\n/gs;
307 22         115 $_[0]->{$_[1]}{'val'}=$val;
308 22 100       92 $_[0]->{$_[1]}{'_attr'}=$_[2] if $_[2];
309              
310             }
311              
312             =item _singleText
313              
314             Accessor for single text items
315              
316             =cut
317              
318             sub _singleText {
319              
320 1 50   1   7 if ($_[2]) {
321 0         0 $_[0]->{$_[1]}{'val'}=$_[0];
322             }
323 1         7 return $_[0]->{$_[1]}{'val'};
324              
325             }
326              
327             =item load_singleDate
328              
329             Loads a date creating a DateTime::Format::ICal object. Thanks Dave!
330              
331             =cut
332              
333             sub load_singleDate {
334              
335 0     0 1 0 my $val=$_[3];
336 0         0 eval {
337 0         0 $_[0]->{$_[1]}{'val'}=DateTime::Format::ICal->parse_datetime( iso8601 => $val );
338 0 0       0 }; if ($@) {
339 0         0 warn "$val; $@\n";
340             }
341 0 0       0 $_[0]->{$_[1]}{'_attr'}=$_[2] if $_[2];
342              
343             }
344              
345             =item load_singleDuration
346              
347             Loads a data duration using DateTime::Format::ICal.
348              
349             =cut
350              
351             sub load_singleDuration {
352              
353 0     0 1 0 my $val=$_[3];
354              
355 0         0 eval {
356 0         0 $_[0]->{$_[1]}{'val'}=DateTime::Format::ICal->parse_duration( $val );
357 0 0       0 }; if ($@) {
358 0         0 warn "$val; $@\n";
359             }
360              
361 0 0       0 $_[0]->{$_[1]}{'_attr'}=$_[2] if $_[2];
362              
363             }
364              
365             =item load_multipleText
366              
367             This is text that is separated by commas. The text is then unescaped. An array
368             of items is created.
369              
370             =cut
371              
372             sub load_multipleText {
373              
374 3     3 1 21 my @vals=split /(?
375 3         8 map { s/\\,/,/ } @vals;
  3         13  
376              
377 3         17 $_[0]->{$_[1]}{'val'}=\@vals;
378 3 50       14 $_[0]->{$_[1]}{'_attr'}=$_[2] if $_[2];
379              
380             }
381              
382             =item load_singleTextType
383              
384             Load text that has a type attribute. Each text of different type attributes
385             will be handled independantly in as a hash entry. If no type attribute is supplied
386             then the typeDefaults types will be used. A line can have multiple types. In the
387             case where multiple types have the same value "_alias" indicators are created.
388             The preferred type is stored in "_pref"
389              
390             =cut
391              
392             sub load_singleTextTyped {
393            
394 28     28 0 94 my $typeDefault=$_[0]->typeDefault;
395              
396 28         53 my $attr=$_[2];
397              
398 28         48 my %type=();
399 28         35 map { map { $type{lc $_}=1 } split /,/, $_ } @{$attr->{TYPE}};
  53         156  
  53         200  
  28         82  
400 28 50       89 map { $type{ lc $_ }=1 } @{$typeDefault->{$_[1]}} unless scalar(keys %type);
  0         0  
  0         0  
401              
402 28         37 my $pref=0;
403 28 100       76 if ($type{pref}) {
404 25         51 delete $type{pref};
405 25         35 $pref=1;
406             }
407              
408 28         102 my @types=sort keys %type;
409 28 50       78 my $actual=@types ? shift @types : "default";
410              
411 28         137 $_[0]->{$_[1]}{$actual}=$_[3];
412              
413 28 100       107 $_[0]->{$_[1]}{_pref}=$actual if $pref;
414 28 50 33     124 delete $_[0]->{$_[1]}{_alias}{$actual} if exists $_[0]->{$_[1]}{_alias} && $_[0]->{$_[1]}{_alias}{$actual};
415 28 0       43 map { $_[0]->{$_[1]}{_alias}{$_}=$actual unless exists $_[0]->{$_[1]}{$_} } @types;
  0         0  
416              
417 28 50       247 $_[0]->{$_[1]}{'_attr'}{$actual}=$_[2] if $_[2];
418              
419             }
420              
421             =item load_singleBinary
422              
423             Not done as I don't have example data yet.
424              
425             =cut
426              
427             sub load_singleBinary {
428 0     0 1 0 die "_singleBinary not done\n";
429             }
430              
431              
432             # sub setDefault {
433             # $_[0]->{"_setDefault"}=$_[1] if exists $_[1];
434             # return $_[0]->{"_setDefault"} if exists $_[0]->{"_setDefault"};
435             # die ref($_[0]) . " does not have a default set to iterate\n";
436             # }
437              
438             =back
439              
440             =head1 SUPPORT
441              
442             For technical support please email to jlawrenc@cpan.org ...
443             for faster service please include "Net::vFile" and "help" in your subject line.
444              
445             =head1 AUTHOR
446              
447             Jay J. Lawrence - jlawrenc@cpan.org
448             Infonium Inc., Canada
449             http://www.infonium.ca/
450              
451             =head1 COPYRIGHT
452              
453             Copyright (c) 2003 Jay J. Lawrence, Infonium Inc. All rights reserved.
454             This program is free software; you can redistribute
455             it and/or modify it under the same terms as Perl itself.
456              
457             The full text of the license can be found in the
458             LICENSE file included with this module.
459              
460             =head1 ACKNOWLEDGEMENTS
461              
462             Net::iCal - whose loading code inspired me for mine
463              
464             =head1 SEE ALSO
465              
466             RFC 2426, Net::iCal
467              
468             =cut
469              
470             1;
471