File Coverage

blib/lib/Text/vCard/Precisely/V4.pm
Criterion Covered Total %
statement 69 69 100.0
branch 14 14 100.0
condition n/a
subroutine 21 21 100.0
pod 8 8 100.0
total 112 112 100.0


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