File Coverage

lib/Text/vCard/Addressbook.pm
Criterion Covered Total %
statement 100 101 99.0
branch 25 28 89.2
condition 7 7 100.0
subroutine 19 19 100.0
pod 7 7 100.0
total 158 162 97.5


line stmt bran cond sub pod time code
1             package Text::vCard::Addressbook;
2             $Text::vCard::Addressbook::VERSION = '3.08';
3 15     15   767304 use Carp;
  15         21  
  15         697  
4 15     15   59 use strict;
  15         20  
  15         252  
5 15     15   47 use warnings;
  15         19  
  15         318  
6 15     15   7446 use Path::Tiny;
  15         88611  
  15         634  
7 15     15   4591 use Text::vFile::asData;
  15         42660  
  15         70  
8 15     15   4014 use Text::vCard;
  15         33  
  15         174  
9              
10             # See this module for your basic parser functions
11 15     15   633 use base qw(Text::vFile::asData);
  15         18  
  15         12640  
12              
13             =head1 NAME
14              
15             Text::vCard::Addressbook - Parse, edit, and create vCard address books (RFC 2426)
16              
17             =head1 WARNING
18              
19             L is built on top of this module and provides a more
20             intuitive user interface. Please try that module first.
21              
22             =head1 SYNOPSIS
23              
24             use Text::vCard::Addressbook;
25              
26             # To read an existing address book file:
27              
28             my $address_book = Text::vCard::Addressbook->new({
29             'source_file' => '/path/to/address_book.vcf',
30             });
31              
32             foreach my $vcard ( $address_book->vcards() ) {
33             print "Got card for " . $vcard->fullname() . "\n";
34             }
35              
36             # To create a new address book file:
37              
38             my $address_book = Text::vCard::Addressbook->new();
39             my $vcard = $address_book->add_vcard;
40             $vcard->fullname('Foo Bar');
41             $vcard->EMAIL('foo@bar.com');
42              
43             open my $out, '>:encoding(UTF-8)', 'new_address_book.vcf' or die;
44             print $out $address_book->export;
45              
46              
47             =head1 DESCRIPTION
48              
49             This package provides an API to reading / editing and creating multiple vCards.
50             A vCard is an electronic business card. This package has been developed based
51             on rfc2426.
52              
53             You will find that many applications (Apple Address book, MS Outlook, Evolution
54             etc) can export and import vCards.
55              
56              
57             =head1 ENCODING AND UTF-8
58              
59             =head2 Constructor Arguments
60              
61             The 'encoding_in' and 'encoding_out' constructor arguments allow you to read
62             and write vCard files with any encoding. Examples of valid values are
63             'UTF-8', 'Latin1', and 'none'.
64              
65             Both values default to 'UTF-8' and this should just work for the vast majority
66             of people. The latest vCard RFC 6350 only allows UTF-8 as an encoding so most
67             people should not need to use either of these constructor arguments.
68              
69             =head2 MIME encodings
70              
71             vCard RFC 6350 only allows UTF-8 but it still permits 8bit MIME encoding
72             schemes such as Quoted-Printable and Base64 which are supported by this module.
73              
74             =head2 Manually setting values on a Text::vCard or Text::vCard::Node object
75              
76             If you manually set values on a Text::vCard or Text::vCard::Node object they
77             must be decoded values. The only exception to this rule is if you are messing
78             around with the 'encoding_out' constructor arg.
79              
80              
81             =head1 METHODS FOR LOADING VCARDS
82              
83             =head2 load()
84              
85             my $address_book = Text::vCard::Addressbook->load(
86             [ 'foo.vCard', 'Addresses.vcf' ], # list of files to load
87             );
88              
89             This method will croak if it is unable to read in any of the files.
90              
91             =cut
92              
93             sub load {
94 8     8 1 4213 my ( $proto, $filenames, $constructor_args ) = @_;
95              
96 8         23 my $self = __PACKAGE__->new($constructor_args);
97              
98 8         10 foreach my $filename ( @{$filenames} ) {
  8         16  
99              
100 8 100       337 croak "Unable to read file $filename" unless -r $filename;
101              
102 7         122 my $file = $self->_path($filename);
103 7         131 my $string = $file->slurp( $self->_iomode_in );
104              
105 7 50       6076 die <
106             ERROR: Either there is no END in this vCard or there is a problem with the line
107             endings. Note that the vCard RFC requires line endings delimited by \\r\\n
108             regardless of your operating system. Windows :crlf mode will strip out the \\r
109             so don't use that.
110             EOS
111             unless $string =~ m/\r\n/m;
112              
113 7         23 $self->import_data($string);
114             }
115              
116 7         20 return $self;
117              
118             }
119              
120             =head2 import_data()
121              
122             $address_book->import_data($string);
123              
124             This method imports data directly from a string. $string is assumed to be
125             decoded (but not MIME decoded).
126              
127             =cut
128              
129             sub import_data {
130 7     7 1 14 my ( $self, $value ) = @_;
131              
132 7         20 $self->_process_text($value);
133             }
134              
135             =head2 new()
136              
137             # Create a new (empty) address book
138             my $address_book = Text::vCard::Addressbook->new();
139            
140             # Load vcards from a single file
141             my $address_book = Text::vCard::Addressbook->new({
142             source_file => '/path/to/address_book.vcf'
143             });
144              
145             # Load vcards from a a string
146             my $address_book = Text::vCard::Addressbook->new({
147             source_text => $source_text
148             });
149              
150             This method will croak if it is unable to read the source_file.
151              
152             The constructor accepts 'encoding_in' and 'encoding_out' attributes. The
153             default values for both are 'UTF-8'. You can set them to 'none' if
154             you don't want your output encoded with Encode::encode(). But be aware the
155             latest vCard RFC 6350 mandates UTF-8.
156              
157             =cut
158              
159             sub new {
160 33     33 1 6491 my ( $proto, $conf ) = @_;
161 33   100     155 my $class = ref($proto) || $proto;
162 33         46 my $self = {};
163              
164 33         59 bless( $self, $class );
165              
166             # create some where to store out individual vCard objects
167 32         295 $self->{'cards'} = [];
168 32   100     120 $self->{encoding_in} = $conf->{encoding_in} || 'UTF-8';
169 32   100     130 $self->{encoding_out} = $conf->{encoding_out} || 'UTF-8';
170              
171             # slurp in file contents
172 32 100       73 if ( defined $conf->{'source_file'} ) {
173              
174             croak "Unable to read file $conf->{'source_file'}\n"
175 13 100       324 unless -r $conf->{'source_file'};
176              
177 12         20 my $filename = $conf->{source_file};
178 12         35 my $file = $self->_path($filename);
179 12         367 $conf->{source_text} = $file->slurp( $self->_iomode_in );
180             }
181              
182             # Process the text if we have it.
183             $self->_process_text( $conf->{'source_text'} )
184 31 100       9110 if defined $conf->{'source_text'};
185              
186 30         72 return $self;
187             }
188              
189             =head1 OTHER METHODS
190              
191             =head2 add_vcard()
192              
193             my $vcard = $address_book->add_vcard();
194              
195             This method creates a new empty L object, stores it in the
196             address book and return it so you can add data to it.
197              
198             =cut
199              
200             sub add_vcard {
201 3     3 1 520 my $self = shift;
202 3         22 my $vcard = Text::vCard->new( { encoding_out => $self->{encoding_out} } );
203 3         6 push( @{ $self->{cards} }, $vcard );
  3         8  
204 3         4 return $vcard;
205             }
206              
207             =head2 vcards()
208              
209             my $vcards = $address_book->vcards();
210             my @vcards = $address_book->vcards();
211              
212             This method returns a reference to an array or an array of
213             vcards in this address book. This could be an empty list
214             if there are no entries in the address book.
215              
216             =cut
217              
218             sub vcards {
219 27     27 1 2928 my $self = shift;
220 27 100       86 return wantarray ? @{ $self->{cards} } : $self->{cards};
  13         50  
221             }
222              
223             =head2 set_encoding()
224              
225             DEPRECATED. Use the 'encoding_in' and 'encoding_out' constructor arguments.
226              
227             =cut
228              
229             sub set_encoding {
230 1     1 1 1728 my ( $self, $coding ) = @_;
231 1         4 $self->{'encoding'} |= '';
232 1 50       6 $self->{'encoding'} = ";charset=$coding" if ( defined $coding );
233 1         2 return $self->{'encoding'};
234 0         0 die "DEPRECATED. Use the 'encoding_in' and 'encoding_out'"
235             . " constructor arguments";
236             }
237              
238             =head2 export()
239              
240             my $string = $address_book->export()
241              
242             This method returns the vcard data as a string in the vcf file format.
243              
244             Please note there is no validation, you must ensure that the correct nodes
245             (FN,N,VERSION) are already added to each vcard if you want to comply with
246             RFC 2426.
247              
248             =cut
249              
250             sub export {
251 9     9 1 3654 my $self = shift;
252 9         11 my $string = '';
253 9         20 $string .= $_->as_string for $self->vcards;
254 9         44 return $string;
255             }
256              
257             # PRIVATE METHODS
258              
259             # PerlIO layers should look like ':encoding(UTF-8)'
260             # The ':encoding()' part does character set and encoding transformations.
261             # Without it you are just declaring the stream to be of a certain encoding.
262             # See PerlIO, PerlIO::encoding docs.
263             sub _iomode_in {
264 19     19   25 my ($self) = @_;
265 19 100       77 return { binmode => ':raw' } if $self->{encoding_in} eq 'none';
266 18         119 return { binmode => ':raw:encoding(' . $self->{encoding_in} . ')' };
267             }
268              
269             # Filename can be a string, a Path::Tiny obj, or a Path::Class obj.
270             # Returns a Path::Tiny obj.
271             sub _path {
272 19     19   29 my ( $self, $filename ) = @_;
273 19 50       98 return ref $filename eq 'Path::Class::File' #
274             ? path("$filename")
275             : path($filename); # works for strings and Path::Tiny objects
276             }
277              
278             # Process a chunk of text, create Text::vCard objects and store in the address book
279             sub _pre_process_text {
280 25     25   33 my ( $self, $text ) = @_;
281              
282 25 100   7   445 if ( $text =~ /quoted-printable/i ) {
  7         3369  
  7         55  
  7         100  
283              
284             # Edge case for 2.1 version
285             #
286             # http://tools.ietf.org/html/rfc2045#section-6.7 point (5),
287             # lines containing quoted-printable encoded data can contain soft line
288             # breaks. These are indicated as single '=' sign at the end of the
289             # line.
290             #
291             # No longer needed in version 3.0:
292             # http://tools.ietf.org/html/rfc2426 point (5)
293             #
294             # 'perldoc perlport' says using \r\n is wrong and confusing for a few
295             # reasons but mainly because the value of \n is different on different
296             # operating systems. It recommends \x0D\x0A instead.
297              
298 5         8 my $out;
299 5         8 my $inside = 0;
300 5         47 foreach my $line ( split( "\x0D\x0A", $text ) ) {
301              
302 61 100       79 if ($inside) {
303 10 100       24 if ( $line =~ /=$/ ) {
304 1         3 $line =~ s/=$//;
305             } else {
306 9         10 $inside = 0;
307             }
308             }
309              
310 61 100       1328 if ( $line =~ /ENCODING=QUOTED-PRINTABLE/i ) {
311 9         13 $inside = 1;
312 9         30 $line =~ s/=$//;
313             }
314 61         17261 $out .= $line . "\x0D\x0A";
315             }
316 5         16 $text = $out;
317              
318             }
319              
320             # Add error checking here ?
321 25         100402 my $asData = Text::vFile::asData->new;
322 25         270 $asData->preserve_params(1);
323              
324 25         431 my @lines = split "\x0D\x0A", $text;
325 25         51 my @lines_with_newlines = map { $_ . "\x0D\x0A" } @lines;
  335         479  
326 25         102 return $asData->parse_lines(@lines_with_newlines)->{objects};
327             }
328              
329             sub _process_text {
330 19     19   31 my ( $self, $text ) = @_;
331              
332 19         47 my $cards = $self->_pre_process_text($text);
333              
334 19         31536 foreach my $card (@$cards) {
335              
336             # Run through each card in the data
337 21 100       91 if ( $card->{'type'} =~ /VCARD/i ) {
338             my $vcard = Text::vCard->new(
339             { 'asData_node' => $card->{'properties'},
340             encoding_in => $self->{encoding_in},
341             encoding_out => $self->{encoding_out}
342             }
343 20         190 );
344 20         33 push( @{ $self->{'cards'} }, $vcard );
  20         48  
345             } else {
346 1         127 carp
347             "This file contains $card->{'type'} data which was not parsed";
348             }
349             }
350              
351 18         179 return $self->{cards};
352             }
353              
354             =head1 AUTHOR
355              
356             Leo Lapworth, LLAP@cuckoo.org
357             Eric Johnson (kablamo), github ~!at!~ iijo dot org
358              
359             =head1 COPYRIGHT
360              
361             Copyright (c) 2003 Leo Lapworth. All rights reserved.
362             This program is free software; you can redistribute
363             it and/or modify it under the same terms as Perl itself.
364              
365             =head1 ACKNOWLEDGEMENTS
366              
367             The authors of L for making my life so much easier.
368              
369             =head1 SEE ALSO
370              
371             L, L
372              
373             =cut
374              
375             1;