File Coverage

lib/Text/vCard.pm
Criterion Covered Total %
statement 159 163 97.5
branch 63 68 92.6
condition 22 28 78.5
subroutine 21 22 95.4
pod 8 8 100.0
total 273 289 94.4


line stmt bran cond sub pod time code
1             package Text::vCard;
2             $Text::vCard::VERSION = '3.07';
3 15     15   36156 use 5.006;
  15         41  
  15         593  
4 15     15   70 use Carp;
  15         18  
  15         919  
5 15     15   111 use strict;
  15         19  
  15         437  
6 15     15   78 use warnings;
  15         19  
  15         616  
7 15     15   2135 use Text::vFile::asData 0.07;
  15         19666  
  15         146  
8 15     15   6509 use Text::vCard::Node;
  15         40  
  15         582  
9              
10             # See this module for your basic parser functions
11 15     15   106 use base qw(Text::vFile::asData);
  15         23  
  15         1679  
12 15     15   81 use vars qw (%lookup %node_aliases @simple);
  15         24  
  15         2423  
13              
14             # If the node's data does not break down use this
15             my @default_field = qw(value);
16              
17             # If it does use these
18             %lookup = (
19             'ADR' => [
20             'po_box', 'extended', 'street', 'city',
21             'region', 'post_code', 'country'
22             ],
23             'N' => [ 'family', 'given', 'middle', 'prefixes', 'suffixes' ],
24             'GEO' => [ 'lat', 'long' ],
25             'ORG' => [ 'name', 'unit' ],
26             );
27              
28             %node_aliases = (
29             'FULLNAME' => 'FN',
30             'BIRTHDAY' => 'BDAY',
31             'TIMEZONE' => 'TZ',
32             'PHONES' => 'TEL',
33             'ADDRESSES' => 'ADR',
34             'NAME' => 'N', # To be deprecated as clashes with RFC
35             'MONIKER' => 'N',
36             );
37              
38             # Generate all our simple methods
39             @simple
40             = qw(FN BDAY MAILER TZ TITLE ROLE NOTE PRODID REV SORT-STRING UID URL CLASS FULLNAME BIRTHDAY TIMEZONE NAME EMAIL NICKNAME PHOTO);
41              
42             # Now we want lowercase as well
43             map { push( @simple, lc($_) ) } @simple;
44              
45             # Generate the methods
46             {
47 15     15   85 no strict 'refs';
  15         21  
  15         562  
48 15     15   77 no warnings 'redefine';
  15         23  
  15         22115  
49              
50             # 'version' handled separately
51             # to prevent conflict with ExtUtils::MakeMaker
52             # and $VERSION
53             for my $node ( @simple, "version" ) {
54             *$node = sub {
55 117     117   573 my ( $self, $value ) = @_;
56              
57             # See if we have it already
58 117         187 my $nodes = $self->get($node);
59 117 100 100     281 if ( !defined $nodes && $value ) {
60              
61             # Add it as a node if not exists and there is a value
62 33         103 $self->add_node( { 'node_type' => $node, } );
63              
64             # Get it out again
65 33         66 $nodes = $self->get($node);
66             }
67              
68 117 100 100     378 if ( scalar($nodes) && $value ) {
69              
70             # Set it
71 34         163 $nodes->[0]->value($value);
72             }
73              
74 117 100       486 return $nodes->[0]->value() if scalar($nodes);
75 1         3 return undef;
76             }
77             }
78             }
79              
80             =head1 NAME
81              
82             Text::vCard - Edit and create vCards (RFC 2426)
83              
84             =head1 WARNING
85              
86             L and L are built on top of this module and provide
87             a more intuitive user interface. Please try those modules first.
88              
89             =head1 SYNOPSIS
90              
91             use Text::vCard;
92             my $cards
93             = Text::vCard->new( { 'asData_node' => $objects_node_from_asData, } );
94              
95             =head1 DESCRIPTION
96              
97             A vCard is an electronic business card.
98              
99             This package is for a single vCard (person / record / set of address
100             information). It provides an API to editing and creating vCards, or supplied
101             a specific piece of the Text::vFile::asData results it generates a vCard
102             with that content.
103              
104             You should really use L as this handles creating
105             vCards from an existing file for you.
106              
107             =head1 METHODS
108              
109             =head2 new()
110              
111             use Text::vCard;
112              
113             my $new_vcard = Text::vCard->new();
114              
115             my $existing_vcard
116             = Text::vCard->new( { 'asData_node' => $objects_node_from_asData, } );
117              
118             =cut
119              
120             sub new {
121 39     39 1 1264 my ( $proto, $conf ) = @_;
122 39   100     207 my $class = ref($proto) || $proto;
123 39         63 my $self = {};
124              
125 39         96 bless( $self, $class );
126              
127 38   100     261 $self->{encoding_out} = $conf->{encoding_out} || 'UTF-8';
128              
129 38         68 my %nodes;
130 38         81 $self->{nodes} = \%nodes;
131              
132 38 100       116 if ( defined $conf->{'asData_node'} ) {
133              
134             # Have a vcard data node being passed in
135 26         43 while ( my ( $node_type, $data ) = each %{ $conf->{'asData_node'} } )
  227         668  
136             {
137 201         169 my $group;
138 201 100       417 if ( $node_type =~ /\./ ) {
139              
140             # Version 3.0 supports group types, we do not
141             # so remove everything before '.'
142 14         59 ( $group, $node_type ) = $node_type =~ /(.+)\.(.*)/;
143             }
144              
145             # Deal with each type (ADR, FN, TEL etc)
146             $self->_add_node(
147 201         611 { 'node_type' => $node_type,
148             'data' => $data,
149             'group' => $group,
150             }
151             );
152             }
153             } # else we're creating a new vCard
154              
155 38         102 return $self;
156             }
157              
158             =head2 add_node()
159              
160             my $address = $vcard->add_node( { 'node_type' => 'ADR', } );
161              
162             This creates a new address (a L object) in the vCard
163             which you can then call the address methods on. See below for what options are available.
164              
165             The node_type parameter must conform to the vCard spec format (e.g. ADR not address)
166              
167             =cut
168              
169             sub add_node {
170 67     67 1 1669 my ( $self, $conf ) = @_;
171 67 100 100     523 croak 'Must supply a node_type'
172             unless defined $conf && defined $conf->{'node_type'};
173 65 100       106 unless ( defined $conf->{data} ) {
174 36         93 my %empty;
175 36         57 my @data = ( \%empty );
176 36         66 $conf->{'data'} = \@data;
177             }
178              
179 65         94 $self->_add_node($conf);
180             }
181              
182             =head2 get()
183              
184             The following method allows you to extract the contents from the vCard.
185              
186             # get all elements
187             $nodes = $vcard->get('tel');
188              
189             # Just get the home address
190             my $nodes = $vcard->get(
191             { 'node_type' => 'addresses',
192             'types' => 'home',
193             }
194             );
195              
196             # get all phone number that matches serveral types
197             my @types = qw(work home);
198             my $nodes = $vcard->get(
199             { 'node_type' => 'tel',
200             'types' => \@types,
201             }
202             );
203              
204              
205             Either an array or array ref is returned, containing
206             L objects. If there are no results of 'node_type'
207             undef is returned.
208              
209             Supplied with a scalar or an array ref the methods
210             return a list of nodes of a type, where relevant. If any
211             of the elements is the prefered element it will be
212             returned as the first element of the list.
213              
214             =cut
215              
216             sub get {
217 359     359 1 4586 my ( $self, $conf ) = @_;
218 359 100       691 carp "You did not supply an element type" unless defined $conf;
219 358 100       527 if ( ref($conf) eq 'HASH' ) {
220 12 100       46 return $self->get_of_type( $conf->{'node_type'}, $conf->{'types'} )
221             if defined $conf->{'types'};
222 6         30 return $self->get_of_type( $conf->{'node_type'} );
223             } else {
224 346         497 return $self->get_of_type($conf);
225             }
226             }
227              
228             =head2 get_simple_type()
229              
230             The following method is a convenience wrapper for accessing simple elements.
231              
232             $value = $vcard->get_simple_type( 'email', [ 'internet', 'work' ] );
233              
234             If multiple elements match, then only the first is returned. If the object
235             isn't found, or doesn't have a simple value, then undef is returned.
236            
237             The argument type may be ommitted, it can be a scalar, or it can be an
238             array reference if multiple types are selected.
239              
240             =cut
241              
242             sub get_simple_type {
243 1     1 1 363 my ( $self, $node_type, $types ) = @_;
244 1 50       5 carp "You did not supply an element type" unless defined $node_type;
245              
246 1         3 my %hash = ( 'node_type', $node_type );
247 1 50       4 $hash{'types'} = $types if defined $types;
248 1         8 my $node = $self->get( \%hash );
249 1 50 33     3 return undef unless $node && @{$node} > 0 && exists $node->[0]->{'value'};
  1   33     13  
250              
251 1         7 $node->[0]->{'value'};
252             }
253              
254             =head2 nodes
255              
256             my $addresses = $vcard->get( { 'node_type' => 'address' } );
257              
258             my $first_address = $addresses->[0];
259              
260             # get the value
261             print $first_address->street();
262              
263             # set the value
264             $first_address->street('Barney Rubble');
265              
266             # See if it is part of a group
267             if ( $first_address->group() ) {
268             print 'Group: ' . $first_address->group();
269             }
270              
271             According to the RFC the following 'simple' nodes should only have one
272             element, this is not enforced by this module, so for example you can
273             have multiple URL's if you wish.
274              
275             =head2 simple nodes
276              
277             For simple nodes, you can also access the first node in the following way:
278              
279             my $fn = $vcard->fullname();
280             # or setting
281             $vcard->fullname('new name');
282              
283             The node will be automatically created if it does not exist and you
284             supplied a value. undef is returned if the node does not
285             exist. Simple nodes can be called as all upper or all lowercase method
286             names.
287              
288             vCard Spec: 'simple' Alias
289             -------------------- --------
290             FN fullname
291             BDAY birthday
292             MAILER
293             TZ timezone
294             TITLE
295             ROLE
296             NOTE
297             PRODID
298             REV
299             SORT-STRING
300             UID
301             URL
302             CLASS
303             EMAIL
304             NICKNAME
305             PHOTO
306             version (lowercase only)
307            
308             =head2 more complex vCard nodes
309              
310             vCard Spec Alias Methods on object
311             ---------- ---------- -----------------
312             N name (depreciated as conflicts with rfc, use moniker)
313             N moniker 'family','given','middle','prefixes','suffixes'
314             ADR addresses 'po_box','extended','street','city','region','post_code','country'
315             GEO 'lat','long'
316             TEL phones
317             LABELS
318             ORG 'name','unit' (unit is a special case and will return an array reference)
319              
320             my $addresses = $vcard->get( { 'node_type' => 'addresses' } );
321             foreach my $address ( @{$addresses} ) {
322             print $address->street();
323             }
324              
325             # Setting values on an address element
326             $addresses->[0]->street('The burrows');
327             $addresses->[0]->region('Wimbeldon common');
328              
329             # Checking an address is a specific type
330             $addresses->[0]->is_type('fax');
331             $addresses->[0]->add_types('home');
332             $addresses->[0]->remove_types('work');
333              
334             =head2 get_group()
335              
336             my $group_name = 'item1';
337             my $node_type = 'X-ABLABEL';
338             my $of_group = $vcard->get_group( $group_name, $node_type );
339             foreach my $label ( @{$of_group} ) {
340             print $label->value();
341             }
342              
343             This method takes one or two arguments. The group name
344             (accessable on any node object by using $node->group() - not
345             all nodes will have a group, indeed most vcards do not seem
346             to use it) and optionally the types of node you with to
347             have returned.
348              
349             Either an array or array reference is returned depending
350             on the calling context, if there are no matches it will
351             be empty.
352              
353             =cut
354              
355             sub get_group {
356 4     4 1 926 my ( $self, $group_name, $node_type ) = @_;
357 4         4 my @to_return;
358              
359 4 100 66     150 carp "No group name supplied"
360             unless defined $group_name
361             and $group_name ne '';
362              
363 3         5 $group_name = lc($group_name);
364              
365 3 100 66     10 if ( defined $node_type && $node_type ne '' ) {
366              
367             # After a specific node type
368 1         2 my $nodes = $self->get($node_type);
369 1         2 foreach my $node ( @{$nodes} ) {
  1         4  
370 2 100       4 push( @to_return, $node ) if $node->group() eq $group_name;
371             }
372             } else {
373              
374             # We want everything from that group
375 2         2 foreach my $node_loop ( keys %{ $self->{nodes} } ) {
  2         8  
376              
377             # Loop through each type
378 12         13 my $nodes = $self->get($node_loop);
379 12         9 foreach my $node ( @{$nodes} ) {
  12         14  
380 18 100       23 if ( $node->group() ) {
381 8 100       9 push( @to_return, $node )
382             if $node->group() eq $group_name;
383             }
384             }
385             }
386             }
387 3 100       10 return wantarray ? @to_return : \@to_return;
388             }
389              
390             =head1 BINARY METHODS
391              
392             These methods allow access to what are potentially binary values such
393             as a photo or sound file. Binary values will be correctly encoded and
394             decoded to/from base 64.
395              
396             API still to be finalised.
397              
398             =head2 photo()
399              
400             =head2 sound()
401              
402             =head2 key()
403              
404             =head2 logo()
405              
406             =cut
407              
408 0     0   0 sub DESTROY {
409             }
410              
411             =head2 get_lookup
412              
413             This method is used internally to lookup those nodes which have
414             multiple elements, e.g. GEO has lat and long, N (name) has family,
415             given, middle etc.
416              
417             If you wish to extend this package (for custom attributes), overload
418             this method in your code:
419              
420             sub my_lookup {
421             return \%my_lookup;
422             }
423             *Text::vCard::get_lookup = \&my_lookup;
424              
425             This has not been tested yet.
426              
427             =cut
428              
429             sub get_lookup {
430 266     266 1 268 my $self = shift;
431 266         380 return \%lookup;
432             }
433              
434             =head2 get_of_type()
435              
436             my $list = $vcard->get_of_type( $node_type, \@types );
437              
438             It is probably easier just to use the get() method, which inturn calls
439             this method.
440              
441             =cut
442              
443             # Used to get the right elements
444             sub get_of_type {
445 364     364 1 1532 my ( $self, $node_type, $types ) = @_;
446              
447             # Upper case the name
448 364         430 $node_type = uc($node_type);
449              
450             # See if there is an alias for it
451 364 100       703 $node_type = uc( $node_aliases{$node_type} )
452             if defined $node_aliases{$node_type};
453              
454 364 100       762 return undef unless defined $self->{nodes}->{$node_type};
455              
456 315 100       402 if ($types) {
457              
458             # After specific types
459 10         8 my @of_type;
460 10 100       22 if ( ref($types) eq 'ARRAY' ) {
461 2         2 @of_type = @{$types};
  2         5  
462             } else {
463 8         13 push( @of_type, $types );
464             }
465 10         9 my @to_return;
466 10         8 foreach my $element ( @{ $self->{nodes}->{$node_type} } ) {
  10         30  
467 23         27 my $check = 1; # assum ok for now
468 23         22 foreach my $type (@of_type) {
469              
470             # set it as bad if we don't match
471 29 100       46 $check = 0 unless $element->is_type($type);
472             }
473 23 100       38 if ( $check == 1 ) {
474              
475 14         19 push( @to_return, $element );
476             }
477             }
478              
479 10 100       21 return undef unless scalar(@to_return);
480              
481             # Make prefered value first
482 9         27 @to_return = sort { _sort_prefs($b) <=> _sort_prefs($a) } @to_return;
  6         10  
483              
484 9 100       44 return wantarray ? @to_return : \@to_return;
485              
486             } else {
487              
488             # Return them all
489             return wantarray
490 305 100       789 ? @{ $self->{nodes}->{$node_type} }
  8         29  
491             : $self->{nodes}->{$node_type};
492             }
493             }
494              
495             =head2 as_string
496              
497             Returns the vCard as a string.
498              
499             =cut
500              
501             sub as_string {
502 22     22 1 5591 my ( $self, $fields ) = @_;
503              
504             # derp
505 22 50       24 my %e = map { lc $_ => 1 } @{ $fields || [] };
  0         0  
  22         130  
506              
507 22         57 my @k = qw(VERSION N FN);
508 22 50       44 if ($fields) {
509 0         0 push @k, sort map { uc $_ } @$fields;
  0         0  
510             } else {
511 139         321 push @k, grep { $_ !~ /^(VERSION|N|FN)$/ }
  139         230  
512 22         28 sort map { uc $_ } keys %{ $self->{nodes} };
  22         85  
513             }
514              
515             # 'perldoc perlport' says using \r\n is wrong and confusing for a few
516             # reasons but mainly because the value of \n is different on different
517             # operating systems. It recommends \x0D\x0A instead.
518 22         46 my $newline = "\x0D\x0A";
519 22         33 my $begin = 'BEGIN:VCARD';
520 22         26 my $end = 'END:VCARD';
521              
522 22         29 my @lines = ($begin);
523 22         41 for my $k (@k) {
524 153         3316 my $nodes = $self->get($k);
525 153         203 push @lines, map { $_->as_string() } @$nodes;
  173         1026  
526             }
527 22         707 return join $newline, @lines, $end, '';
528             }
529              
530             sub _sort_prefs {
531 12     12   10 my $check = shift;
532 12 100       15 if ( $check->is_type('pref') ) {
533 10         19 return 1;
534             } else {
535 2         3 return 0;
536             }
537             }
538              
539             # Private method for adding nodes
540             sub _add_node {
541 266     266   399 my ( $self, $conf ) = @_;
542              
543 266         388 my $value_fields = $self->get_lookup();
544              
545 266         443 my $node_type = uc( $conf->{node_type} );
546 266 100       542 $node_type = $node_aliases{$node_type}
547             if defined $node_aliases{$node_type};
548              
549 266         228 my $field_list;
550              
551 266 100       460 if ( defined $value_fields->{$node_type} ) {
552              
553             # We know what the field list is
554 65         123 $field_list = $value_fields->{$node_type};
555             } else {
556              
557             # No defined fields - use just the 'value' one
558 201         232 $field_list = \@default_field;
559             }
560 266 100       550 unless ( defined $self->{nodes}->{$node_type} ) {
561              
562             # create space to hold list of node objects
563 247         208 my @node_list_space;
564 247         427 $self->{nodes}->{$node_type} = \@node_list_space;
565             }
566 266         218 my $last_node;
567 266         224 foreach my $node_data ( @{ $conf->{data} } ) {
  266         397  
568 309   100     2012 my $node_obj = Text::vCard::Node->new(
569             { node_type => $node_type,
570             fields => $field_list,
571             data => $node_data,
572             group => $conf->{group} || '',
573             encoding_out => $self->{encoding_out},
574             }
575             );
576              
577 309         501 push( @{ $self->{nodes}->{$node_type} }, $node_obj );
  309         571  
578              
579             # store the last node so we can return it.
580 309         488 $last_node = $node_obj;
581             }
582 266         642 return $last_node;
583             }
584              
585             =head1 AUTHOR
586              
587             Leo Lapworth, LLAP@cuckoo.org
588             Eric Johnson (kablamo), github ~!at!~ iijo dot org
589              
590             =head1 Repository (git)
591              
592             http://github.com/ranguard/text-vcard, git://github.com/ranguard/text-vcard.git
593              
594             =head1 COPYRIGHT
595              
596             Copyright (c) 2005-2010 Leo Lapworth. All rights reserved.
597             This program is free software; you can redistribute
598             it and/or modify it under the same terms as Perl itself.
599              
600             =head1 SEE ALSO
601              
602             L, L,
603             L L, L L,
604              
605             =cut
606              
607             1;