File Coverage

blib/lib/Data/Microformat/hCard.pm
Criterion Covered Total %
statement 161 174 92.5
branch 70 80 87.5
condition 36 53 67.9
subroutine 15 15 100.0
pod 5 5 100.0
total 287 327 87.7


line stmt bran cond sub pod time code
1             package Data::Microformat::hCard;
2 13     13   5826 use base qw(Data::Microformat);
  13         23  
  13         5354  
3              
4 13     13   141 use strict;
  13         24  
  13         440  
5 13     13   66 use warnings;
  13         29  
  13         616  
6              
7             our $VERSION = "0.04";
8              
9 13     13   7742 use Data::Microformat::adr;
  13         29  
  13         344  
10 13     13   6297 use Data::Microformat::geo;
  13         27  
  13         283  
11 13     13   6672 use Data::Microformat::hCard::type;
  13         27  
  13         333  
12 13     13   6837 use Data::Microformat::hCard::name;
  13         39  
  13         279  
13 13     13   6943 use Data::Microformat::hCard::organization;
  13         28  
  13         30700  
14              
15 40     40 1 110 sub class_name { "vcard" }
16 44     44 1 272 sub singular_fields { qw(fn n bday tz geo sort_string uid class) }
17 44     44 1 272 sub plural_fields { qw(adr agent category email key label logo mailer nickname note org photo rev role sound tel title url) }
18              
19             sub is_representative
20             {
21 7     7 1 12 my $self = shift;
22 7 100       15 if ($self->{_representative})
23             {
24 3         11 return 1;
25             }
26             else
27             {
28 4         15 return 0;
29             }
30             }
31              
32             sub from_tree
33             {
34 17     17 1 39 my $class = shift;
35 17         34 my $tree = shift;
36              
37 17         31 my $representative_url = shift;
38 17 100       68 if ($representative_url)
39             {
40 4         11 $representative_url = _normalize_url($representative_url);
41             }
42            
43              
44 17         41 my $rel_me = "";
45            
46 17         26 my @all_cards;
47 17         171 my @cards = $tree->look_down("class", qr/vcard/);
48            
49             #First: build the list of things we recognize;
50 17         3151 my $recognized_regex = "(";
51 17         152 foreach my $field ( ( Data::Microformat::adr->singular_fields,
52             Data::Microformat::adr->plural_fields,
53             Data::Microformat::adr->class_name,
54             Data::Microformat::geo->singular_fields,
55             Data::Microformat::geo->plural_fields,
56             Data::Microformat::geo->class_name,
57             Data::Microformat::hCard::name->singular_fields,
58             Data::Microformat::hCard::name->plural_fields,
59             Data::Microformat::hCard::name->class_name,
60             Data::Microformat::hCard::organization->singular_fields,
61             Data::Microformat::hCard::organization->plural_fields,
62             Data::Microformat::hCard::organization->class_name,
63             Data::Microformat::hCard::type->singular_fields,
64             Data::Microformat::hCard::type->plural_fields,
65             Data::Microformat::hCard::type->class_name,
66             Data::Microformat::hCard->singular_fields,
67             Data::Microformat::hCard->plural_fields,
68             Data::Microformat::hCard->class_name, ))
69             {
70 884         1023 $field =~ s/\_/\-/;
71 884         1200 $recognized_regex .= '(^|\s)'.$field.'($|\s)|';
72             }
73 17         96 chop($recognized_regex);
74 17         29 $recognized_regex .= ")";
75            
76              
77 17         39 foreach my $card_tree (@cards)
78             {
79             # Walk the tree looking for useless bits
80             # Where class is undefined
81 23         84 my @useless = $card_tree->look_down("class", undef);
82 23         1986 foreach my $element (@useless)
83             {
84 7         58 my @kids = $element->detach_content;
85 7         85 my $parent = $element->detach;
86 7 100       112 if (@kids)
87             {
88 5         16 $parent->push_content(@kids);
89             }
90 7         139 $element->delete;
91             }
92            
93             @useless = $card_tree->look_down(sub{
94 230 100   230   11232 if ($_[0]->attr('class'))
95             {
96 229         2126 return $_[0]->attr('class') !~ m/$recognized_regex/;
97             }
98             else
99             {
100 1         11 return 1;
101 23         183 }});
102            
103 23         676 foreach my $element (@useless)
104             {
105 1         4 my @kids = $element->detach_content;
106 1         7 my $parent = $element->detach;
107 1 50       7 if (@kids)
108             {
109 0         0 $parent->push_content(@kids);
110             }
111 1         3 $element->delete;
112             }
113            
114 23         202 my $card = Data::Microformat::hCard->new;
115 23         116 $card->{_no_dupe_keys} = 1;
116              
117 23         92 my @bits = $card_tree->content_list;
118            
119 23         191 foreach my $bit (@bits)
120             {
121 155 100       367 if (ref($bit) eq "HTML::Element")
122             {
123 130         127 my $nested_goes_here;
124 130         323 my $hcard_class = $bit->attr('class');
125 130 50       1182 next unless $hcard_class;
126            
127             #Check for nested vcard.
128 130 100       292 if ($hcard_class =~ m/vcard/)
129             {
130             #We have a nested class in here. Mark where it needs to go.
131 1         1 my $temp_hcard_class = $hcard_class;
132 1         4 $temp_hcard_class =~ s/vcard//;
133 1         5 $temp_hcard_class = $class->_trim($temp_hcard_class);
134 1         4 my @types = split(" ", $temp_hcard_class);
135 1 50       5 if (scalar @types > 0)
136             {
137 1         2 $nested_goes_here = $types[0];
138 1         12 $hcard_class =~ s/$nested_goes_here//;
139 1         4 $hcard_class = $class->_trim($hcard_class);
140             #We do this so that if the type is, for instance,
141             # "agent vcard," that we just put the vcard in
142             # agent, and not anywhere else.
143             # vcard *MUST* have another class, otherwise we''ll
144             # discard it.
145             }
146             }
147 130         298 my @types = split(" ", $hcard_class);
148 130         195 foreach my $type (@types)
149             {
150 143         201 $type =~ s/\-/\_/;
151 143         496 $type = $class->_trim($type);
152            
153 143         149 my $data;
154 143         436 my @cons = $bit->content_list;
155            
156 143 100       1127 unless (scalar @cons > 1)
157             {
158 114         327 $data = $class->_trim($cons[0]);
159 114 100 66     336 if ($bit->tag eq "abbr" && $bit->attr('title'))
    100 66        
    100 66        
    100          
160             {
161 9         209 $data = $class->_trim($bit->attr('title'));
162             }
163             elsif ($bit->tag eq "a" && $bit->attr('href'))
164             {
165 38 100       940 if ($type =~ m/(photo|logo|agent|sound|url)/)
166             {
167 28         74 $data = $class->_trim($class->_url_decode($bit->attr('href')));
168 28 100 66     77 if ($bit->attr('rel') && $bit->attr('rel') eq "me")
169             {
170 1         23 $rel_me = $data;
171             }
172             }
173             }
174             elsif ($bit->tag eq "object" && $bit->attr('data'))
175             {
176 2 100       57 if ($type =~ m/(photo|logo|agent|sound|url)/)
177             {
178 1         3 $data = $class->_trim($bit->attr('data'));
179             }
180             }
181             elsif ($bit->tag eq "img")
182             {
183 2 100 66     52 if ($type =~ m/(photo|logo|agent|sound|url)/ && $bit->attr('src'))
    50          
184             {
185 1         13 $data = $class->_trim($bit->attr('src'));
186             }
187             elsif ($bit->attr('alt'))
188             {
189 1         9 $data = $class->_trim($bit->attr('alt'));
190             }
191             }
192             }
193            
194 143 100       2452 if ($type eq "vcard")
    100          
    100          
    100          
    100          
    100          
    100          
195             {
196 1         27 my $nestedcard = $class->from_tree($bit);
197 1 50       3 if ($nested_goes_here)
198             {
199 1         9 $card->$nested_goes_here($nestedcard);
200             }
201             }
202             elsif ($type eq "tel")
203             {
204 10         61 my $tel = Data::Microformat::hCard::type->from_tree($bit);
205 10         66 $card->tel($tel);
206             }
207             elsif ($type eq "email")
208             {
209 7         34 my $email = Data::Microformat::hCard::type->from_tree($bit);
210 7         50 $card->email($email);
211             }
212             elsif ($type eq "n")
213             {
214 2         12 my $name = Data::Microformat::hCard::name->from_tree($bit);
215 2         13 $card->n($name);
216             }
217             elsif ($type eq "adr")
218             {
219 7         61 my $adr = Data::Microformat::adr->from_tree($bit);
220 7         47 $card->adr($adr);
221             }
222             elsif ($type eq "geo")
223             {
224 4         28 my $geo = Data::Microformat::geo->from_tree($bit);
225 4         29 $card->geo($geo);
226             }
227             elsif ($type eq "org")
228             {
229 6         29 my $org = Data::Microformat::hCard::organization->from_tree($bit);
230 6         40 $card->org($org);
231             }
232             else
233             {
234 106         311 eval { $card->$type($data); };
  106         741  
235 106 50       478 if ($@)
236             {
237 0         0 print STDERR "Didn't recognize type $type.\n";
238             }
239             }
240             }
241             }
242             }
243            
244              
245            
246             # Check: Implied N Optimization?
247 23 100 100     146 if (!$card->n && $card->fn && (!$card->org || (!$card->fn eq $card->org)))
      66        
      66        
248             {
249 7         50 my $n = Data::Microformat::hCard::name->new;
250 7         36 my @arr = split(" ", $card->fn);
251 7 50       30 if ($arr[1])
252             {
253 7         17 $arr[1] =~ s/\.//;
254             }
255 7 50 33     42 if ($arr[0] =~ m/\,/ && length $arr[1] == 1)
256             {
257 0         0 $arr[0] =~ s/\,//;
258 0         0 $n->family_name($class->_trim($arr[0]));
259 0         0 $n->given_name($class->_trim($arr[1]));
260             }
261             else
262             {
263 7         30 $n->family_name($class->_trim($arr[1]));
264 7         35 $n->given_name($class->_trim($arr[0]));
265             }
266 7         41 $card->n($n);
267             }
268            
269             # Check: Org?
270 23 100 100     125 if (($card->org) && (($card->fn || "") eq $card->org->organization_name))
      100        
271             {
272 2         18 my $name = Data::Microformat::hCard::name->new;
273 2         25 $name->family_name(" ");
274 2         15 $name->given_name(" ");
275 2         15 $name->additional_name(" ");
276 2         14 $name->honorific_prefix(" ");
277 2         24 $name->honorific_suffix(" ");
278 2         15 $card->n($name);
279             }
280            
281             # Check: Nickname Optimization?
282 23 100       122 if ($card->fn)
283             {
284 10         52 my @words = split(" ", $card->fn);
285 10 0 66     73 if (($card->org && (!$card->org->organization_name eq $card->fn)) && (!$card->n) && (scalar @words == 1))
      33        
      33        
286             {
287 0         0 $card->nickname($card->fn);
288 0         0 my $name = Data::Microformat::hCard::name->new;
289 0         0 $name->family_name("");
290 0         0 $name->given_name("");
291 0         0 $name->additional_name("");
292 0         0 $name->honorific_prefix("");
293 0         0 $name->honorific_suffix("");
294 0         0 $card->n($name);
295             }
296             }
297 23         56 $card->{_no_dupe_keys} = 0;
298 23         101 push (@all_cards, $card);
299             }
300            
301 17         95 $tree->delete;
302            
303             # Check: Representative hCard?
304 17 100       3874 if ($representative_url)
305             {
306 4 100       7 if (scalar @all_cards == 1)
307             {
308 1         3 $all_cards[0]->{_representative} = 1;
309             }
310             else
311             {
312 3         4 my $found_one = 0;
313 3         5 foreach my $card (@all_cards)
314             {
315 5 100 66     19 if ($card->url && $card->uid && $card->url eq $card->uid && _normalize_url($card->url) eq _normalize_url($representative_url))
      100        
      66        
316             {
317 1         2 $card->{_representative} = 1;
318 1         2 $found_one = 1;
319 1         1 last;
320             }
321             }
322 3 100       8 if (!$found_one)
323             {
324 2         5 foreach my $card (@all_cards)
325             {
326 4 100 66     14 if ($card->url && $card->url eq $rel_me)
327             {
328 1         2 $card->{_representative} = 1;
329 1         2 last;
330             }
331             }
332             }
333             }
334             }
335            
336 17 100       63 if (wantarray)
337             {
338 4         21 return @all_cards;
339             }
340             else
341             {
342 13         88 return $all_cards[0];
343             }
344             }
345              
346             sub _normalize_url
347             {
348 6     6   8 my $url = shift;
349 6         13 $url =~ s/[A-Z]/[a-z]/;
350 6         9 $url =~ s/\/$//;
351 6         15 return $url;
352             }
353              
354             1;
355              
356             __END__