File Coverage

blib/lib/RDF/vCard/Entity.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package RDF::vCard::Entity;
2              
3 1     1   22 use 5.008;
  1         4  
  1         44  
4 1     1   6 use strict;
  1         2  
  1         38  
5              
6 1     1   2109 use JSON qw[];
  1         18951  
  1         51  
7             use RDF::TrineX::Functions
8 0           -shortcuts,
9             statement => { -as => 'rdf_statement' },
10 1     1   648 iri => { -as => 'rdf_resource' };
  0            
11              
12             sub V { return 'http://www.w3.org/2006/vcard/ns#' . shift; }
13             sub VX { return 'http://buzzword.org.uk/rdf/vcardx#' . shift; }
14             sub RDF { return 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' . shift; }
15             sub XSD { return 'http://www.w3.org/2001/XMLSchema#' . shift; }
16              
17             use namespace::clean;
18              
19             use overload '""' => \&to_string;
20             our $VERSION = '0.010';
21              
22             sub new
23             {
24             my ($class, %options) = @_;
25             $options{profile} ||= 'VCARD';
26             $options{lines} ||= [];
27             $options{components} ||= [];
28             $options{node} ||= $class->_node;
29             bless { %options }, $class;
30             }
31              
32             sub _node
33             {
34             my ($class) = @_;
35             return RDF::Trine::Node::Blank->new;
36             }
37              
38             sub profile
39             {
40             my ($self) = @_;
41             return $self->{profile};
42             }
43              
44             sub lines
45             {
46             my ($self) = @_;
47             return $self->{lines};
48             }
49              
50             sub components
51             {
52             my ($self) = @_;
53             return $self->{components};
54             }
55              
56             sub add
57             {
58             my ($self, $line) = @_;
59             push @{ $self->lines }, $line;
60             $self->_entity_order_fu($line);
61             return $self;
62             }
63              
64             sub add_component
65             {
66             my ($self, $c) = @_;
67             push @{ $self->components }, $c;
68             return $self;
69             }
70              
71             sub get
72             {
73             my ($self, $property) = @_;
74             return grep {
75             lc $_->property eq lc $property
76             } @{ $self->lines };
77             }
78              
79             sub matches
80             {
81             my ($self, $property, $regexp) = @_;
82             return grep {
83             $_->value_to_string =~ $regexp;
84             } $self->get($property);
85             }
86              
87             sub entity_order
88             {
89             my ($self) = @_;
90            
91             return $self->{property}{'sort-string'}
92             || $self->{property}{'n'}
93             || $self->{property}{'n-faked'}
94             || $self->{property}{'fn'}
95             || $self->{property}{'nickname'};
96             }
97              
98             sub _entity_order_fu
99             {
100             my ($self, $line) = @_;
101            
102             if ($line->property =~ /^(sort.string|n|fn|nickname)$/i)
103             {
104             my $x = $line->value_to_string;
105             $self->{property}{ lc $line->property } = $x if length $x;
106            
107             if (lc $line->property eq 'fn')
108             {
109             my @parts = split /\s+/, $x;
110             my $last = pop @parts;
111             unshift @parts, $last;
112             $self->{property}{'n-faked'} = join ';', @parts;
113             }
114             }
115             return $self;
116             }
117              
118             sub to_string
119             {
120             my ($self) = @_;
121            
122             my @lines = sort {
123             $a->property_order cmp $b->property_order;
124             } @{$self->lines};
125              
126             my @components = sort {
127             $a->entity_order cmp $b->entity_order;
128             } @{$self->components};
129              
130             my $str = sprintf("BEGIN:%s\r\n", $self->profile);
131             foreach my $line (@lines)
132             {
133             $str .= $line . "\r\n";
134             }
135             foreach my $component (@components)
136             {
137             $str .= $component;
138             }
139             $str .= sprintf("END:%s\r\n", $self->profile);
140            
141             return $str;
142             }
143              
144             sub node
145             {
146             my ($self) = @_;
147             return $self->{node};
148             }
149              
150             sub add_to_model
151             {
152             my ($self, $model) = @_;
153            
154             $model->add_statement(rdf_statement(
155             $self->node,
156             rdf_resource( RDF('type') ),
157             rdf_resource( V('VCard') ),
158             ));
159              
160             foreach my $line (@{ $self->lines })
161             {
162             $line->add_to_model($model, $self->node);
163             }
164            
165             return $self;
166             }
167              
168             sub to_jcard
169             {
170             my ($self, $hashref) = @_;
171             return ($hashref ? $self->TO_JSON : JSON::to_json($self));
172             }
173              
174             {
175             my @singular = qw(fn n bday tz geo sort-string uid class rev
176             anniversary birth dday death gender kind prodid sex version);
177             my @typed = qw(email tel adr label impp);
178            
179             sub TO_JSON
180             {
181             my ($self) = @_;
182             my $object = {};
183            
184             foreach my $line (@{ $self->lines })
185             {
186             my $p = lc $line->property;
187            
188             if ($p eq 'n')
189             {
190             my $o;
191             my @sp = qw(family-name given-name additional-name
192             honorific-prefix honorific-suffix);
193             for my $i (0..4)
194             {
195             if ($line->nvalue->[$i] and @{$line->nvalue->[$i]})
196             {
197             $o->{ $sp[$i] } = [ @{$line->nvalue->[$i]} ];
198             }
199             }
200             push @{$object->{n}}, $o;
201             }
202             elsif ($p eq 'org')
203             {
204             my @components = map { $_->[0] } @{$line->nvalue};
205             my $o = { 'organization-name' => shift @components };
206             $o->{'organization-unit'} = \@components;
207             push @{$object->{n}}, $o;
208             }
209             elsif ($p eq 'adr')
210             {
211             my $o;
212             while (my ($k, $v) = each %{$line->type_parameters})
213             {
214             push @{$o->{$k}}, (ref $v eq 'ARRAY' ? @$v : $v);
215             }
216             if ($o->{type})
217             {
218             $o->{type} = [ sort map {lc $_} @{ $o->{type} } ]
219             }
220             my @sp = qw(post-office-box extended-address street-address
221             locality region country-name postal-code);
222             for my $i (0..6)
223             {
224             if ($line->nvalue->[$i] and @{$line->nvalue->[$i]})
225             {
226             $o->{ $sp[$i] } = [ @{$line->nvalue->[$i]} ];
227             }
228             }
229             push @{$object->{adr}}, $o;
230             }
231             elsif ($p eq 'categories')
232             {
233             push @{$object->{categories}}, '@@TODO';
234             }
235             elsif ($p eq 'geo')
236             {
237             $object->{geo} = {
238             latitude => $line->nvalue->[0][0],
239             longitude => $line->nvalue->[1][0],
240             };
241             }
242             elsif (grep { $_ eq $p } @typed)
243             {
244             my $o = {};
245             while (my ($k, $v) = each %{$line->type_parameters})
246             {
247             push @{$o->{$k}}, (ref $v eq 'ARRAY' ? @$v : $v);
248             }
249             $o->{value} = $line->nvalue->[0][0];
250             if ($o->{type})
251             {
252             $o->{type} = [ sort map {lc $_} @{ $o->{type} } ]
253             }
254            
255             push @{ $object->{$p} }, $o;
256             }
257             elsif (grep { $_ eq $p } @singular)
258             {
259             $object->{$p} ||= $line->nvalue->[0][0];
260             }
261             else
262             {
263             push @{ $object->{$p} }, $line->nvalue->[0][0];
264             }
265             }
266            
267             return $object;
268             }
269             }
270              
271             1;
272              
273             __END__
274              
275             =head1 NAME
276              
277             RDF::vCard::Entity - represents a single vCard
278              
279             =head1 DESCRIPTION
280              
281             Instances of this class correspond to individual vCard objects, though
282             it could potentially be used as basis for other RFC 2425-based formats
283             such as iCalendar.
284              
285             =head2 Constructor
286              
287             =over
288              
289             =item * C<< new(%options) >>
290              
291             Returns a new RDF::vCard::Entity object.
292              
293             The only option worth worrying about is B<profile> which sets the
294             profile for the entity. This defaults to "VCARD".
295              
296             RDF::vCard::Entity overloads stringification, so you can do the following:
297              
298             my $vcard = RDF::vCard::Entity->new;
299             print $vcard if $vcard =~ /VCARD/i;
300              
301             =back
302              
303             =head2 Methods
304              
305             =over
306              
307             =item * C<< to_string() >>
308              
309             Formats the object according to RFC 2425 and RFC 2426.
310              
311             =item * C<< to_jcard() >>
312              
313             Formats the object according to L<http://microformats.org/wiki/jcard>.
314              
315             C<< to_jcard(1) >> will return the same data but without the JSON stringification.
316              
317             =item * C<< add_to_model($model) >>
318              
319             Given an RDF::Trine::Model, adds triples to the model for this entity.
320              
321             =item * C<< node() >>
322              
323             Returns an RDF::Trine::Node::Blank identifying this entity.
324              
325             =item * C<< entity_order() >>
326              
327             Returns a string along the lines of "Surname;Forename" useful for
328             sorting a list of entities.
329              
330             =item * C<< profile() >>
331              
332             Returns the entity type - e.g. "VCARD".
333              
334             =item * C<< lines() >>
335              
336             Returns an arrayref of L<RDF::vCard::Line> objects in the order they
337             were originally added.
338              
339             This excludes the "BEGIN:VCARD" and "END:VCARD" lines.
340              
341             =item * C<< add($line) >>
342              
343             Add a L<RDF::vCard::Line>.
344              
345             =item * C<< get($property) >>
346              
347             Returns a list of L<RDF::vCard::Line> objects for the given property.
348              
349             e.g.
350              
351             print "It has an address!\n" if ($vcard->get('ADR'));
352              
353             =item * C<< matches($property, $regexp) >>
354              
355             Checks to see if a property's value matches a regular expression.
356              
357             print "In London\n" if $vcard->matches(ADR => /London/);
358              
359             =item * C<< add_component($thing) >>
360              
361             Adds a nested entity within this one. This method is unused for vCard, but
362             is a hook for the benefit of L<RDF::iCalendar>.
363              
364             =item * C<< components >>
365              
366             Lists nested entities within this one.
367              
368             =back
369              
370             =begin private
371              
372             =item TO_JSON
373              
374             =end private
375              
376             =head1 SEE ALSO
377              
378             L<RDF::vCard>.
379              
380             =head1 AUTHOR
381              
382             Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
383              
384             =head1 COPYRIGHT
385              
386             Copyright 2011 Toby Inkster
387              
388             This library is free software; you can redistribute it and/or modify it
389             under the same terms as Perl itself.
390