File Coverage

blib/lib/Net/vCard.pm
Criterion Covered Total %
statement 52 86 60.4
branch 6 28 21.4
condition 0 3 0.0
subroutine 10 17 58.8
pod 8 12 66.6
total 76 146 52.0


line stmt bran cond sub pod time code
1             package Net::vCard;
2              
3 5     5   133466 use strict;
  5         11  
  5         197  
4 5     5   27 use warnings;
  5         9  
  5         311  
5              
6             our $VERSION=0.1;
7             our $WARN=0;
8              
9             =head1 NAME
10              
11             Net::vCard - Read and write vCard files (RFC 2426). vCard files hold personal information that you would typically find on a business card. Name, numbers, addresses, and even logos. This module can also serve as a base class for other vFile readers.
12              
13             =head1 SYNOPSIS
14              
15             use Net::vCard;
16              
17             my $cards=Net::vCard->loadFile( "addresses.vcf" );
18              
19             foreach my $card ( @$cards ) {
20              
21             print $card->givenName, " ", $card->familyName, "\n";
22             print $card->ADR->address, "\n";
23             print $card->ADR->city, " ", $card->ADR->region, "\n";
24             print $card->ADR->postalCode, "\n";
25              
26             print $card->ADR("home")->address, "\n";
27             print $card->ADR("home")->city, " ", $card->ADR("home")->region, "\n";
28             print $card->ADR("home")->postalCode, "\n";
29              
30             }
31              
32             =head1 MODULE STATUS
33              
34             The current state of this module is a pretty solid parser and internal data structure.
35              
36             Now I will be adding get/set handlers for the various properties. As well, I'd really like
37             to get some pathelogical data from different vCard producers. Right now I have a pretty good
38             handle on Apple's Addressbook - which is the whole reason why I wrote this stuff.
39              
40             For those who really want to use this module right away
41              
42             - go ahead and access the hash values directly for the time being
43             - keep in mind that I will be making a get/set method interface
44             - once that is established you will need to use that interface instead
45              
46             =cut
47              
48              
49 5     5   25 use base qw(Net::vFile);
  5         15  
  5         3278  
50 5     5   9981 use Net::vCard::ADR;
  5         13  
  5         3393  
51              
52             $Net::vFile::classMap{'VCARD'}=__PACKAGE__;
53              
54             =head1 ACCESSOR METHODS
55              
56             =head2 NAME values
57              
58             =over 4
59              
60             =item $vcard->familyName( [ familyName ] )
61              
62             =cut
63              
64             sub familyName {
65 0 0   0 1 0 if (exists $_[1]) {
66 0         0 $_[0]->{'N'}{'familyName'}=$_[1];
67             }
68 0         0 return $_[0]->{'N'}{'familyName'};
69             };
70              
71             =item $vcard->givenName( [ givenName ] )
72              
73             =cut
74              
75             sub givenName {
76 0 0   0 1 0 if (exists $_[1]) {
77 0         0 $_[0]->{'N'}{'givenName'}=$_[1];
78             }
79 0         0 return $_[0]->{'N'}{'givenName'};
80             };
81              
82             =item $vcard->additionalNames( [ additionalNames ] )
83              
84             =cut
85              
86             sub additionalNames {
87 0 0   0 1 0 if (exists $_[1]) {
88 0         0 $_[0]->{'N'}{'additionalNames'}=$_[1];
89             }
90 0         0 return $_[0]->{'N'}{'additionalNames'};
91             };
92              
93             =item $vcard->suffixes( [ suffixes ] )
94              
95             =cut
96              
97             sub suffixes {
98 0 0   0 1 0 if (exists $_[1]) {
99 0         0 $_[0]->{'N'}{'suffixes'}=$_[1];
100             }
101 0         0 return $_[0]->{'N'}{'suffixes'};
102             };
103              
104             =item $vcard->prefixes( [ prefixes ] )
105              
106             =cut
107              
108             sub prefixes {
109 0 0   0 1 0 if (exists $_[1]) {
110 0         0 $_[0]->{'N'}{'prefixes'}=$_[1];
111             }
112 0         0 return $_[0]->{'N'}{'prefixes'};
113             };
114              
115             =back
116              
117             =head2 ADDRESSES
118              
119             To access address data:
120              
121             $card->ADR( type )->field;
122             $card->ADR( )->city; # Default address, city field
123             $card->ADR( "home" )->address; # Home address type, address field
124              
125             =over 4
126              
127             =item $card->ADR( [type] )->country
128              
129             =item $card->ADR( [type] )->poBox
130              
131             =item $card->ADR( [type] )->city
132              
133             =item $card->ADR( [type] )->region
134              
135             =item $card->ADR( [type] )->address
136              
137             =item $card->ADR( [type] )->postalCode
138              
139             =item $card->ADR( [type] )->extendedAddress
140              
141             =back
142              
143              
144             There are some decisions to be taken wrt ADR values.
145              
146             Firstly
147              
148             As of now the RFC specifies
149             action to take in the case of unlisted type - the address gets four types - intl,
150             parcel, postal, and work. This implies that several types refer to the same address.
151              
152             What I am doing for loading this data is storing the address in a hash entry by
153             the first name and listing the remainder in "_alias" hash key.
154              
155             What happens when one of these addresses is updated? Do we copy all the values to
156             unique hash entries or do we update the common copy, requiring the developer to
157             explicitly declare a new address replace the common entry.
158              
159             If this doesn't make sense email me and I'll try another explaination.
160              
161             Secondly
162              
163             What about preferred addresses? For now I am going to let the module user optionally
164             request their preferred address type. If it does not exist then we'll keep looking
165             for less preferred address types like the "pref" that was specified when loading vcard
166             data, and finally the 4 default types.
167              
168             =cut
169              
170             sub ADR {
171              
172 0     0 1 0 my $self=shift;
173 0   0     0 my $reqType=shift || $self->{'ADR'}{'_pref'};
174              
175 0         0 foreach my $type ( $reqType, @{$self->typeDefault->{'ADR'}} ) {
  0         0  
176 0 0       0 next unless $type;
177 0 0       0 if (exists $self->{'ADR'}{$type}) {
178 0         0 return $self->{'ADR'}{$type};
179             }
180              
181 0 0       0 if (exists $self->{'ADR'}{'_alias'}{$type}) {
182 0         0 return $self->{'ADR'}{'_alias'}{$type};
183             }
184             }
185              
186 0 0       0 warn "No address found\n" if $WARN;
187 0         0 my $adrPkg=ref($self) . "::ADR";
188 0         0 return $adrPkg->new;
189              
190             }
191              
192 1     1 0 1097 sub FN { $_[0]->_singleText( "FN", $_[1] ); }
193 0     0 0 0 sub BDAY { $_[0]->_singleText( "BDAY", $_[1] ); }
194              
195             sub varHandler {
196              
197             return {
198 10     10 1 252 'FN' => 'singleText',
199             'N' => 'N',
200             'NICKNAME' => 'multipleText',
201             'PHOTO' => 'singleBinary',
202             'BDAY' => 'singleText',
203             'ADR' => 'ADR',
204             'LABEL' => 'singleTextTyped',
205             'TEL' => 'singleTextTyped',
206             'EMAIL' => 'singleTextTyped',
207             'MAILER' => 'singleText',
208             'TZ' => 'singleText',
209             'GEO' => 'GEO',
210             'TITLE' => 'singleText',
211             'ROLE' => 'singleText',
212             'LOGO' => 'singleBinary',
213             'AGENT' => 'singleText',
214             'ORG' => 'multipleText',
215             'CATEGORIES' => 'multipleText',
216             'NOTE' => 'singleText',
217             'PRODID' => 'singleText',
218             'REV' => 'singleText',
219             'SORT-STRING' => 'singleText',
220             'SOUND' => 'singleBinary',
221             'UID' => 'singleText',
222             'URL' => 'singleText',
223             'VERSION' => 'singleText',
224             'CLASS' => 'singleText',
225             'KEY' => 'singleBinary',
226             };
227              
228             }
229              
230             sub typeDefault {
231              
232             return {
233 36     36 1 296 'ADR' => [ qw(intl postal parcel work) ],
234             'LABEL' => [ qw(intl postal parcel work) ],
235             'TEL' => [ qw(voice) ],
236             'EMAIL' => [ qw(internet) ],
237             };
238              
239             }
240              
241             sub load_N {
242              
243 5 50   5 0 19 die "load_N: @_ cannot have attributes\n" if $_[2];
244            
245 5     5   128 no warnings;
  5         11  
  5         3604  
246 5         71 my @parts = split /(?
247 5         16 map { s/\\;/;/g; } @parts;
  10         41  
248              
249 5         18 my @additional = split /(?
250 5         11 map { s/\\,/,/g; } @additional;
  0         0  
251              
252 5         17 my @prefixes = split /(?
253 5         10 map { s/\\,/,/g; } @prefixes;
  0         0  
254              
255 5         13 my @suffixes = split /(?
256 5         9 map { s/\\,/,/g; } @suffixes;
  0         0  
257              
258 5         66 $_[0]->{$_[1]} = {
259             familyName => $parts[0],
260             givenName => $parts[1],
261             additionalNames => \@additional,
262             suffixes => \@suffixes,
263             prefixes => \@prefixes,
264             };
265              
266             }
267              
268             sub load_ADR {
269              
270 8     8 0 16 my $attr=$_[2];
271              
272 8         18 my %type=();
273 8         12 map { map { $type{lc $_}=1 } split /,/, $_ } @{$attr->{TYPE}};
  13         43  
  13         49  
  8         24  
274 8         32 my $typeDefault=$_[0]->typeDefault;
275 8 50       40 map { $type{ lc $_ }=1 } @{$typeDefault->{$_[1]}} unless scalar(keys %type);
  0         0  
  0         0  
276              
277 8         100 my @parts = split /(?
278 8         18 map { s/\\;/;/g; s/\\n/\n/gs; } @parts;
  56         81  
  56         120  
279              
280 8         17 my $pref=0;
281 8 100       28 if ($type{pref}) {
282 5         12 delete $type{pref};
283 5         24 $pref=1;
284             }
285 8         32 my @types=sort keys %type;
286              
287             # What to do about comma separated things?
288              
289 8         15 my $actual=shift @types;
290 8         24 my $adrPkg = ref($_[0]) . "::ADR";
291              
292 8         143 $_[0]->{$_[1]}{$actual} = $adrPkg->new( {
293             poBox => $parts[0],
294             extendedAddress => $parts[1],
295             address => $parts[2],
296             city => $parts[3],
297             region => $parts[4],
298             postalCode => $parts[5],
299             country => $parts[6],
300             });
301              
302 8 100       42 $_[0]->{$_[1]}{_pref}=$actual if $pref;
303 8         31 delete $_[0]->{$_[1]}{_alias}{$actual};
304 8 0       50 map { $_[0]->{$_[1]}{_alias}{$_}=$actual unless exists $_[0]->{$_[1]}{$_} } @types;
  0            
305              
306             }
307              
308             =head1 SUPPORT
309              
310             For technical support please email to jlawrenc@cpan.org ...
311             for faster service please include "Net::vCard" and "help" in your subject line.
312              
313             =head1 AUTHOR
314              
315             Jay J. Lawrence - jlawrenc@cpan.org
316             Infonium Inc., Canada
317             http://www.infonium.ca/
318              
319             =head1 COPYRIGHT
320              
321             Copyright (c) 2003 Jay J. Lawrence, Infonium Inc. All rights reserved.
322             This program is free software; you can redistribute
323             it and/or modify it under the same terms as Perl itself.
324              
325             The full text of the license can be found in the
326             LICENSE file included with this module.
327              
328             =head1 ACKNOWLEDGEMENTS
329              
330             Net::iCal - whose loading code inspired me for mine
331              
332             =head1 SEE ALSO
333              
334             RFC 2426, Net::iCal
335              
336             =cut
337              
338             1;
339