File Coverage

blib/lib/Email/Extractor/Utils.pm
Criterion Covered Total %
statement 124 128 96.8
branch 30 40 75.0
condition 6 12 50.0
subroutine 31 31 100.0
pod 14 14 100.0
total 205 225 91.1


line stmt bran cond sub pod time code
1             #ABSTRACT: Set of functions that can be useful when building web crawlers
2              
3              
4             package Email::Extractor::Utils;
5             $Email::Extractor::Utils::VERSION = '0.03';
6 2     2   76183 use strict;
  2         11  
  2         49  
7 2     2   10 use warnings;
  2         3  
  2         49  
8 2     2   10 use feature 'say';
  2         4  
  2         194  
9              
10 2     2   14 use Cwd;
  2         4  
  2         113  
11 2     2   11 use Carp;
  2         3  
  2         150  
12 2     2   784 use URI::URL;
  2         14817  
  2         84  
13 2     2   13 use File::Spec;
  2         2  
  2         51  
14 2     2   864 use File::Slurp qw(read_file);
  2         14089  
  2         142  
15 2     2   14 use File::Basename; # fileparse
  2         2  
  2         179  
16 2     2   889 use Regexp::Common qw /URI/;
  2         3955  
  2         11  
17 2     2   39367 use LWP::UserAgent;
  2         65175  
  2         68  
18 2     2   871 use LWPx::TimedHTTP qw(:autoinstall);
  2         45335  
  2         11  
19 2     2   565 use Mojo::DOM;
  2         128352  
  2         3116  
20              
21             require Exporter;
22              
23             our @ISA = qw(Exporter);
24             our @EXPORT_OK = qw(
25             looks_like_url
26             looks_like_rel_link
27             looks_like_file
28             get_file_uri
29             load_addr_to_str
30             absolutize_links_array
31             find_all_links
32             find_links_by_text
33             drop_asset_links
34             drop_anchor_links
35             remove_query_params
36             remove_external_links
37             isin
38             );
39             our %EXPORT_TAGS = ( 'ALL' => [@EXPORT_OK] );
40              
41             our $Verbose = 0 unless defined $Verbose;
42              
43              
44             our $Assets = [
45             'css', 'less', 'js', 'jpg', 'JPG', 'jpeg', 'JPEG', 'png',
46             'PNG', 'svg', 'doc', 'docx', 'ppt', 'odt', 'rtf', 'ppt'
47             ];
48              
49             # Loads url and measure timings
50              
51             sub _load_url_verbose {
52 1     1   4 my $addr = shift;
53 1         13 my $ua = LWP::UserAgent->new;
54 1         3363 my $resp = $ua->get($addr); # HTTP::Response
55              
56 1         110 my @headers = qw/
57             Client-Request-Dns-Time
58             Client-Request-Connect-Time
59             Client-Request-Transmit-Time
60             Client-Response-Server-Time
61             Client-Response-Receive-Time
62             /;
63              
64 1         3 my $msg;
65 1         4 for my $h (@headers) {
66 5         338 my $prm = ( split( '-', $h ) )[2];
67 5 50       31 $msg .= ' ' . $prm . ' : ' . $resp->header($h) . "\n"
68             if defined $resp->header($h);
69             }
70              
71 1 50       72 say $msg if $Verbose;
72 1 0 33     8 say $resp->status_line if ( $resp->is_error && $Verbose );
73 1         49 return $resp->content;
74             }
75              
76              
77             sub load_addr_to_str {
78 5     5 1 2558 my $addr = shift;
79              
80 5         13 eval {
81              
82 5 100       19 if ( looks_like_url($addr) ) {
83              
84 1 50       5 say "$addr: is url" if $Verbose;
85 1         5 _load_url_verbose($addr);
86              
87             }
88             else {
89              
90 4 50       15 say "$addr: is file" if $Verbose;
91 4         13 my $file_uri = get_file_uri($addr);
92              
93 4 50       19 if ( looks_like_file($file_uri) ) {
94 4         18 return read_file( get_abs_path($addr) );
95              
96             }
97             else {
98 0         0 die "No such file: " . $addr . " or it is not file or http uri";
99             }
100              
101             }
102              
103             };
104             }
105              
106              
107             sub get_abs_path {
108 4     4 1 10 my $filename = shift;
109 4         91 return File::Spec->catfile( getcwd(), $filename );
110             }
111              
112              
113             sub get_file_uri {
114 4     4 1 11 my $filename = shift;
115 4         136 return 'file://' . File::Spec->catfile( getcwd(), $filename );
116             }
117              
118              
119             sub looks_like_url {
120 22     22 1 960 my $string = shift;
121 22         109 my $regexp = qr($RE{URI}{HTTP}{-scheme=>qr/https?/}{-keep});
122              
123 22 100       3762 if ( $string =~ $regexp ) {
124              
125             # return $7 if defined $7;
126 13         100 return $1;
127             }
128             else {
129 9         39 return 0;
130             }
131             }
132              
133              
134             sub looks_like_rel_link {
135 8     8 1 2039 my $link = shift;
136 8 100       13 return 0 if ( looks_like_url($link) );
137 4         12 return 1;
138             }
139              
140              
141             sub looks_like_file {
142 14     14 1 1690 my $string = shift;
143 14 100       52 if ( $string =~ qr($RE{URI}{file}) ) {
144 5         774 return 1;
145             }
146             else {
147 9         717 return 0;
148             }
149             }
150              
151              
152             sub absolutize_links_array {
153 3     3 1 2579 my ( $links_arr, $dname ) = @_;
154              
155 3 100 66     65 confess 'No valid dname in absolutize_links_array()'
156             unless defined $dname && length $dname;
157              
158 2         3 my @res;
159              
160 2         5 for my $l (@$links_arr) {
161 6 100       39 if ( looks_like_rel_link($l) ) {
162 3         9 $l = url( $l, $dname )->abs->as_string;
163             }
164 6         6977 push @res, $l;
165             }
166              
167 2         23 return \@res;
168             }
169              
170              
171             sub remove_external_links {
172 3     3 1 2841 my ( $links_arr, $only_dname ) = @_;
173              
174 3 100 66     29 confess 'No valid dname in remove_external_links()'
175             unless defined $only_dname && length $only_dname;
176              
177             my @res =
178 2 100       4 grep { ( $_ =~ /^$only_dname/ ) || looks_like_file( 'file://' . $_ ) }
  10         68  
179             @$links_arr;
180              
181             # looks_like_file('file://'.$_) = looks_like_relative_link
182 2         10 return \@res;
183             }
184              
185              
186             sub drop_asset_links {
187 7     7 1 1923 my $links = shift;
188              
189 7         13 $links = remove_query_params($links);
190              
191 7         8 my @res;
192              
193 7         15 for my $link (@$links) {
194 11         850 my ( $filename, $dirs, $suffix ) = fileparse( $link, @$Assets );
195 11 100       44 push( @res, $link ) if ( $suffix eq '' );
196             }
197              
198 7         27 return \@res;
199             }
200              
201              
202             sub drop_anchor_links {
203 7     7 1 2212 my $links_arr = shift;
204 7         12 my @res = grep { $_ !~ /^#/ } @$links_arr;
  8         25  
205 7         17 return \@res;
206             }
207              
208              
209             sub remove_query_params {
210 14     14 1 1845 my $links = shift;
211              
212 14         18 my @res;
213              
214 14         22 for my $link (@$links) {
215 22         60 my $uri = URI->new($link);
216 22 50       14909 if ( $uri->query ) {
217 0         0 my $l = length($link) - length( $uri->query ) - 1;
218 0         0 my $new_str = substr $link, 0, $l;
219 0         0 push( @res, $new_str );
220             }
221             else {
222 22         357 push( @res, $link );
223             }
224             }
225              
226 14         35 return \@res;
227             }
228              
229              
230             sub find_all_links {
231 7     7 1 2035 my $html = shift;
232 7         30 my $dom = Mojo::DOM->new($html);
233 7         1778 return $dom->find('a')->map( attr => 'href' )->to_array;
234             }
235              
236              
237             sub find_links_by_text {
238 10     10 1 3747 my ( $html, $a_text, $upper_lower_case_flag ) = @_;
239 10         39 my $dom = Mojo::DOM->new($html); # Mojo::Collection of Mojo::DOM
240              
241             # return $dom->find('a')->grep('text' => $a_text)->map(attr => 'href')->to_array;
242 9     9   834 return $dom->find('a')->grep( sub { $_->text eq $a_text } )
243 10 100       1820 ->map( attr => 'href' )->to_array
244             if !defined $upper_lower_case_flag;
245              
246             # TO-DO: fix lc/uc issue is case of non-ascii characters
247              
248             # warn Dumper $crawler->extract_contact_links('Контакты');
249             # warn Dumper $crawler->extract_contact_links('контакты');
250             # warn Dumper $crawler->extract_contact_links('КОНТАКТЫ');
251              
252             # https://stackoverflow.com/questions/3399129/compare-two-strings-regardless-of-case-size-in-perl
253 2     2   27 use utf8;
  2         4  
  2         14  
254 6 50       17 if ($upper_lower_case_flag) {
255             return $dom->find('a')->grep(
256             sub {
257 6     6   1022 lc $_->text eq lc $a_text;
258             }
259 6         17 )->map( attr => 'href' )->to_array;
260             }
261              
262             }
263              
264              
265             sub isin($$) {
266 3     3 1 9 my ( $val, $array_ref ) = @_;
267              
268 3 50 33     22 return 0 unless $array_ref && defined $val;
269 3         26 for my $v (@$array_ref) {
270 3 100       15 return 1 if $v eq $val;
271             }
272              
273 2         11 return 0;
274             }
275              
276             1;
277              
278             __END__