File Coverage

blib/lib/HTML/ParseBrowser.pm
Criterion Covered Total %
statement 169 183 92.3
branch 100 120 83.3
condition 27 47 57.4
subroutine 7 8 87.5
pod 2 2 100.0
total 305 360 84.7


line stmt bran cond sub pod time code
1             package HTML::ParseBrowser;
2             $HTML::ParseBrowser::VERSION = '1.15';
3 1     1   722 use 5.006;
  1         4  
4 1     1   5 use strict;
  1         2  
  1         19  
5 1     1   4 use warnings;
  1         1  
  1         26  
6              
7 1     1   5 use vars qw($AUTOLOAD);
  1         1  
  1         2676  
8              
9             my %lang =
10             (
11             'en' => 'English',
12             'de' => 'German',
13             'fr' => 'French',
14             'es' => 'Spanish',
15             'it' => 'Italian',
16             'da' => 'Danish',
17             'ja' => 'Japanese',
18             'ru' => 'Russian',
19             );
20             my $langRE = join('|', keys %lang);
21              
22             my %name_map =
23             (
24             'Mozilla' => 'Netscape',
25             'Gecko' => 'Mozilla',
26             'Netscape6' => 'Netscape',
27             'MSIE' => 'Internet Explorer',
28             'Edg' => 'Edge',
29             );
30              
31             sub new {
32 1     1 1 604 my $class = shift;
33 1         2 my $browser = {};
34 1   33     8 bless $browser, ref $class || $class;
35 1         4 $browser->Parse(shift);
36 1         5 return $browser;
37             }
38              
39             sub Parse {
40 49     49 1 142757 my $browser = shift;
41 49         94 my $ua_string = shift;
42 49         81 my $useragent = $ua_string;
43 49         74 my $version;
44 49         71 delete $browser->{$_} for keys %{$browser};
  49         458  
45 49 100       151 return undef unless $useragent;
46 48 50       112 return undef if $useragent eq '-';
47 48         97 $browser->{user_agent} = $useragent;
48 48         317 $useragent =~ s/Opera (?=\d)/Opera\//i;
49              
50 48         196 while ($useragent =~ s/\[(\w+)\]//) {
51 2   33     5 push @{$browser->{languages}}, $lang{$1} || $1;
  2         13  
52 2         6 push @{$browser->{langs}}, $1;
  2         10  
53             }
54              
55 48         307 while ($useragent =~ /\((.*?)\)/) {
56 76 100       204 $browser->{detail} .= '; ' if defined($browser->{detail});
57 76         239 $browser->{detail} .= $1;
58 76         535 $useragent =~ s/\((.*?)\)//;
59             }
60 48 100       118 if (defined($browser->{detail})) {
61 47         342 $browser->{properties} = [split /;\s+/, $browser->{detail}];
62             }
63              
64 48         467 $browser->{useragents} = [grep /\//, split /\s+/, $useragent];
65              
66 48 100       444 if ($ua_string =~ /(iPhone|iPad|iPod).*?OS\s+(\d+_\d(_\d)?)/) {
    100          
    100          
    100          
    100          
    100          
67 7         16 $browser->{name} = 'Safari';
68 7         16 $browser->{os} = $browser->{ostype} = 'iOS';
69 7         33 ($browser->{osvers} = $2) =~ s/_/./g;
70 7 50       61 if ($useragent =~ m!(Version|CriOS)/((\d+)(\.(\d+)[\.0-9]*)?)!) {
71 7 100       43 if ($1 eq 'CriOS') {
72 1         28 $browser->{name} = 'Chrome';
73             }
74 7         26 $browser->{version}->{v} = $2;
75 7         16 $browser->{version}->{major} = $3;
76 7 50 33     45 $browser->{version}->{minor} = $5 if defined($5) && $5 ne '';
77             }
78             }
79             elsif ($ua_string =~ m!\((BlackBerry|BB10).*Version/([0-9\.]+)!) {
80 2         5 my $version_string = $2;
81 2         7 $browser->{name} = $browser->{ostype} = 'BlackBerry';
82 2         6 $browser->{version}->{v} = $version_string;
83 2 50       12 if ($version_string =~ m!^([0-9]+)(\.([0-9]+).*)?!) {
84 2         6 $browser->{version}->{major} = $browser->{osvers} = $1;
85 2         8 $browser->{os} = "BlackBerry $1";
86 2 50 33     17 $browser->{version}->{minor} = $3 if defined($3) && $3 ne '';
87             }
88             }
89             elsif ($ua_string =~ m!Mozilla/5.0 \(.*?Windows.*?; rv:((\d+)\.(\d+))\) like Gecko!) {
90 1         4 $browser->{name} = 'MSIE';
91 1         5 $browser->{version}->{v} = $1;
92 1         4 $browser->{version}->{major} = $2;
93 1         3 $browser->{version}->{minor} = $3;
94             } elsif ($useragent =~ m!OPR/((\d+)\.(\d+)\.\d+\.\d+)!) {
95 1         5 $browser->{name} = 'Opera';
96 1         4 $browser->{version}->{v} = $1;
97 1         3 $browser->{version}->{major} = $2;
98 1         3 $browser->{version}->{minor} = $3;
99             } elsif ($useragent =~ m!\bVersion/((\d+)\.(\d+)\S*) Safari/!) {
100 4         12 $browser->{name} = 'Safari';
101 4         16 $browser->{version}->{v} = $1;
102 4         13 $browser->{version}->{major} = $2;
103 4         14 $browser->{version}->{minor} = $3;
104             } elsif ($useragent =~ m!Opera/.*Version/((\d+)\.(\d+)\S*)$!) {
105 2         6 $browser->{name} = 'Opera';
106 2         8 $browser->{version}->{v} = $1;
107 2         7 $browser->{version}->{major} = $2;
108 2         6 $browser->{version}->{minor} = $3;
109             } else {
110 31         50 my $seenchrome = 0;
111 31         48 for (@{$browser->{useragents}}) {
  31         83  
112 79         229 my ($br, $ver) = split /\//;
113 79 50       185 $br = 'Chrome' if $br eq 'CriOS';
114 79 100 66     199 if ($br ne 'Safari' || not $seenchrome) {
115 73         122 $browser->{name} = $br;
116 73         140 $browser->{version}->{v} = $ver;
117 73 100       262 if ($ver =~ m!^v?(\d+)\.(\d+)!) {
118 64         222 ($browser->{version}->{major}, $browser->{version}->{minor}) = ($1, $2);
119             }
120             }
121 79 100       199 $seenchrome = 1 if lc($br) eq 'chrome';
122 79 100       159 last if lc($br) eq 'iron';
123 78 100       142 last if lc($br) eq 'lynx';
124             # last if lc($br) eq 'chrome';
125 77 100       175 last if lc($br) eq 'opera';
126             }
127             }
128              
129 48         81 for (@{$browser->{properties}}) {
  48         101  
130 211 100       571 /compatible/i and next;
131              
132 202 100 100     883 unless (defined($browser->{name}) && (lc($browser->{name}) eq 'webtv' || lc($browser->{name}) eq 'opera')) {
      66        
133 183 100       374 /^MSIE (.*)$/ and do {
134 6         14 $browser->{name} = 'MSIE';
135 6         13 $browser->{version}->{v} = $1;
136             ($browser->{version}->{major},
137 6         25 $browser->{version}->{minor}) = split /\./, $1, 2;
138             };
139             }
140              
141 202 50       371 m!^Edge/(([0-9]+)\.([0-9]+))! and do {
142 0         0 $browser->{name} = 'Edge';
143 0         0 $browser->{version}->{v} = $1;
144 0         0 $browser->{version}->{major} = $2;
145 0         0 $browser->{version}->{minor} = $3;
146             };
147              
148 202 100       357 if (m!^AOL ([0-9].*)!) {
149 1         3 $browser->{name} = 'AOL';
150 1         4 $browser->{version}->{v} = $1;
151 1         6 ($browser->{version}->{major}, $browser->{version}->{minor}) = split /\./, $browser->{version}->{v};
152             }
153              
154 202 100       351 /^Konqueror\/([-0-9.a-z]+)/ and do {
155 1         3 $browser->{name} = 'Konqueror';
156 1         10 $browser->{version}->{v} = $1;
157 1         10 ($browser->{version}->{major}, $browser->{version}->{minor}) = split /\./, $browser->{version}->{v};
158             };
159              
160 202 0 33     389 /\bCamino\/([0-9.]+)/ and do {
161 0         0 $browser->{name} = 'Camino';
162 0         0 $browser->{version}->{v} = $1;
163 0         0 ($browser->{version}->{major}, $browser->{version}->{minor}) = split /\./, $browser->{version}->{v}, 2;
164             } and last;
165              
166 202 100       345 if (m!^Opera Mini/([0-9.]+)!) {
167 1         3 $browser->{name} = 'Opera Mini';
168 1         4 $browser->{version}->{v} = $1;
169 1         7 ($browser->{version}->{major}, $browser->{version}->{minor}) = split /\./, $browser->{version}->{v};
170             }
171              
172 202 100       365 if (/^Win/) {
173 26         63 $browser->{os} = $_;
174 26 100       106 $browser->{ostype} = 'Windows' . (/phone/i ? ' Phone' : '');
175 26 100 66     185 if (/Windows NT\s*((\d+)(\.\d+)?)/ || /^WinNT((\d+)(\.\d+)?)/) {
    100          
    50          
176 19         44 $browser->{ostype} = 'Windows NT';
177 19         34 $version = $1;
178 19 100 66     138 if ($version >= 10) {
    100          
    100          
    100          
    50          
    100          
    50          
    0          
179 2         7 $browser->{osvers} = '10';
180             }
181             elsif ($version >= 6.3 && $version < 7) {
182 1         4 $browser->{osvers} = '8.1';
183             } elsif ($version >= 6.2) {
184 1         4 $browser->{osvers} = '8';
185             } elsif ($version >= 6.1) {
186 5         13 $browser->{osvers} = '7';
187             } elsif ($version >= 6.06) {
188 0         0 $browser->{osvers} = 'Server 2008';
189             } elsif ($version >= 6.0) {
190 5         13 $browser->{osvers} = 'Vista';
191             } elsif ($version >= 5.1) {
192 5         13 $browser->{osvers} = 'XP';
193             } elsif ($version >= 5.0) {
194 0         0 $browser->{osvers} = '2000';
195             } else {
196 0         0 $browser->{osvers} = $version;
197             }
198             }
199             elsif (/Windows (?:Phone )?(\d+(\.\d+)?)/) {
200 1 50       6 $browser->{osvers} = $1 if !$browser->{osvers};
201             } elsif (/Win(\w\w)/i) {
202 6 100       25 $browser->{osvers} = $1 if !$browser->{osvers};
203             }
204             }
205              
206 202 100       366 if (/^Mac/) {
207 7         25 $browser->{os} = $_;
208 7         20 $browser->{ostype} = 'Macintosh';
209 7         33 (undef, $browser->{osvers}) = split /[ _]/, $_, 2;
210             }
211              
212 202 50       349 if (/^PPC$/) {
213 0         0 $browser->{osarc} = 'PPC';
214             }
215              
216             # TODO: parsing of version and osarc doesn't always get it right. See Danish Opera test
217 202 100 100     586 if (/Android\s([\.0-9]+)/ && lc($browser->{name}) ne 'edge') {
    100          
218 3         8 $browser->{os} = 'Android';
219 3         6 $browser->{ostype} = 'Linux';
220 3         8 $browser->{osvers} = $1;
221             } elsif (/^Linux/) {
222 8         17 my $lstr = $_;
223 8         19 $browser->{os} = 'Linux';
224 8         16 $browser->{ostype} = 'Linux';
225 8 100       57 if ($lstr =~ s/(i386|mips|amd64|sparc64|ppc|i686|i586|armv51|x86|x86-64|x86_64|ppc64|x64|x64_64)\b//) {
226 4         14 $browser->{osarc} = $1;
227             }
228 8 50       26 if ($lstr =~ / (\d+\.\S+)/) {
229 0         0 $browser->{osvers} = $1;
230             }
231             }
232              
233 202 100       476 if (/^(SunOS|Solaris)/i) {
234 1         4 $browser->{os} = $_;
235 1         2 $browser->{ostype} = 'Solaris';
236 1 50       11 if (/(sun4[a-z]|i86pc)/) {
237 1         3 $browser->{osarc} = $1;
238             }
239 1 50       8 if (/^SunOS\s*([0-9\.]+)/) {
240 1         3 $browser->{osvers} = $1;
241             }
242             }
243              
244 202 100 100     1222 if (/^($langRE)-/ || /^($langRE)$/) {
245 21         48 my $langCode = $1;
246 21         35 push(@{$browser->{languages}}, $lang{$langCode});
  21         82  
247 21         34 push(@{$browser->{langs}}, $langCode);
  21         62  
248             }
249             }
250              
251 48 100 66     206 if (defined($browser->{name}) && exists $name_map{ $browser->{name} }) {
252 8         22 $browser->{name} = $name_map{ $browser->{name} };
253             }
254              
255 48   33     104 $browser->{name} ||= $useragent;
256              
257 48 100       95 if ($browser->{name} eq 'Konqueror') {
258 1   50     4 $browser->{ostype} ||= 'Linux';
259             }
260              
261 48         76 my %langs_in;
262              
263 48         67 for (@{$browser->{langs}}) {
  48         107  
264 23         73 $langs_in{$_}++;
265             }
266              
267 48 100       154 if (int(keys %langs_in) > 0) {
268 23         96 ($browser->{lang}) = sort {$langs_in{$a} <=> $langs_in{$b}} keys %langs_in;
  0         0  
269 23   33     84 $browser->{language} = $lang{$browser->{lang}} || $browser->{lang};
270             # delete $browser->{language} unless $browser->{language};
271             }
272 48         138 return $browser;
273             }
274              
275       0     sub DESTROY {
276             }
277              
278             sub AUTOLOAD {
279 257     257   936 my $self = shift;
280 257         737 my $method = lc($AUTOLOAD);
281 257         1512 $method =~ s/^.*\:\://;
282              
283 257 100       846 if (exists($self->{$method})) {
    50          
284 176         713 return $self->{$method};
285             } elsif (exists($self->{version}->{$method})) {
286 81         338 return $self->{version}->{$method};
287             }
288              
289 0           return undef;
290             }
291              
292             __END__