File Coverage

lib/Text/vCard.pm
Criterion Covered Total %
statement 158 161 98.1
branch 63 68 92.6
condition 22 28 78.5
subroutine 21 22 95.4
pod 8 8 100.0
total 272 287 94.7


line stmt bran cond sub pod time code
1             package Text::vCard;
2             $Text::vCard::VERSION = '3.08';
3 15     15   57479 use 5.006;
  15         38  
4 15     15   51 use Carp;
  15         14  
  15         675  
5 15     15   67 use strict;
  15         25  
  15         251  
6 15     15   46 use warnings;
  15         16  
  15         458  
7 15     15   1592 use Text::vFile::asData 0.07;
  15         16064  
  15         69  
8 15     15   5619 use Text::vCard::Node;
  15         26  
  15         455  
9              
10             # See this module for your basic parser functions
11 15     15   73 use base qw(Text::vFile::asData);
  15         14  
  15         1209  
12 15     15   59 use vars qw (%lookup %node_aliases @simple);
  15         16  
  15         1700  
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   64 no strict 'refs';
  15         16  
  15         380  
48 15     15   48 no warnings 'redefine';
  15         19  
  15         16914  
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   484 my ( $self, $value ) = @_;
56              
57             # See if we have it already
58 117         140 my $nodes = $self->get($node);
59 117 100 100     243 if ( !defined $nodes && $value ) {
60              
61             # Add it as a node if not exists and there is a value
62 33         87 $self->add_node( { 'node_type' => $node, } );
63              
64             # Get it out again
65 33         53 $nodes = $self->get($node);
66             }
67              
68 117 100 100     348 if ( scalar($nodes) && $value ) {
69              
70             # Set it
71 34         155 $nodes->[0]->value($value);
72             }
73              
74 117 100       401 return $nodes->[0]->value() if scalar($nodes);
75 1         4 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 960 my ( $proto, $conf ) = @_;
122 39   100     182 my $class = ref($proto) || $proto;
123 39         50 my $self = {};
124              
125 39         75 bless( $self, $class );
126              
127 38   100     165 $self->{encoding_out} = $conf->{encoding_out} || 'UTF-8';
128              
129 38         39 my %nodes;
130 38         53 $self->{nodes} = \%nodes;
131              
132 38 100       86 if ( defined $conf->{'asData_node'} ) {
133              
134             # Have a vcard data node being passed in
135 26         34 while ( my ( $node_type, $data ) = each %{ $conf->{'asData_node'} } )
  228         557  
136             {
137 202         140 my $group;
138 202 100       354 if ( $node_type =~ /\./ ) {
139              
140             # Version 3.0 supports group types, we do not
141             # so remove everything before '.'
142 14         61 ( $group, $node_type ) = $node_type =~ /(.+)\.(.*)/;
143             }
144              
145             # Deal with each type (ADR, FN, TEL etc)
146             $self->_add_node(
147 202         479 { 'node_type' => $node_type,
148             'data' => $data,
149             'group' => $group,
150             }
151             );
152             }
153             } # else we're creating a new vCard
154              
155 38         78 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 1101 my ( $self, $conf ) = @_;
171             croak 'Must supply a node_type'
172 67 100 100     484 unless defined $conf && defined $conf->{'node_type'};
173 65 100       99 unless ( defined $conf->{data} ) {
174 36         25 my %empty;
175 36         44 my @data = ( \%empty );
176 36         50 $conf->{'data'} = \@data;
177             }
178              
179 65         85 $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 361     361 1 3712 my ( $self, $conf ) = @_;
218 361 100       611 carp "You did not supply an element type" unless defined $conf;
219 360 100       482 if ( ref($conf) eq 'HASH' ) {
220             return $self->get_of_type( $conf->{'node_type'}, $conf->{'types'} )
221 12 100       42 if defined $conf->{'types'};
222 6         22 return $self->get_of_type( $conf->{'node_type'} );
223             } else {
224 348         430 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 226 my ( $self, $node_type, $types ) = @_;
244 1 50       3 carp "You did not supply an element type" unless defined $node_type;
245              
246 1         2 my %hash = ( 'node_type', $node_type );
247 1 50       4 $hash{'types'} = $types if defined $types;
248 1         6 my $node = $self->get( \%hash );
249 1 50 33     4 return undef unless $node && @{$node} > 0 && exists $node->[0]->{'value'};
  1   33     9  
250              
251 1         3 $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 687 my ( $self, $group_name, $node_type ) = @_;
357 4         4 my @to_return;
358              
359 4 100 66     130 carp "No group name supplied"
360             unless defined $group_name
361             and $group_name ne '';
362              
363 3         3 $group_name = lc($group_name);
364              
365 3 100 66     9 if ( defined $node_type && $node_type ne '' ) {
366              
367             # After a specific node type
368 1         3 my $nodes = $self->get($node_type);
369 1         2 foreach my $node ( @{$nodes} ) {
  1         2  
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         1 foreach my $node_loop ( keys %{ $self->{nodes} } ) {
  2         6  
376              
377             # Loop through each type
378 12         13 my $nodes = $self->get($node_loop);
379 12         10 foreach my $node ( @{$nodes} ) {
  12         12  
380 18 100       20 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     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 267     267 1 203 my $self = shift;
431 267         273 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 366     366 1 1243 my ( $self, $node_type, $types ) = @_;
446              
447             # Upper case the name
448 366         345 $node_type = uc($node_type);
449              
450             # See if there is an alias for it
451             $node_type = uc( $node_aliases{$node_type} )
452 366 100       581 if defined $node_aliases{$node_type};
453              
454 366 100       579 return undef unless defined $self->{nodes}->{$node_type};
455              
456 317 100       323 if ($types) {
457              
458             # After specific types
459 10         8 my @of_type;
460 10 100       19 if ( ref($types) eq 'ARRAY' ) {
461 2         1 @of_type = @{$types};
  2         5  
462             } else {
463 8         12 push( @of_type, $types );
464             }
465 10         7 my @to_return;
466 10         9 foreach my $element ( @{ $self->{nodes}->{$node_type} } ) {
  10         21  
467 23         17 my $check = 1; # assum ok for now
468 23         20 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       43 if ( $check == 1 ) {
474              
475 14         17 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         18 @to_return = sort { _sort_prefs($b) <=> _sort_prefs($a) } @to_return;
  6         10  
483              
484 9 100       27 return wantarray ? @to_return : \@to_return;
485              
486             } else {
487              
488             # Return them all
489             return wantarray
490 8         23 ? @{ $self->{nodes}->{$node_type} }
491 307 100       615 : $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 4220 my ( $self, $fields ) = @_;
503              
504             # derp
505 22 50       23 my %e = map { lc $_ => 1 } @{ $fields || [] };
  0         0  
  22         99  
506              
507 22         46 my @k = qw(VERSION N FN);
508 22 50       39 if ($fields) {
509 0         0 push @k, sort map { uc $_ } @$fields;
  0         0  
510             } else {
511 141         292 push @k, grep { $_ !~ /^(VERSION|N|FN)$/ }
512 22         26 sort map { uc $_ } keys %{ $self->{nodes} };
  141         207  
  22         73  
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         39 my $newline = "\x0D\x0A";
519 22         22 my $begin = 'BEGIN:VCARD';
520 22         18 my $end = 'END:VCARD';
521              
522 22         27 my @lines = ($begin);
523 22         29 for my $k (@k) {
524 155         2695 my $nodes = $self->get($k);
525 155         204 push @lines, map { $_->as_string() } @$nodes;
  175         863  
526             }
527 22         563 return join $newline, @lines, $end, '';
528             }
529              
530             sub _sort_prefs {
531 12     12   9 my $check = shift;
532 12 100       15 if ( $check->is_type('pref') ) {
533 10         13 return 1;
534             } else {
535 2         4 return 0;
536             }
537             }
538              
539             # Private method for adding nodes
540             sub _add_node {
541 267     267   219 my ( $self, $conf ) = @_;
542              
543 267         333 my $value_fields = $self->get_lookup();
544              
545 267         330 my $node_type = uc( $conf->{node_type} );
546             $node_type = $node_aliases{$node_type}
547 267 100       442 if defined $node_aliases{$node_type};
548              
549 267         176 my $field_list;
550              
551 267 100       357 if ( defined $value_fields->{$node_type} ) {
552              
553             # We know what the field list is
554 65         73 $field_list = $value_fields->{$node_type};
555             } else {
556              
557             # No defined fields - use just the 'value' one
558 202         181 $field_list = \@default_field;
559             }
560 267 100       406 unless ( defined $self->{nodes}->{$node_type} ) {
561              
562             # create space to hold list of node objects
563 248         174 my @node_list_space;
564 248         331 $self->{nodes}->{$node_type} = \@node_list_space;
565             }
566 267         187 my $last_node;
567 267         169 foreach my $node_data ( @{ $conf->{data} } ) {
  267         344  
568             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 310   100     1576 );
576              
577 310         409 push( @{ $self->{nodes}->{$node_type} }, $node_obj );
  310         488  
578              
579             # store the last node so we can return it.
580 310         364 $last_node = $node_obj;
581             }
582 267         427 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;