1 |
8 |
ahitrov@rambler.ru |
package Utils::HTML; |
2 |
|
|
|
3 |
|
|
# ---------------------------------------------------------------------------- |
4 |
|
|
# Здесь хранятся процедуры для удобства верстки |
5 |
|
|
# ---------------------------------------------------------------------------- |
6 |
|
|
|
7 |
|
|
use strict; |
8 |
|
|
use vars qw($VERSION @ISA @EXPORT $state $HTML $request); |
9 |
|
|
use HTML::TokeParser; |
10 |
|
|
use Contenido::Globals; |
11 |
|
|
use utf8; |
12 |
|
|
|
13 |
|
|
use Exporter; |
14 |
|
|
@ISA = qw(Exporter); |
15 |
|
|
@EXPORT = qw( |
16 |
|
|
&help |
17 |
|
|
&spacer |
18 |
|
|
&tlontl |
19 |
|
|
&word_ending |
20 |
|
|
&math_percent |
21 |
|
|
&wrap_long_words |
22 |
|
|
&break_word |
23 |
|
|
&error_catch |
24 |
|
|
&top100 |
25 |
|
|
&top100js |
26 |
|
|
&top100old |
27 |
|
|
&color |
28 |
|
|
&server_name |
29 |
|
|
&text_trim |
30 |
|
|
&limit_words |
31 |
|
|
&email |
32 |
|
|
&url |
33 |
|
|
&banner |
34 |
|
|
&banner2 |
35 |
|
|
&js_escape |
36 |
|
|
&html_escape |
37 |
|
|
&html_unescape |
38 |
|
|
&cgiescape |
39 |
|
|
&url_escape |
40 |
|
|
&url_unescape |
41 |
|
|
&format_date |
42 |
|
|
); |
43 |
|
|
|
44 |
|
|
$VERSION = '0.1'; |
45 |
|
|
|
46 |
|
|
# Всякие удобные функции, которые будут импортированы в HTML::Mason::Commands |
47 |
|
|
# Набор уродиков - для совместимости |
48 |
|
|
|
49 |
|
|
sub format_date { |
50 |
|
|
my ($date, $format) = @_; |
51 |
|
|
my ($year, $month, $day, $hour, $min, $sec, $msec) = split(/[T\-\.\:\s]+/, $date); |
52 |
|
|
$year = substr($year, -2) if $format =~ /(^[yY]{2}$|[^yY][yY]{2}$|^[yY]{2}[^yY])/; |
53 |
|
|
my %formats = ( |
54 |
|
|
d => '%3$d', |
55 |
|
|
D => '%3$d', |
56 |
|
|
h => '%4$d', |
57 |
|
|
H => '%4$d', |
58 |
|
|
m => '%5$d', |
59 |
|
|
M => '%2$d', |
60 |
|
|
dd => '%3$02d', |
61 |
|
|
DD => '%3$02d', |
62 |
|
|
mm => '%5$02d', |
63 |
|
|
MM => '%2$02d', |
64 |
|
|
hh => '%4$02d', |
65 |
|
|
HH => '%4$02d', |
66 |
|
|
ss => '%6$02d', |
67 |
|
|
SS => '%6$02d', |
68 |
|
|
yyyy => '%1$04d', |
69 |
|
|
YYYY => '%1$04d', |
70 |
|
|
); |
71 |
|
|
$format =~ s/([yYmMdDhHsS]+)/$formats{$1}/gi; |
72 |
|
|
my $result = sprintf($format, $year, $month, $day, $hour, $min, $sec, $msec); |
73 |
|
|
return $result; |
74 |
|
|
} |
75 |
|
|
|
76 |
|
|
sub spacer { |
77 |
|
|
my %opts = @_; |
78 |
|
|
my $w = $opts{w} || 1; |
79 |
|
|
my $h = $opts{h} || 1; |
80 |
|
|
|
81 |
|
|
return '<div style="width:'.$w.'px; height:'.$h.'px"><!-- --></div>'; |
82 |
|
|
} |
83 |
|
|
|
84 |
|
|
|
85 |
|
|
sub tlontl { |
86 |
|
|
my %opts = @_; |
87 |
|
|
my $src_link = $opts{link}; |
88 |
|
|
my $param = $opts{param}; |
89 |
|
|
my $object = $opts{object}; |
90 |
|
|
my $absolute = $opts{absolute}; |
91 |
|
|
my $skip_args = $opts{skip_args}; |
92 |
|
|
|
93 |
|
|
my $request_uri = $absolute ? 'http://'.$ENV{SERVER_NAME} : ''; |
94 |
|
|
$request_uri .= $skip_args ? $ENV{SCRIPT_NAME} : $ENV{REQUEST_URI}; |
95 |
|
|
|
96 |
|
|
my $link = $src_link; |
97 |
|
|
if ($skip_args) { |
98 |
|
|
$link =~ s/\?.*$//; |
99 |
|
|
} |
100 |
|
|
|
101 |
|
|
if ($link eq '') { |
102 |
|
|
return $object; |
103 |
|
|
} elsif($request_uri eq $link || $request_uri eq $link.'index.html') { |
104 |
|
|
return $object; |
105 |
|
|
} else { |
106 |
|
|
return '<a href="'.$src_link.'"'.($param ? ' '.$param : '' ).'>'.$object.'</a>'; |
107 |
|
|
} |
108 |
|
|
} |
109 |
|
|
|
110 |
|
|
|
111 |
|
|
sub word_ending { |
112 |
|
|
my %opts = @_; |
113 |
|
|
|
114 |
|
|
my $amount = $opts{'amount'}; # количество |
115 |
|
|
my $one = $opts{'one'}; # негрятенок |
116 |
|
|
my $two = $opts{'two'}; # негрятенка |
117 |
|
|
my $ten = $opts{'ten'}; # негрятят |
118 |
|
|
|
119 |
|
|
my $word = $ten; |
120 |
|
|
my $last_num = $amount; |
121 |
|
|
my $next_to_last_num = 0; |
122 |
|
|
|
123 |
|
|
return $word unless defined $amount && $amount =~ /^\d+$/; |
124 |
|
|
|
125 |
|
|
if (length($last_num) >= 2) { |
126 |
|
|
$last_num =~ s/.*(\d)(\d)$/$2/; |
127 |
|
|
$next_to_last_num = $1; |
128 |
|
|
} |
129 |
|
|
|
130 |
|
|
# 10 <= ? < 20 |
131 |
|
|
if ($next_to_last_num == 1) { |
132 |
|
|
$word = $ten; |
133 |
|
|
|
134 |
|
|
# 1,21,31,...,n1 |
135 |
|
|
} elsif ($last_num == 1) { |
136 |
|
|
$word = $one; |
137 |
|
|
|
138 |
|
|
# 5,6,7,8,9,10,25,26,.....,n5,n6,n7,n8,n9,n0 |
139 |
|
|
} elsif ($last_num > 4 || $last_num == 0) { |
140 |
|
|
$word = $ten; |
141 |
|
|
|
142 |
|
|
# other |
143 |
|
|
} else { |
144 |
|
|
$word = $two; |
145 |
|
|
} |
146 |
|
|
|
147 |
|
|
return $word; |
148 |
|
|
} |
149 |
|
|
|
150 |
|
|
# Нужен для постоения таблиц, ширина которых задается посредством конфигурационных файлов |
151 |
|
|
# <% math_percent('100%+200%-25%/2') %> результат: 288% |
152 |
|
|
# <% math_percent($project_conf->{left}+$project_conf->{center}) %> |
153 |
|
|
sub math_percent { |
154 |
|
|
my $exp = shift; |
155 |
|
|
$exp =~ s/\%//g; |
156 |
|
|
$exp = eval($exp); |
157 |
|
|
$exp = sprintf("%.0f", $exp); |
158 |
|
|
return $exp.'%'; |
159 |
|
|
} |
160 |
|
|
|
161 |
|
|
# Вставка тега wbr в длинные слова |
162 |
|
|
# Получает ссылку на строку (будьте внимательны - оригинальная строка будет изменена) |
163 |
|
|
sub wrap_long_words { |
164 |
|
|
my $string = shift; |
165 |
|
|
my %opts = @_; |
166 |
|
|
|
167 |
|
|
unless ($string && ref($string) eq 'SCALAR' && length($$string)) { |
168 |
|
|
return; |
169 |
|
|
} |
170 |
|
|
|
171 |
|
|
my $wordlength = $opts{'wordlength'} || 40; |
172 |
|
|
|
173 |
|
|
if (length($$string) <= $opts{'wordlength'}) { |
174 |
|
|
return $$string; |
175 |
|
|
} |
176 |
|
|
|
177 |
|
|
my $newstring = ''; |
178 |
|
|
|
179 |
|
|
my $p = HTML::TokeParser->new($string); |
180 |
|
|
$p->{textify} = {}; |
181 |
|
|
|
182 |
|
|
while (my $token = $p->get_token()) { |
183 |
|
|
my $type = $token->[0]; |
184 |
|
|
if ( $type eq 'S' ) { |
185 |
|
|
$newstring .= $token->[4]; |
186 |
|
|
} elsif ( $type eq 'E' ) { |
187 |
|
|
$newstring .= $token->[2]; |
188 |
|
|
} elsif ( $type eq 'T') { |
189 |
|
|
$token->[1] =~ s/\S{$wordlength,}/break_word($&,$wordlength)/eg; |
190 |
|
|
$newstring .= $token->[1]; |
191 |
|
|
} |
192 |
|
|
} |
193 |
|
|
$$string = $newstring; |
194 |
|
|
return $$string; |
195 |
|
|
} |
196 |
|
|
|
197 |
|
|
# Вставка тега wbr в одно длинное слово |
198 |
|
|
sub break_word { |
199 |
|
|
my ($word, $wordlength) = @_; |
200 |
|
|
$word =~ s/((?:[^&\s]|(&\#?\w{1,7};)){$wordlength})\B/$1<wbr \/>/g; |
201 |
|
|
return $word; |
202 |
|
|
} |
203 |
|
|
|
204 |
|
|
# Отлов ошибок |
205 |
|
|
sub error_catch { |
206 |
|
|
unless ( $state->development ) { |
207 |
|
|
return <<HTML; |
208 |
|
|
<script type="text/javascript">if(escape('а')!='%u0430') { var cs_i2=new Image; cs_i2.src='http://err.rambler.ru/cs/'; }function globalCsChErr(a,b,c) {var i=new Image; i.src='http://err.rambler.ru/js/?'+escape(a)+','+escape(b)+','+escape(c)+'/'; return true;}window.onerror=globalCsChErr;</script> |
209 |
|
|
HTML |
210 |
|
|
} else { |
211 |
|
|
return '<!--// sub &error_catch(); //-->'; |
212 |
|
|
} |
213 |
|
|
} |
214 |
|
|
|
215 |
|
|
# Rambler's Top100 |
216 |
|
|
sub top100 { |
217 |
|
|
my $top100id = shift || $HTML->{top100}; |
218 |
|
|
unless ( $state->development ) { |
219 |
|
|
return '<!-- top100 --><script type="text/javascript">new Image().src = "http://counter.rambler.ru/top100.scn?'.$top100id.'&rn="+Math.random()+"&rf="+escape(document.referrer);</script><noscript><a href="http://top100.rambler.ru/"><img src="http://counter.rambler.ru/top100.cnt?'.$top100id.'" alt="Rambler\'s Top100 Service" width="1" height="1" border="0"></a></noscript><!-- // top100 -->'; |
220 |
|
|
} else { |
221 |
|
|
return '<!--// sub &top100('.$top100id.'); (с использованием new Image().src) //-->'; |
222 |
|
|
} |
223 |
|
|
} |
224 |
|
|
|
225 |
|
|
sub top100old { |
226 |
|
|
unless ( $state->development ) { |
227 |
|
|
return '<!-- top100 --><a href="http://top100.rambler.ru/"><img src="http://counter.rambler.ru/top100.cnt?'.$HTML->{top100}.'" alt="Rambler\'s Top100 Service" width="1" height="1" border="0"></a><!-- // top100 -->'; |
228 |
|
|
} else { |
229 |
|
|
return '<!--// sub &top100old('.$HTML->{top100}.'); (без использования js) //-->'; |
230 |
|
|
} |
231 |
|
|
} |
232 |
|
|
|
233 |
|
|
sub top100js { |
234 |
|
|
unless ( $state->development ) { |
235 |
|
|
return '<!-- begin of Top100 code --><script type="text/javascript" src="http://counter.rambler.ru/top100.jcn?'.$HTML->{top100}.'"></script><noscript><img src="http://counter.rambler.ru/top100.cnt?'.$HTML->{top100}.'" alt="Rambler\'s Top100 Service" width="1" height="1" border="0" /></noscript><!-- end of Top100 code -->'; |
236 |
|
|
} else { |
237 |
|
|
return '<!--// sub &top100js('.$HTML->{top100}.'); (с использованием js) //-->'; |
238 |
|
|
} |
239 |
|
|
} |
240 |
|
|
|
241 |
|
|
# Раскрас строк зеброй |
242 |
|
|
sub color { return ++$request->{HTML_color_count} % 2 ? $_[0] : $_[1] } |
243 |
|
|
|
244 |
|
|
# универсальная замена $ENV{'SERVER_NAME'} |
245 |
|
|
sub server_name { return $ENV{'HTTP_X_HOST'} || $state->httpd_server() } |
246 |
|
|
|
247 |
|
|
# обрезает текст до нужной длины, предварительно удаляя html-теги (бывшая /inc/text_trim.msn) |
248 |
|
|
sub text_trim { |
249 |
|
|
my %opts = @_; |
250 |
|
|
my $text = $opts{'text'}; |
251 |
|
|
my $length = $opts{'length'} || 200; |
252 |
|
|
my $ellipsis = $opts{'ellipsis'} || '…'; |
253 |
|
|
$text =~ s/<[^>]*>//g; |
254 |
|
|
if (length($text) > $length) { |
255 |
|
|
$text = substr($text, 0, $length); |
256 |
|
|
$text =~ s/\s+\S*$//; |
257 |
|
|
$text .= $ellipsis; |
258 |
|
|
} |
259 |
|
|
return $text; |
260 |
|
|
} |
261 |
|
|
# limit_words('text', { min_words => 70, max_words => 100, ending => '...' }) |
262 |
|
|
sub limit_words { |
263 |
|
|
my $text = shift; my ($t1, $t2) = (); |
264 |
|
|
my %args = ref($_[0]) ? %{ $_[0] } : @_; |
265 |
|
|
|
266 |
|
|
my @words = split ' ', $text; $args{max_words} ||= 50; $args{min_words} ||= 10; |
267 |
|
|
|
268 |
|
|
return $text if $#words < $args{max_words}; |
269 |
|
|
$t1 = $t2 = join ' ', @words[0 .. $args{max_words}-1]; |
270 |
|
|
|
271 |
|
|
# magic ! |
272 |
|
|
my @wds = split ' ', $t1; |
273 |
|
|
return $t1 if $t1 =~ s/^(.+[\w»")]{3,}[.!?])+\s?[А-ЯA-Z«"].+?$/$1/ and scalar(@wds) > $args{min_words}; |
274 |
|
|
|
275 |
|
|
$t2 =~ s/[.,:;!?\s—-]+$//; |
276 |
|
|
$t2.($args{ending} || ''); |
277 |
|
|
} |
278 |
|
|
|
279 |
|
|
sub email { |
280 |
|
|
my $email = shift; |
281 |
|
|
$email =~ s/[<>'"\\]*//g; |
282 |
|
|
if ($email =~ /\@/) { |
283 |
|
|
return '<a href="mailto:'.$email.'">'.$email.'</a>'; |
284 |
|
|
} else { |
285 |
|
|
return $email; |
286 |
|
|
} |
287 |
|
|
} |
288 |
|
|
|
289 |
|
|
sub url { |
290 |
|
|
my $url = shift; |
291 |
|
|
$url =~ s/[<>'"\\]*//g; |
292 |
|
|
$url =~ s/^\s+//; |
293 |
|
|
$url =~ s/\/$//; |
294 |
|
|
return unless $url; |
295 |
|
|
$url =~ s/^((https?|ftp):\/\/)//; |
296 |
|
|
my $protocol = $1; |
297 |
|
|
unless ($protocol) { |
298 |
|
|
$protocol = 'http://'; |
299 |
|
|
} |
300 |
|
|
return '<a href="'.$protocol.$url.'" target="_blank">'.$url.'</a>'; |
301 |
|
|
} |
302 |
|
|
|
303 |
|
|
sub banner { |
304 |
|
|
my %opts = @_; |
305 |
|
|
my $id = $opts{'id'}; |
306 |
|
|
return '<!-- &banner(id=>'.$id.'); --><!--#include virtual="/ibanOsurg?rip=$remote_addr&place_id='.$id.'&sid=2" --><!-- // banner -->'; |
307 |
|
|
} |
308 |
|
|
|
309 |
|
|
sub banner2 { |
310 |
|
|
my %opts = @_; |
311 |
|
|
my $id = $opts{'id'}; |
312 |
|
|
my $div_class = $opts{'div_class'}; |
313 |
|
|
return '<!-- &banner2(id=>'.$id.'); --><!--#include virtual="/iban1?rip=$remote_addr&pg='.$id.'&ifr=5&wxh=&divclass='.$div_class.'"--><!-- // banner2 -->'; |
314 |
|
|
} |
315 |
|
|
|
316 |
|
|
sub help() { |
317 |
|
|
use Data::Dumper; |
318 |
|
|
my $opt = shift; |
319 |
|
|
my $data = ''; |
320 |
|
|
|
321 |
|
|
if ($opt) { |
322 |
|
|
|
323 |
|
|
} else { |
324 |
|
|
foreach (@EXPORT){ |
325 |
|
|
$data .= '<li>'.$_; |
326 |
|
|
} |
327 |
|
|
} |
328 |
|
|
return $data; |
329 |
|
|
} |
330 |
|
|
|
331 |
|
|
sub js_escape { |
332 |
|
|
my $string = shift; |
333 |
|
|
$string =~ s/([\"\'\\])/\\$1/g; |
334 |
|
|
$string =~ s/\r?\n/\\n/gs; |
335 |
|
|
$string =~ s/\r//gs; |
336 |
|
|
return $string; |
337 |
|
|
} |
338 |
|
|
|
339 |
|
|
sub html_escape { |
340 |
|
|
my $string = shift; |
341 |
|
|
$string =~ s/&/&/g; |
342 |
|
|
$string =~ s/"/"/g; |
343 |
|
|
$string =~ s/>/>/g; |
344 |
|
|
$string =~ s/</</g; |
345 |
|
|
return $string; |
346 |
|
|
} |
347 |
|
|
|
348 |
|
|
sub html_unescape { |
349 |
|
|
my $string = shift; |
350 |
|
|
$string =~ s/&/&/g; |
351 |
|
|
$string =~ s/"/"/g; |
352 |
|
|
$string =~ s/>/>/g; |
353 |
|
|
$string =~ s/</</g; |
354 |
|
|
return $string; |
355 |
|
|
} |
356 |
|
|
|
357 |
|
|
sub rss_unescape { |
358 |
|
|
my $string = shift; |
359 |
|
|
my %opts = @_; |
360 |
|
|
|
361 |
|
|
if ( ref($string) eq 'SCALAR' ) { |
362 |
|
|
for ( $$string ) { |
363 |
|
|
s/»/"/gi; |
364 |
|
|
s/«/"/gi; |
365 |
|
|
s/”/"/gi; |
366 |
|
|
s/“/"/gi; |
367 |
|
|
s/’/\'/gi; |
368 |
|
|
s/‘/\'/gi; |
369 |
|
|
s/ /\ /gi; |
370 |
|
|
s/"/"/gi; |
371 |
|
|
s/©/(c)/gi; |
372 |
|
|
s/®/(r)/gi; |
373 |
|
|
} |
374 |
|
|
} elsif ( length($string) ) { |
375 |
|
|
for ( $string ) { |
376 |
|
|
s/»/"/gi; |
377 |
|
|
s/«/"/gi; |
378 |
|
|
s/”/"/gi; |
379 |
|
|
s/“/"/gi; |
380 |
|
|
s/’/\'/gi; |
381 |
|
|
s/‘/\'/gi; |
382 |
|
|
s/ /\ /gi; |
383 |
|
|
s/"/"/gi; |
384 |
|
|
s/©/(c)/gi; |
385 |
|
|
s/®/(r)/gi; |
386 |
|
|
} |
387 |
|
|
return $string; |
388 |
|
|
} |
389 |
|
|
} |
390 |
|
|
|
391 |
|
|
sub cgiescape { |
392 |
|
|
my $string = shift; |
393 |
|
|
$string =~ s/([^a-zA-Z_0-9.-])/sprintf "\%\%\%02x",ord($1)/ge; |
394 |
|
|
return $string; |
395 |
|
|
} |
396 |
|
|
|
397 |
|
|
sub url_escape { |
398 |
|
|
return URI::Escape::uri_escape(shift); |
399 |
|
|
} |
400 |
|
|
|
401 |
|
|
sub url_unescape { |
402 |
|
|
return URI::Escape::uri_unescape(shift); |
403 |
|
|
} |
404 |
|
|
|
405 |
|
|
1; |