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.09';
3 15     15   772160 use Carp;
  15         20  
  15         702  
4 15     15   56 use strict;
  15         15  
  15         220  
5 15     15   42 use warnings;
  15         16  
  15         294  
6 15     15   7408 use Path::Tiny;
  15         90005  
  15         669  
7 15     15   4785 use Text::vFile::asData;
  15         42615  
  15         73  
8 15     15   3890 use Text::vCard;
  15         29  
  15         152  
9              
10             # See this module for your basic parser functions
11 15     15   572 use base qw(Text::vFile::asData);
  15         15  
  15         12691  
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 4454 my ( $proto, $filenames, $constructor_args ) = @_;
95              
96 8         33 my $self = __PACKAGE__->new($constructor_args);
97              
98 8         9 foreach my $filename ( @{$filenames} ) {
  8         20  
99              
100 8 100       374 croak "Unable to read file $filename" unless -r $filename;
101              
102 7         138 my $file = $self->_path($filename);
103 7         142 my $string = $file->slurp( $self->_iomode_in );
104              
105 7 50       6576 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         22 $self->import_data($string);
114             }
115              
116 7         21 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 12 my ( $self, $value ) = @_;
131              
132 7         24 $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 7082 my ( $proto, $conf ) = @_;
161 33   100     166 my $class = ref($proto) || $proto;
162 33         43 my $self = {};
163              
164 33         72 bless( $self, $class );
165              
166             # create some where to store out individual vCard objects
167 32         296 $self->{'cards'} = [];
168 32   100     131 $self->{encoding_in} = $conf->{encoding_in} || 'UTF-8';
169 32   100     131 $self->{encoding_out} = $conf->{encoding_out} || 'UTF-8';
170              
171             # slurp in file contents
172 32 100       84 if ( defined $conf->{'source_file'} ) {
173              
174             croak "Unable to read file $conf->{'source_file'}\n"
175 13 100       345 unless -r $conf->{'source_file'};
176              
177 12         19 my $filename = $conf->{source_file};
178 12         32 my $file = $self->_path($filename);
179 12         332 $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       8770 if defined $conf->{'source_text'};
185              
186 30         73 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 521 my $self = shift;
202 3         21 my $vcard = Text::vCard->new( { encoding_out => $self->{encoding_out} } );
203 3         6 push( @{ $self->{cards} }, $vcard );
  3         8  
204 3         6 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 2973 my $self = shift;
220 27 100       80 return wantarray ? @{ $self->{cards} } : $self->{cards};
  13         47  
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 1621 my ( $self, $coding ) = @_;
231 1         4 $self->{'encoding'} |= '';
232 1 50       5 $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 3482 my $self = shift;
252 9         13 my $string = '';
253 9         20 $string .= $_->as_string for $self->vcards;
254 9         39 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   26 my ($self) = @_;
265 19 100       72 return { binmode => ':raw' } if $self->{encoding_in} eq 'none';
266 18         112 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   26 my ( $self, $filename ) = @_;
273 19 50       97 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   34 my ( $self, $text ) = @_;
281              
282 25 100   7   471 if ( $text =~ /quoted-printable/i ) {
  7         3484  
  7         55  
  7         90  
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         6 my $out;
299 5         9 my $inside = 0;
300 5         49 foreach my $line ( split( "\x0D\x0A", $text ) ) {
301              
302 61 100       80 if ($inside) {
303 10 100       25 if ( $line =~ /=$/ ) {
304 1         4 $line =~ s/=$//;
305             } else {
306 9         38 $inside = 0;
307             }
308             }
309              
310 61 100       1377 if ( $line =~ /ENCODING=QUOTED-PRINTABLE/i ) {
311 9         11 $inside = 1;
312 9         28 $line =~ s/=$//;
313             }
314 61         18403 $out .= $line . "\x0D\x0A";
315             }
316 5         21 $text = $out;
317              
318             }
319              
320             # Add error checking here ?
321 25         104687 my $asData = Text::vFile::asData->new;
322 25         348 $asData->preserve_params(1);
323              
324 25         462 my @lines = split "\x0D\x0A", $text;
325 25         51 my @lines_with_newlines = map { $_ . "\x0D\x0A" } @lines;
  335         472  
326 25         114 return $asData->parse_lines(@lines_with_newlines)->{objects};
327             }
328              
329             sub _process_text {
330 19     19   30 my ( $self, $text ) = @_;
331              
332 19         44 my $cards = $self->_pre_process_text($text);
333              
334 19         31262 foreach my $card (@$cards) {
335              
336             # Run through each card in the data
337 21 100       109 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         178 );
344 20         28 push( @{ $self->{'cards'} }, $vcard );
  20         46  
345             } else {
346 1         127 carp
347             "This file contains $card->{'type'} data which was not parsed";
348             }
349             }
350              
351 18         163 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;