Line # Revision Author
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.'&amp;rn="+Math.random()+"&amp;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 536 ahitrov my $text = Encode::decode('utf-8', $opts{'text'});
251 8 ahitrov@rambler.ru my $length = $opts{'length'} || 200;
252 536 ahitrov my $ellipsis = Encode::decode('utf-8', $opts{'ellipsis'}) || '&hellip;';
253 8 ahitrov@rambler.ru $text =~ s/<[^>]*>//g;
254 if (length($text) > $length) {
255 $text = substr($text, 0, $length);
256 $text =~ s/\s+\S*$//;
257 $text .= $ellipsis;
258 }
259 536 ahitrov return Encode::encode('utf-8', $text);
260 8 ahitrov@rambler.ru }
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 98 ahitrov s/^(.+\w{3,}[»")]?[.!?]+)\s*[А-ЯA-Z«"].+?$/$1/s and (()=/(\s+)/g)>$args{min_words} and return$_ for $t1;
273 8 ahitrov@rambler.ru
274 $t2 =~ s/[.,:;!?\s—-]+$//;
275 $t2.($args{ending} || '');
276 }
277
278 sub email {
279 my $email = shift;
280 $email =~ s/[<>'"\\]*//g;
281 if ($email =~ /\@/) {
282 return '<a href="mailto:'.$email.'">'.$email.'</a>';
283 } else {
284 return $email;
285 }
286 }
287
288 sub url {
289 my $url = shift;
290 $url =~ s/[<>'"\\]*//g;
291 $url =~ s/^\s+//;
292 $url =~ s/\/$//;
293 return unless $url;
294 $url =~ s/^((https?|ftp):\/\/)//;
295 my $protocol = $1;
296 unless ($protocol) {
297 $protocol = 'http://';
298 }
299 return '<a href="'.$protocol.$url.'" target="_blank">'.$url.'</a>';
300 }
301
302 sub banner {
303 my %opts = @_;
304 my $id = $opts{'id'};
305 return '<!-- &banner(id=>'.$id.'); --><!--#include virtual="/ibanOsurg?rip=$remote_addr&place_id='.$id.'&sid=2" --><!-- // banner -->';
306 }
307
308 sub banner2 {
309 my %opts = @_;
310 my $id = $opts{'id'};
311 my $div_class = $opts{'div_class'};
312 return '<!-- &banner2(id=>'.$id.'); --><!--#include virtual="/iban1?rip=$remote_addr&pg='.$id.'&ifr=5&wxh=&divclass='.$div_class.'"--><!-- // banner2 -->';
313 }
314
315 sub help() {
316 use Data::Dumper;
317 my $opt = shift;
318 my $data = '';
319
320 if ($opt) {
321
322 } else {
323 foreach (@EXPORT){
324 $data .= '<li>'.$_;
325 }
326 }
327 return $data;
328 }
329
330 sub js_escape {
331 my $string = shift;
332 $string =~ s/([\"\'\\])/\\$1/g;
333 $string =~ s/\r?\n/\\n/gs;
334 $string =~ s/\r//gs;
335 return $string;
336 }
337
338 sub html_escape {
339 my $string = shift;
340 $string =~ s/&/&amp;/g;
341 $string =~ s/"/&quot;/g;
342 $string =~ s/>/&gt;/g;
343 $string =~ s/</&lt;/g;
344 return $string;
345 }
346
347 sub html_unescape {
348 my $string = shift;
349 $string =~ s/&amp;/&/g;
350 $string =~ s/&quot;/"/g;
351 $string =~ s/&gt;/>/g;
352 $string =~ s/&lt;/</g;
353 return $string;
354 }
355
356 sub rss_unescape {
357 my $string = shift;
358 my %opts = @_;
359
360 if ( ref($string) eq 'SCALAR' ) {
361 for ( $$string ) {
362 s/&raquo;/"/gi;
363 s/&laquo;/"/gi;
364 s/&rdquo;/"/gi;
365 s/&ldquo;/"/gi;
366 s/&rsquo;/\'/gi;
367 s/&lsquo;/\'/gi;
368 s/&nbsp;/\ /gi;
369 s/&quot;/"/gi;
370 s/&copy;/(c)/gi;
371 s/&reg;/(r)/gi;
372 }
373 } elsif ( length($string) ) {
374 for ( $string ) {
375 s/&raquo;/"/gi;
376 s/&laquo;/"/gi;
377 s/&rdquo;/"/gi;
378 s/&ldquo;/"/gi;
379 s/&rsquo;/\'/gi;
380 s/&lsquo;/\'/gi;
381 s/&nbsp;/\ /gi;
382 s/&quot;/"/gi;
383 s/&copy;/(c)/gi;
384 s/&reg;/(r)/gi;
385 }
386 return $string;
387 }
388 }
389
390 sub cgiescape {
391 my $string = shift;
392 $string =~ s/([^a-zA-Z_0-9.-])/sprintf "\%\%\%02x",ord($1)/ge;
393 return $string;
394 }
395
396 sub url_escape {
397 return URI::Escape::uri_escape(shift);
398 }
399
400 sub url_unescape {
401 return URI::Escape::uri_unescape(shift);
402 }
403
404 1;

Небольшая справка по веткам

cnddist – контейнер, в котором хранятся все дистрибутивы всех библиотек и программных пакетов, которые использовались при построении различных версий Contenido. Если какой-то библиотеки в данном хранилище нет, инсталлятор сделает попытку "подтянуть" ее с веба (например, с CPAN). Если библиотека слишком старая, есть очень большая вероятность, что ее там уже нет. Поэтому мы храним весь хлам от всех сборок. Если какой-то дистрибутив вдруг отсутствует в cnddist - напишите нам, мы положим его туда.

koi8 – отмирающая ветка, чей код, выдача и все внутренние библиотеки заточены на кодировку KOI8-R. Вносятся только те дополнения, которые касаются внешнего вида и функционала админки, баги ядра, обязательные обновления портов и мелочи, которые легко скопипастить. В дальнейшем планируется полная остановка поддержки по данной ветке.

utf8 – актуальная ветка, заточенная под UTF-8.

Внутри каждой ветки: core – исходники ядра; install – скрипт установки инсталляции; plugins – плагины; samples – "готовые к употреблению" проекты, которые можно поставить, запустить и посмотреть, как они работают.