File Coverage

blib/lib/Text/vCard/Precisely/V4.pm
Criterion Covered Total %
statement 69 70 98.5
branch 15 16 93.7
condition n/a
subroutine 21 21 100.0
pod 8 8 100.0
total 113 115 98.2


line stmt bran cond sub pod time code
1             package Text::vCard::Precisely::V4;
2              
3             our $VERSION = '0.26';
4              
5 14     14   1402092 use Moose;
  14         6086640  
  14         103  
6 14     14   105788 use Moose::Util::TypeConstraints;
  14         33  
  14         140  
7 14     14   40164 use MooseX::Types::DateTime qw(TimeZone);
  14         7867195  
  14         118  
8              
9             extends 'Text::vCard::Precisely::V3';
10              
11 14     14   28869 use Carp;
  14         34  
  14         1120  
12 14     14   9886 use Encode;
  14         137194  
  14         1236  
13              
14             =encoding utf8
15              
16             =head1 NAME
17              
18             Text::vCard::Precisely::V4 - Read, Write and Edit B<vCards 4.0>
19              
20             =head1 SYNOPSIS
21            
22             You can unlock types that will be available in vCard4.0
23              
24             my $vc = Text::vCard::Precisely->new( version => '4.0' );
25             # Or you can write like below:
26             my $vc4 = Text::vCard::Precisely::V4->new();
27              
28             The Usage is same with L<Text::vCard::Precisely::V3>
29              
30             =head1 DESCRIPTION
31              
32             This module is an additional version for reading/writing for vCard4.0. it's just a wrapper of L<Text::vCard::Precisely::V3|https://metacpan.org/pod/Text::vCard::Precisely::V3>
33              
34             B<Caution!> It's NOT be recommended because some reasons below:
35              
36             =over
37              
38             =item
39              
40             Mac OS X and iOS can't parse vCard4.0 with UTF-8 precisely.
41              
42             =item
43              
44             Android 4.4.x can't parse vCard4.0.
45              
46             =back
47              
48             Note that the vCard RFC requires C<FN> type.
49             And this module does not check or warn if these conditions have not been met.
50              
51             =cut
52              
53 14     14   6616 use Text::vCard::Precisely::V4::Node;
  14         62  
  14         698  
54 14     14   8602 use Text::vCard::Precisely::V4::Node::N;
  14         58  
  14         1075  
55 14     14   8454 use Text::vCard::Precisely::V4::Node::Address;
  14         337  
  14         766  
56 14     14   8556 use Text::vCard::Precisely::V4::Node::Tel;
  14         63  
  14         725  
57 14     14   8411 use Text::vCard::Precisely::V4::Node::Related;
  14         63  
  14         730  
58 14     14   8956 use Text::vCard::Precisely::V4::Node::Member;
  14         62  
  14         693  
59 14     14   8463 use Text::vCard::Precisely::V4::Node::Image;
  14         58  
  14         36425  
60              
61             has version => ( is => 'ro', isa => 'Str', default => '4.0' );
62              
63             =head1 Constructors
64              
65             =head2 load_hashref($HashRef)
66              
67             SAME as 3.0
68              
69             =head2 loadI<file($file>name)
70              
71             SAME as 3.0
72              
73             =head2 load_string($vCard)
74              
75             SAME as 3.0
76              
77             =cut
78              
79             override '_parse_param' => sub {
80             my ( $self, $content ) = @_;
81             my $ref = super();
82             $ref->{'media_type'} = $content->{'param'}{'MEDIATYPE'} if $content->{'param'}{'MEDIATYPE'};
83             return $ref;
84             };
85              
86             =head1 METHODS
87              
88             =head2 as_string()
89              
90             Returns the vCard as a string.
91             You HAVE TO use C<Encode::encode_utf8()> if your vCard is written in utf8
92              
93             =cut
94              
95             my $cr = "\x0D\x0A";
96             my @types = qw(
97             FN N NICKNAME
98             ADR TEL EMAIL IMPP LANG GEO
99             ORG TITLE ROLE CATEGORIES RELATED
100             NOTE SOUND URL FBURL CALADRURI CALURI
101             XML KEY SOCIALPROFILE PHOTO LOGO SOURCE
102             );
103              
104             # ToDo: to accept SORT-AS param in FN,N,ORG
105              
106             sub as_string {
107 35     35 1 5802 my ($self) = @_;
108 35         253 my $str = $self->_header();
109 35         233 $str .= $self->_make_types(@types);
110              
111 35 100       1020 $str .= 'KIND:' . $self->kind() . $cr if $self->kind();
112 35 100       1056 $str .= 'BDAY:' . $self->bday() . $cr if $self->bday();
113 35 100       1028 $str .= 'ANNIVERSARY:' . $self->anniversary() . $cr if $self->anniversary();
114 35 100       1037 $str .= 'GENDER:' . $self->gender() . $cr if $self->gender();
115 35 100       1041 $str .= 'UID:' . $self->uid() . $cr if $self->uid();
116 35 100       1050 $str .= join '', @{ $self->member() } if $self->member();
  1         30  
117 35 100       1106 map { $str .= "CLIENTPIDMAP:$_" . $cr } @{ $self->clientpidmap() } if $self->clientpidmap();
  3         17  
  2         94  
118              
119 35         183 $str .= $self->_footer();
120 35         205 $str = $self->_fold($str);
121 35 50       1292 return decode( $self->encoding_out(), $str ) unless $self->encoding_out() eq 'none';
122 0         0 return $str;
123             }
124              
125             =head2 as_file($filename)
126              
127             Write data in vCard format to $filename.
128              
129             Dies if not successful.
130              
131             =head1 SIMPLE GETTERS/SETTERS
132              
133             These methods accept and return strings.
134              
135             =head2 version()
136              
137             Returns Version number of the vcard. Defaults to B<'3.0'>
138              
139             It is B<READONLY> method. So you can NOT downgrade it to 3.0
140              
141             =head2 rev()
142              
143             To specify revision information about the current vCard
144              
145             The format in as_string() is B<different from 3.0>, but the interface is SAME
146              
147             =head1 COMPLEX GETTERS/SETTERS
148              
149             They are based on Moose with coercion
150              
151             So these methods accept not only ArrayRef[HashRef] but also ArrayRef[Str],
152             single HashRef or single Str
153              
154             Read source if you were confused
155              
156             =head2 n()
157              
158             The format is SAME as 3.0
159              
160             =cut
161              
162             subtype 'v4N' => as 'Text::vCard::Precisely::V4::Node::N';
163             coerce 'v4N', from 'HashRef[Maybe[Ref]|Maybe[Str]]', via {
164             my %param;
165             while ( my ( $key, $value ) = each %$_ ) {
166             $param{$key} = $value if $value;
167             }
168             return Text::vCard::Precisely::V4::Node::N->new( \%param );
169             },
170             from 'HashRef[Maybe[Str]]',
171             via { Text::vCard::Precisely::V4::Node::N->new( { content => $_ } ) },
172             from 'ArrayRef[Maybe[Str]]', via {
173             Text::vCard::Precisely::V4::Node::N->new(
174             { content => {
175             family => $_->[0] || '',
176             given => $_->[1] || '',
177             additional => $_->[2] || '',
178             prefixes => $_->[3] || '',
179             suffixes => $_->[4] || '',
180             }
181             }
182             )
183             },
184             from 'Str',
185             via { Text::vCard::Precisely::V4::Node::N->new( { content => [ split /(?<!\\);/, $_ ] } ) };
186             has n => ( is => 'rw', isa => 'v4N', coerce => 1 );
187              
188             =head2 tel()
189              
190             The format in as_string() is B<different from 3.0>, but the interface is SAME
191            
192             =cut
193              
194             subtype 'v4Tels' => as 'ArrayRef[Text::vCard::Precisely::V4::Node::Tel]';
195             coerce 'v4Tels',
196             from 'Str',
197             via { [ Text::vCard::Precisely::V4::Node::Tel->new( { content => $_ } ) ] },
198             from 'HashRef', via {
199             my $types = ref( $_->{'types'} ) eq 'ARRAY' ? $_->{'types'} : [ $_->{'types'} ];
200             [ Text::vCard::Precisely::V4::Node::Tel->new( { %$_, types => $types } ) ]
201             }, from 'ArrayRef[HashRef]', via {
202             [ map {
203             my $types = ref( $_->{'types'} ) eq 'ARRAY' ? $_->{'types'} : [ $_->{'types'} ];
204             Text::vCard::Precisely::V4::Node::Tel->new( { %$_, types => $types } )
205             } @$_
206             ]
207             };
208             has tel => ( is => 'rw', isa => 'v4Tels', coerce => 1 );
209              
210             =head2 adr(), address()
211              
212             Both are same method with Alias
213              
214             LABEL param and GEO param are now available
215              
216             =cut
217              
218             subtype 'v4Address' => as 'ArrayRef[Text::vCard::Precisely::V4::Node::Address]';
219             coerce 'v4Address',
220             from 'HashRef',
221             via { [ Text::vCard::Precisely::V4::Node::Address->new($_) ] }, from 'ArrayRef[HashRef]', via {
222             [ map { Text::vCard::Precisely::V4::Node::Address->new($_) } @$_ ]
223             };
224             has adr => ( is => 'rw', isa => 'v4Address', coerce => 1 );
225              
226             =head2 email()
227              
228             The format is SAME as 3.0
229              
230             =head2 url()
231              
232             The format is SAME as 3.0
233              
234             =head2 photo(), logo()
235              
236             The format is SAME as 3.0
237              
238             =cut
239              
240             subtype 'v4Photos' => as 'ArrayRef[Text::vCard::Precisely::V4::Node::Image]';
241             coerce 'v4Photos', from 'HashRef', via {
242             my $name = uc [ split( /::/, [ caller(2) ]->[3] ) ]->[-1];
243             return [
244             Text::vCard::Precisely::V4::Node::Image->new(
245             { name => $name,
246             media_type => $_->{media_type} || $_->{type},
247             content => $_->{content},
248             }
249             )
250             ]
251             }, from 'ArrayRef[HashRef]', via {
252             [ map {
253             if ( ref $_->{types} eq 'ARRAY' ) {
254             ( $_->{media_type} ) = @{ $_->{types} };
255             delete $_->{types};
256             }
257             Text::vCard::Precisely::V4::Node::Image->new($_)
258             } @$_
259             ]
260             }, from 'Str', # when parse BASE64 encoded strings
261             via {
262             my $name = uc [ split( /::/, [ caller(2) ]->[3] ) ]->[-1];
263             return [
264             Text::vCard::Precisely::V4::Node::Image->new(
265             { name => $name,
266             content => $_,
267             }
268             )
269             ]
270             }, from 'ArrayRef[Str]', # when parse BASE64 encoded strings
271             via {
272             my $name = uc [ split( /::/, [ caller(2) ]->[3] ) ]->[-1];
273             return [
274             map { Text::vCard::Precisely::V4::Node::Image->new( { name => $name, content => $_, } ) }
275             @$_ ]
276             }, from 'Object', # when URI.pm is used
277             via { [ Text::vCard::Precisely::V4::Node::Image->new( { content => $_->as_string() } ) ] };
278             has [qw| photo logo |] => ( is => 'rw', isa => 'v4Photos', coerce => 1 );
279              
280             =head2 note()
281              
282             The format is SAME as 3.0
283              
284             =head2 org(), title(), role(), categories()
285              
286             The format is SAME as 3.0
287              
288             =head2 fn(), full_name(), fullname()
289              
290             They are same method at all with Alias
291              
292             The format is SAME as 3.0
293              
294             =head2 nickname()
295              
296             The format is SAME as 3.0
297            
298             =head2 lang()
299              
300             To specify the language(s) that may be used for contacting the entity associated with the vCard
301              
302             It's the B<new method from 4.0>
303              
304             =head2 impp(), xml()
305              
306             I don't think they are so popular paramater, but here are the methods!
307              
308             They are the B<new method from 4.0>
309              
310             =head2 geo(), key()
311              
312             The format is SAME as 3.0
313              
314             =cut
315              
316             subtype 'v4Node' => as 'ArrayRef[Text::vCard::Precisely::V4::Node]';
317             coerce 'v4Node', from 'Str', via {
318             my $name = uc [ split( /::/, [ caller(2) ]->[3] ) ]->[-1];
319             return [ Text::vCard::Precisely::V4::Node->new( { name => $name, content => $_ } ) ]
320             }, from 'HashRef', via {
321             my $name = uc [ split( /::/, [ caller(2) ]->[3] ) ]->[-1];
322             return [
323             Text::vCard::Precisely::V4::Node->new(
324             { name => $_->{'name'} || $name,
325             types => $_->{'types'} || [],
326             sort_as => $_->{'sort_as'},
327             content => $_->{'content'} || croak "No value in HashRef!",
328             }
329             )
330             ]
331             }, from 'ArrayRef[HashRef]', via {
332             my $name = uc [ split( /::/, [ caller(2) ]->[3] ) ]->[-1];
333             return [
334             map {
335             Text::vCard::Precisely::V4::Node->new(
336             { name => $_->{'name'} || $name,
337             types => $_->{'types'} || [],
338             sort_as => $_->{'sort_as'},
339             content => $_->{'content'} || croak "No value in HashRef!",
340             }
341             )
342             } @$_
343             ]
344             };
345             has [qw|note org title role categories fn nickname lang impp xml geo key|] =>
346             ( is => 'rw', isa => 'v4Node', coerce => 1 );
347              
348             =head2 source(), sound()
349              
350             The formats are SAME as 3.0
351              
352             =head2 fburl(), caladruri(), caluri()
353              
354             I don't think they are so popular types, but here are the methods!
355              
356             They are the B<new method from 4.0>
357              
358             =cut
359              
360             has [qw|source sound fburl caladruri caluri|] => ( is => 'rw', isa => 'URLs', coerce => 1 );
361              
362             subtype 'Related' => as 'ArrayRef[Text::vCard::Precisely::V4::Node::Related]';
363             coerce 'Related',
364             from 'HashRef',
365             via { [ Text::vCard::Precisely::V4::Node::Related->new($_) ] }, from 'ArrayRef[HashRef]', via {
366             [ map { Text::vCard::Precisely::V4::Node::Related->new($_) } @$_ ]
367             };
368             has related => ( is => 'rw', isa => 'Related', coerce => 1 );
369              
370             =head2 kind()
371              
372             To specify the kind of object the vCard represents
373              
374             It's the B<new method from 4.0>
375            
376             =cut
377              
378             subtype 'KIND' => as 'Str' =>
379             where {m/^(?:individual|group|org|location|[a-z0-9\-]+|X-[a-z0-9\-]+)$/s}
380             => message {"The KIND you provided, $_, was not supported"};
381             has kind => ( is => 'rw', isa => 'KIND' );
382              
383             subtype 'v4TimeStamp' => as 'Str' => where {m/^\d{8}T\d{6}(?:Z(?:-\d{2}(?:\d{2})?)?)?$/is}
384             => message {"The TimeStamp you provided, $_, was not correct"};
385             coerce 'v4TimeStamp', from 'Str', via {
386             m/^(\d{4})-?(\d{2})-?(\d{2})(?:T(\d{2}):?(\d{2}):?(\d{2})Z)?$/is;
387             return sprintf '%4d%02d%02dT%02d%02d%02dZ', $1, $2, $3, $4, $5, $6
388             }, from 'Int', via {
389             my ( $s, $m, $h, $d, $M, $y ) = gmtime($_);
390             return sprintf '%4d%02d%02dT%02d%02d%02dZ', $y + 1900, $M + 1, $d, $h, $m, $s
391             }, from 'ArrayRef[HashRef]', via { $_->[0]{content} };
392             has rev => ( is => 'rw', isa => 'v4TimeStamp', coerce => 1 );
393              
394             =head2 member(), clientpidmap()
395              
396             I don't think they are so popular types, but here are the methods!
397              
398             It's the B<new method from 4.0>
399              
400             =cut
401              
402             subtype 'MEMBER' => as 'ArrayRef[Text::vCard::Precisely::V4::Node::Member]';
403             coerce 'MEMBER',
404             from 'UID',
405             via { [ Text::vCard::Precisely::V4::Node::Member->new($_) ] }, from 'ArrayRef[UID]', via {
406             [ map { Text::vCard::Precisely::V4::Node::Member->new( { content => $_ } ) } @$_ ]
407             };
408             has member => ( is => 'rw', isa => 'MEMBER', coerce => 1 );
409              
410             subtype 'CLIENTPIDMAP' => as 'Str' =>
411             where {m/^\d+;urn:uuid:[A-F0-9]{8}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{12}$/is}
412             => message {"The CLIENTPIDMAP you provided, $_, was not correct"};
413             subtype 'CLIENTPIDMAPs' => as 'ArrayRef[CLIENTPIDMAP]';
414             coerce 'CLIENTPIDMAPs', from 'Str', via { [$_] };
415             has clientpidmap => ( is => 'rw', isa => 'CLIENTPIDMAPs', coerce => 1 );
416              
417             =head2 tz(), timezone()
418              
419             Both are same method with Alias
420              
421             The format is SAME as 3.0
422            
423             =head2 bday(), birthday()
424              
425             Both are same method with Alias
426              
427             The format is SAME as 3.0
428              
429             =head2 anniversary()
430              
431             The date of marriage, or equivalent, of the object the vCard represents
432            
433             It's the B<new method from 4.0>
434              
435             =head2 gender()
436              
437             To specify the components of the sex and gender identity of the object the vCard represents
438              
439             It's the B<new method from 4.0>
440              
441             =head2 prodid()
442              
443             The format is SAME as 3.0
444              
445             =cut
446              
447             has [qw|bday anniversary gender prodid|] => ( is => 'rw', isa => 'Str' );
448              
449             __PACKAGE__->meta->make_immutable;
450 14     14   166 no Moose;
  14         68  
  14         133  
451              
452             =head1 DEPRECATED Methods
453              
454             B<They're DEPRECATED in 4.0>
455              
456             =head2 sort_string()
457              
458             Use C<SORT-AS> param instead of it
459              
460             =cut
461              
462             sub sort_string {
463 2     2 1 1217 my $self = shift;
464 2         351 croak "'SORT-STRING' type is DEPRECATED! Use 'SORT-AS' param instead of it.";
465             }
466              
467             =head2 label()
468              
469             Use C<LABEL> param in C<ADR> instead of it
470              
471             =cut
472              
473             sub label {
474 1     1 1 506 my $self = shift;
475 1         97 croak "'LABEL' Type is DEPRECATED in vCard4.0!";
476             }
477              
478             =head2 class(), name(), profile(), mailer()
479              
480             There is no method for these, just warn if you use them
481              
482             =cut
483              
484             sub class {
485 1     1 1 444 my $self = shift;
486 1         96 croak "'CLASS' Type is DEPRECATED from vCard4.0!";
487             }
488              
489             sub name {
490 1     1 1 12 my $self = shift;
491 1         282 croak "'NAME' Type is DEPRECATED from vCard4.0!";
492             }
493              
494             sub profile {
495 1     1 1 896 my $self = shift;
496 1         101 croak "'PROFILE' Type is DEPRECATED from vCard4.0!";
497             }
498              
499             sub mailer {
500 1     1 1 424 my $self = shift;
501 1         96 croak "'MAILER' Type is DEPRECATED from vCard4.0!";
502             }
503              
504             =head2 agent()
505              
506             Use C<AGENT> param in C<RELATED> instead of it
507              
508             =cut
509              
510             sub agent {
511 1     1 1 461 my $self = shift;
512 1         96 croak "'AGENT' Type is DEPRECATED from vCard4.0! Use AGENT param in RELATED instead of it";
513             }
514              
515             1;
516              
517             =head1 aroud UTF-8
518              
519             If you want to send precisely the vCard with UTF-8 characters to
520             the B<ALMOST> of smartphones, Use 3.0
521              
522             It seems to be TOO EARLY to use 4.0
523              
524             =head1 for under perl-5.12.5
525              
526             This module uses C<\P{ascii}> in regexp so You have to use 5.12.5 and later
527              
528             =head1 SEE ALSO
529              
530             =over
531              
532             =item
533              
534             L<RFC 6350|https://tools.ietf.org/html/rfc6350>
535              
536             =item
537              
538             L<Text::vCard::Precisely::V3>
539              
540             =item
541              
542             L<vCard on Wikipedia|https://en.wikipedia.org/wiki/VCard>
543            
544             =back
545            
546             =head1 AUTHOR
547            
548             Yuki Yoshida(L<worthmine|https://github.com/worthmine>)
549              
550             =head1 LICENSE
551              
552             This is free software; you can redistribute it and/or modify it under the same terms as Perl.