File Coverage

blib/lib/Aion/Format/Url.pm
Criterion Covered Total %
statement 122 138 88.4
branch 60 80 75.0
condition 11 16 68.7
subroutine 18 19 94.7
pod 6 6 100.0
total 217 259 83.7


line stmt bran cond sub pod time code
1             package Aion::Format::Url;
2              
3 1     1   121058 use common::sense;
  1         1  
  1         6  
4              
5 1     1   71 use List::Util qw//;
  1         2  
  1         12  
6 1     1   4 use Encode qw//;
  1         1  
  1         20  
7              
8 1     1   3 use Exporter qw/import/;
  1         2  
  1         201  
9             our @EXPORT = our @EXPORT_OK = grep {
10             ref \$Aion::Format::Url::{$_} eq "GLOB"
11             && *{$Aion::Format::Url::{$_}}{CODE} && !/^(_|(NaN|import)\z)/n
12             } keys %Aion::Format::Url::;
13              
14              
15             #@category escape url
16              
17 1     1   5 use constant UNSAFE_RFC3986 => qr/[^A-Za-z0-9\-\._~]/;
  1         1  
  1         88  
18              
19             # Эскейпит значение
20             sub to_url_param(;$) {
21 20 100   20 1 6387 my ($param) = @_ == 0? $_: @_;
22 1     1   4 use bytes;
  1         1  
  1         5  
23 20 100       24 $param =~ s/${\ UNSAFE_RFC3986}/$& eq " "? "+": sprintf "%%%02X", ord $&/ge;
  9         35  
  20         62  
24 20         52 $param
25             }
26              
27             # Преобразует в формат url-параметров
28             sub to_url_params(;$) {
29 3 100   3 1 147551 my ($param) = @_ == 0? $_: @_;
30              
31 3         4 my @R;
32 3         4 my @S = [$param];
33 3         8 while(@S) {
34 22         21 my $u = pop @S;
35 22         28 my ($x, $key) = @$u;
36            
37 22 100       44 if(ref $x eq "HASH") {
    100          
    100          
    100          
38             push @S, defined($key)
39 2         4 ? (map [$x->{$_}, "$key\[${\to_url_param}]"], sort keys %$x)
40 5 100       21 : (map [$x->{$_}, to_url_param], sort keys %$x)
41             ;
42             }
43             elsif(ref $x eq "ARRAY") {
44 4         5 my $i = '';
45 4         7 push @S, map [$_, "$key\[${\($i++)}]"], @$x;
  10         38  
46             }
47             elsif(!defined $x) {}
48 5         11 elsif($x eq 1) { unshift @R, $key }
49             else {
50 7         9 unshift @R, join "=", $key, to_url_param $x;
51             }
52             }
53            
54 3         13 join "&", @R
55             }
56              
57             # Определяет кодировку. В koi8-r и в cp1251 большие и малые буквы как бы поменялись местами, поэтому у правильной кодировки вес будет больше
58             sub _bohemy {
59 0     0   0 my ($s) = @_;
60 0         0 my $c = 0;
61 0         0 while($s =~ /[а-яё]+/gi) {
62 0         0 my $x = $&;
63 0 0       0 if($x =~ /^[А-ЯЁа-яё][а-яё]*$/) { $c += length $x } else { $c -= length $x }
  0         0  
  0         0  
64             }
65             $c
66 0         0 }
67              
68             sub from_url_param(;$) {
69 10 100   10 1 23 my ($param) = @_ == 0? $_: @_;
70              
71 10 100       19 utf8::encode($param) if utf8::is_utf8($param);
72              
73             {
74 1     1   1013 no utf8;
  1         210  
  1         5  
  10         11  
75 1     1   26 use bytes;
  1         1  
  1         3  
76 10         13 $param =~ tr!\+! !;
77 10         12 $param =~ s!%([\da-f]{2})! chr hex $1 !iage;
  3         8  
78             }
79              
80 10         10 eval { $param = Encode::decode_utf8($param, Encode::FB_CROAK) };
  10         43  
81              
82 10 50       55 if($@) { # видимо тут кодировка cp1251 или koi8-r
83 0         0 my $cp = Encode::decode('cp1251', $param);
84 0         0 my $koi = Encode::decode('koi8-r', $param);
85             # выбираем перекодировку в которой меньше больших букв внутри слова
86 0 0       0 $param = _bohemy($koi) > _bohemy($cp)? $koi: $cp;
87             }
88              
89             $param
90 10         35 }
91              
92             sub _set_url_param(@) {
93 6     6   9 my ($x, $val) = @_;
94 6 50       10 if(ref $$x eq "ARRAY") { push @$$x, $val }
  0 50       0  
95 0         0 elsif(ref $$x eq "HASH") { $$x = [$$x, $val] }
96 6         10 else { $$x = $val }
97             }
98              
99             sub from_url_params(;$) {
100 1 50   1 1 2538 my ($params) = @_ == 0? $_: @_;
101              
102 1         3 my %param;
103             my $x;
104 1         0 my $was_val;
105              
106 1         7 while($params =~ /\G (?:
107             (?:^|&) (? [^&=\[\]]* )
108             | \[ (? [^\[\]]* ) \]
109             | (?: = (? [^&]*) )
110             | .
111             ) /gsx) {
112              
113 15 100       49 if(exists $+{key1}) {
    100          
    50          
114 5 100       10 _set_url_param $x, 1 unless $was_val;
115 5         5 $was_val = 0;
116 5         10 $x = \$param{from_url_param $+{key1}};
117             }
118             elsif(exists $+{key}) {
119             # Добрасываем в массив
120 7 100 100     47 if($+{key} eq '' || int $+{key} eq $+{key}) {
121 6 50 66     14 $$x = [$$x] if ref $$x ne 'ARRAY' && defined $$x;
122 6         28 $x = \$$x->[$+{key}];
123             }
124             else {
125             # Добавляем в хеш
126 1         2 my $key = from_url_param $+{key};
127 1 50 33     3 $$x = {$key => $$x} if ref $$x ne 'HASH' && defined $$x;
128 1         5 $x = \$$x->{$key};
129             }
130             }
131             elsif(exists $+{val}) {
132 3         6 _set_url_param $x, from_url_param $+{val};
133 3         11 $was_val = 1;
134             }
135            
136             }
137            
138 1 50       2 _set_url_param $x, 1 unless $was_val;
139              
140 1         4 \%param
141             }
142              
143             #@category parse url
144              
145             sub _parse_url ($) {
146 30     30   33 my ($link) = @_;
147 30         134 $link =~ m!^
148             ( (? \w+ ) : )?
149             ( //
150             ( (? [^:/?\#\@]* ) :
151             (? [^/?\#\@]* ) \@ )?
152             (? [^/?\#]* ) )?
153             ( / (? [^?\#]* ) )?
154             (? [^?\#]+ )?
155             ( \? (? [^\#]* ) )?
156             ( \# (? .* ) )?
157             \z!xn;
158 30         232 return %+;
159             }
160              
161             # 1 - set / in each page, if it not file (*.*), or 0 - unset
162 1     1   1267 use Aion::Env::Etc DIR => (default => 0, key => 'aion.format.url.dir');
  1         4966  
  1         5  
163 1     1   116 use Aion::Env::Etc ONPAGE => (default => "off://off", key => 'aion.format.url.onpage');
  1         1  
  1         3  
164              
165             # Парсит и нормализует url
166             sub parse_url($;$$) {
167 15     15 1 4145 my ($link, $onpage, $dir) = @_;
168 15   100     50 $onpage //= ONPAGE;
169 15   50     38 $dir //= DIR;
170 15         16 my $orig = $link;
171              
172 15         29 my %link = _parse_url $link;
173 15         28 my %onpage = _parse_url $onpage;
174              
175 15 100       39 if(!exists $link{path}) {
176 11 50       63 $link{path} = join "", $onpage{path}, $onpage{path} =~ m!/\z!? (): "/", $link{part};
177 11         13 delete $link{part};
178             }
179              
180 15 100       27 if(exists $link{proto}) {}
    100          
181             elsif(exists $link{domain}) {
182 1         2 $link{proto} = $onpage{proto};
183             }
184             else {
185 13         38 $link{proto} = $onpage{proto};
186 13 50       21 $link{user} = $onpage{user} if exists $onpage{user};
187 13 50       17 $link{pass} = $onpage{pass} if exists $onpage{pass};
188 13         19 $link{domain} = $onpage{domain};
189             }
190              
191             # нормализуем
192 15         21 $link{proto} = lc $link{proto};
193 15         20 $link{domain} = lc $link{domain};
194 15         27 $link{dom} = $link{domain} =~ s/^www\.//r;
195 15         20 $link{path} = lc $link{path};
196              
197 15         31 my @path = split m!/!, $link{path}; my @p;
  15         28  
198              
199 15         20 for my $p (@path) {
200 18 50       28 if($p eq ".") {}
    50          
201             elsif($p eq "..") {
202             #@p or die "Выход за пределы пути";
203 0         0 pop @p;
204             }
205 18         46 else { push @p, $p }
206             }
207              
208 15         28 @p = grep { $_ ne "" } @p;
  18         35  
209              
210 15 100       26 if(@p) {
    50          
211 9         16 $link{path} = join "/", "", @p;
212 9 100       22 if($link{path} =~ m![^/]*\.[^/]*\z!) {
    50          
213 1         3 $link{dir} = $`;
214 1         2 $link{file} = $&;
215             } elsif($dir) {
216 0         0 $link{path} = $link{dir} = "$link{path}/";
217             } else {
218 8         21 $link{dir} = "$link{path}/";
219             }
220             } elsif($dir) {
221 0         0 $link{path} = "/";
222 6         8 } else { delete $link{path} }
223              
224 15         18 $link{orig} = $orig;
225 15         17 $link{onpage} = $onpage;
226             $link{link} = join "", $link{proto}, "://",
227             exists $link{user} || exists $link{pass}? ($link{user},
228             exists $link{pass}? ":$link{pass}": (), '@'): (),
229             $link{dom},
230             $link{path},
231             length($link{query})? ("?", $link{query}): (),
232 15 50 66     91 length($link{hash})? ("#", $link{hash}): (),
    100          
    100          
    100          
233             ;
234              
235 15         97 return \%link;
236             }
237              
238             # Нормализует url
239             sub normalize_url($;$$) {
240             parse_url($_[0], $_[1], $_[2])->{link}
241 12     12 1 7688 }
242              
243             1;
244              
245             __END__