2007-05-21 03:34:53 +04:00
|
|
|
|
#!/usr/bin/perl
|
2008-09-06 02:38:55 +04:00
|
|
|
|
# Некоторые простые полезные функции
|
2007-05-21 03:34:53 +04:00
|
|
|
|
|
|
|
|
|
package VMX::Common;
|
|
|
|
|
|
2008-09-23 20:14:01 +04:00
|
|
|
|
use strict;
|
2008-10-13 02:49:29 +04:00
|
|
|
|
use utf8;
|
2008-02-21 23:56:43 +03:00
|
|
|
|
use Encode;
|
2008-10-13 02:49:29 +04:00
|
|
|
|
|
|
|
|
|
use DBI;
|
2007-05-29 19:40:59 +04:00
|
|
|
|
use Digest::MD5;
|
2008-10-13 02:49:29 +04:00
|
|
|
|
|
2007-05-21 03:34:53 +04:00
|
|
|
|
require Exporter;
|
|
|
|
|
|
2008-09-23 20:14:01 +04:00
|
|
|
|
our @EXPORT_OK = qw(
|
|
|
|
|
quotequote min max trim htmlspecialchars strip_tags strip_unsafe_tags
|
|
|
|
|
file_get_contents dbi_hacks ar1el filemd5 mysql_quote updaterow_hashref
|
2008-10-13 02:49:29 +04:00
|
|
|
|
insertall_hashref dumper_no_lf multiselectall_hashref
|
2008-09-23 20:14:01 +04:00
|
|
|
|
);
|
|
|
|
|
our %EXPORT_TAGS = (all => [ @EXPORT_OK ]);
|
2008-02-13 04:18:50 +03:00
|
|
|
|
|
2008-09-23 20:14:01 +04:00
|
|
|
|
our $allowed_html = [qw/
|
|
|
|
|
div span a b i u p h\d+ strike strong small big blink center ol pre sub
|
|
|
|
|
sup font br table tr td th tbody tfoot thead tt ul li em img marquee
|
|
|
|
|
/];
|
2007-05-21 03:34:53 +04:00
|
|
|
|
|
2008-10-13 02:49:29 +04:00
|
|
|
|
# Exporter-ский импорт + подмена функции в DBI
|
2008-09-06 02:38:55 +04:00
|
|
|
|
sub import
|
|
|
|
|
{
|
|
|
|
|
foreach (@_)
|
|
|
|
|
{
|
|
|
|
|
if ($_ eq '!dbi_hacks')
|
|
|
|
|
{
|
2007-05-21 03:34:53 +04:00
|
|
|
|
return Exporter::import(@_);
|
2008-09-06 02:38:55 +04:00
|
|
|
|
}
|
|
|
|
|
elsif ($_ eq 'dbi_hacks')
|
|
|
|
|
{
|
2008-02-13 04:18:50 +03:00
|
|
|
|
$_ = '!dbi_hacks';
|
2007-05-21 03:34:53 +04:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
*DBI::_::st::fetchall_hashref = *VMX::Common::fetchall_hashref;
|
|
|
|
|
*DBI::st::fetchall_hashref = *VMX::Common::fetchall_hashref;
|
|
|
|
|
$DBI::DBI_methods{st}{fetchall_hashref} = { U =>[1,2,'[ $key_field ]'] };
|
2007-09-02 15:07:58 +04:00
|
|
|
|
$DBI::DBI_methods{db}{selectall_hashref} = { U =>[2,0,'$statement [, $keyfield [, \%attr [, @bind_params ] ] ]'], O=>0x2000 };
|
2007-05-21 03:34:53 +04:00
|
|
|
|
$Exporter::ExportLevel = 1;
|
|
|
|
|
my $r = Exporter::import(@_);
|
|
|
|
|
$Exporter::ExportLevel = 0;
|
|
|
|
|
return $r;
|
|
|
|
|
}
|
|
|
|
|
|
2008-10-13 02:49:29 +04:00
|
|
|
|
# Функция возвращает минимальное из значений
|
|
|
|
|
# $r = min (@list)
|
2008-09-06 02:38:55 +04:00
|
|
|
|
sub min
|
|
|
|
|
{
|
2007-05-21 03:34:53 +04:00
|
|
|
|
return undef if (@_ < 1);
|
|
|
|
|
my $r = shift;
|
|
|
|
|
foreach (@_) { $r = $_ if $r > $_; }
|
|
|
|
|
return $r;
|
|
|
|
|
}
|
|
|
|
|
|
2008-10-13 02:49:29 +04:00
|
|
|
|
# Функция возвращает максимальное из значений
|
|
|
|
|
# $r = max (@list)
|
2008-09-06 02:38:55 +04:00
|
|
|
|
sub max
|
|
|
|
|
{
|
2007-05-30 19:40:07 +04:00
|
|
|
|
return undef if (@_ < 1);
|
|
|
|
|
my $r = shift;
|
|
|
|
|
foreach (@_) { $r = $_ if $r < $_; }
|
|
|
|
|
return $r;
|
|
|
|
|
}
|
|
|
|
|
|
2008-10-13 02:49:29 +04:00
|
|
|
|
# ar1el($a) - аналог ($a || [])->[0], только ещё проверяет, что $a есть arrayref
|
2008-09-06 02:38:55 +04:00
|
|
|
|
sub ar1el
|
|
|
|
|
{
|
|
|
|
|
return undef unless 'ARRAY' eq ref $_[0];
|
|
|
|
|
return shift @{$_[0]};
|
2007-05-25 03:13:23 +04:00
|
|
|
|
}
|
|
|
|
|
|
2008-10-13 02:49:29 +04:00
|
|
|
|
# Функция обрезает пробельные символы в начале и конце строки
|
|
|
|
|
# trim ($r) in-place
|
2008-09-06 02:38:55 +04:00
|
|
|
|
sub trim
|
|
|
|
|
{
|
2008-09-07 01:18:12 +04:00
|
|
|
|
$_ = $_[0];
|
|
|
|
|
s/^\s+//so;
|
|
|
|
|
s/\s+$//so;
|
|
|
|
|
$_;
|
2007-05-21 03:34:53 +04:00
|
|
|
|
}
|
|
|
|
|
|
2008-10-13 02:49:29 +04:00
|
|
|
|
# аналог HTML::Entities::encode_entities
|
|
|
|
|
# $str = htmlspecialchars ($str)
|
2008-09-06 02:38:55 +04:00
|
|
|
|
sub htmlspecialchars
|
|
|
|
|
{
|
2007-05-21 03:34:53 +04:00
|
|
|
|
$_ = shift;
|
2008-09-06 02:38:55 +04:00
|
|
|
|
s/&/'/gso;
|
|
|
|
|
s/</</gso;
|
|
|
|
|
s/>/>/gso;
|
|
|
|
|
s/\"/"/gso;
|
|
|
|
|
s/\'/'/gso;
|
2007-05-21 03:34:53 +04:00
|
|
|
|
return $_;
|
|
|
|
|
}
|
|
|
|
|
|
2008-10-13 02:49:29 +04:00
|
|
|
|
# удаление тегов из строки, кроме заданных
|
|
|
|
|
# $str = strip_tags ($str)
|
2008-09-06 02:38:55 +04:00
|
|
|
|
sub strip_tags
|
|
|
|
|
{
|
2007-05-21 03:34:53 +04:00
|
|
|
|
$_ = shift;
|
2008-09-23 20:14:01 +04:00
|
|
|
|
my $ex = join '|', @{(shift)};
|
2007-05-21 03:34:53 +04:00
|
|
|
|
s/<\/?(?!\/?($ex))([a-z0-9_\-]+)[^<>]*>//gis;
|
|
|
|
|
return $_;
|
|
|
|
|
}
|
|
|
|
|
|
2008-10-13 02:49:29 +04:00
|
|
|
|
# удаление небезопасных HTML тегов (всех кроме our $allowed_html)
|
2008-09-06 02:38:55 +04:00
|
|
|
|
sub strip_unsafe_tags
|
|
|
|
|
{
|
2008-09-23 20:14:01 +04:00
|
|
|
|
strip_tags($_[0], $allowed_html);
|
2008-09-06 02:38:55 +04:00
|
|
|
|
}
|
|
|
|
|
|
2008-10-13 02:49:29 +04:00
|
|
|
|
# аналог File::Slurp
|
|
|
|
|
# $contents = file_get_contents ($filename)
|
2008-09-06 02:38:55 +04:00
|
|
|
|
sub file_get_contents
|
|
|
|
|
{
|
2007-05-21 03:34:53 +04:00
|
|
|
|
my ($tmp, $res);
|
|
|
|
|
open ($tmp, '<'.$_[0]);
|
2008-09-06 02:38:55 +04:00
|
|
|
|
if ($tmp)
|
|
|
|
|
{
|
2007-05-25 22:14:24 +04:00
|
|
|
|
local $/ = undef;
|
|
|
|
|
$res = <$tmp>;
|
2007-05-21 03:34:53 +04:00
|
|
|
|
close ($tmp);
|
|
|
|
|
}
|
|
|
|
|
return $res;
|
|
|
|
|
}
|
|
|
|
|
|
2008-10-13 02:49:29 +04:00
|
|
|
|
# изменённый вариант функции DBI::_::st::fetchall_hashref
|
|
|
|
|
# <ни фига не нужный велосипед>
|
|
|
|
|
# делает то же что и $dbh->selectall_arrayref(..., {Slice=>{}}, ...);
|
2008-09-06 02:38:55 +04:00
|
|
|
|
sub fetchall_hashref
|
|
|
|
|
{
|
2007-05-21 03:34:53 +04:00
|
|
|
|
my ($sth, $key_field) = @_;
|
|
|
|
|
my $hash_key_name = $sth->{FetchHashKeyName} || 'NAME';
|
|
|
|
|
my $names_hash = $sth->FETCH("${hash_key_name}_hash");
|
|
|
|
|
my @key_fields = (ref $key_field) ? @$key_field : $key_field ? ($key_field) : ();
|
|
|
|
|
my @key_indexes;
|
|
|
|
|
my $num_of_fields = $sth->FETCH('NUM_OF_FIELDS');
|
2008-09-06 02:38:55 +04:00
|
|
|
|
foreach (@key_fields)
|
|
|
|
|
{
|
|
|
|
|
my $index = $names_hash->{$_}; # perl index not column
|
|
|
|
|
$index = $_ - 1 if !defined $index && DBI::looks_like_number($_) && $_>=1 && $_ <= $num_of_fields;
|
|
|
|
|
return $sth->set_err(1, "Field '$_' does not exist (not one of @{[keys %$names_hash]})")
|
2007-05-21 03:34:53 +04:00
|
|
|
|
unless defined $index;
|
2008-09-06 02:38:55 +04:00
|
|
|
|
push @key_indexes, $index;
|
2007-05-21 03:34:53 +04:00
|
|
|
|
}
|
|
|
|
|
my $rows = {};
|
|
|
|
|
$rows = [] unless @key_indexes;
|
|
|
|
|
my $NAME = $sth->FETCH($hash_key_name);
|
|
|
|
|
my @row = (undef) x $num_of_fields;
|
|
|
|
|
$sth->bind_columns(\(@row)) if @row;
|
2008-09-06 02:38:55 +04:00
|
|
|
|
while ($sth->fetch)
|
|
|
|
|
{
|
2007-05-21 03:34:53 +04:00
|
|
|
|
my $ref;
|
2008-09-06 02:38:55 +04:00
|
|
|
|
if (@key_indexes)
|
|
|
|
|
{
|
2007-05-21 03:34:53 +04:00
|
|
|
|
$ref = $rows;
|
|
|
|
|
$ref = $ref->{$row[$_]} ||= {} for @key_indexes;
|
2008-09-06 02:38:55 +04:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2007-05-21 03:34:53 +04:00
|
|
|
|
push @$rows, {};
|
|
|
|
|
$ref = $rows->[@$rows-1];
|
|
|
|
|
}
|
|
|
|
|
@$ref{@$NAME} = @row;
|
|
|
|
|
}
|
|
|
|
|
return $rows;
|
|
|
|
|
}
|
|
|
|
|
|
2008-10-13 02:49:29 +04:00
|
|
|
|
# Обновить все строки, у которых значения полей с названиями ключей %$key
|
|
|
|
|
# равны значениям %$key, установив в них поля с названиями ключей %$row
|
|
|
|
|
# значениям %$row
|
2008-09-06 02:38:55 +04:00
|
|
|
|
sub updaterow_hashref
|
|
|
|
|
{
|
2008-02-13 04:18:50 +03:00
|
|
|
|
my ($dbh, $table, $row, $key) = @_;
|
|
|
|
|
return 0 unless
|
2008-09-23 20:14:01 +04:00
|
|
|
|
$dbh && $table &&
|
2008-02-13 04:18:50 +03:00
|
|
|
|
$row && ref($row) eq 'HASH' && %$row &&
|
|
|
|
|
$key && ref($key) eq 'HASH' && %$key;
|
|
|
|
|
my @f = keys %$row;
|
|
|
|
|
my @k = keys %$key;
|
|
|
|
|
my $sql =
|
2008-09-23 20:14:01 +04:00
|
|
|
|
'UPDATE `'.$table.'` SET '.
|
2008-02-13 04:18:50 +03:00
|
|
|
|
join(', ', map { "`$_`=?" } @f).
|
2008-03-12 03:52:05 +03:00
|
|
|
|
' WHERE '.join(' AND ', map { "`$_`=?" } @k);
|
2008-02-13 04:18:50 +03:00
|
|
|
|
my @bind = (@$row{@f}, @$key{@k});
|
|
|
|
|
return $dbh->do($sql, {}, @bind);
|
|
|
|
|
}
|
|
|
|
|
|
2008-10-13 02:49:29 +04:00
|
|
|
|
# Вставить набор записей $rows = [{},{},{},...] в таблицу $table
|
|
|
|
|
# Возможно после этого дополнить каждую запись $reselect полями (напр. '*'),
|
|
|
|
|
# сделав дополнительный запрос выборки. Для этого требуются ещё поля
|
|
|
|
|
# `ji` INT DEFAULT NULL и `jin` INT DEFAULT NULL, и индекс по ним.
|
2008-08-15 21:31:24 +04:00
|
|
|
|
sub insertall_hashref
|
|
|
|
|
{
|
2008-09-05 20:46:22 +04:00
|
|
|
|
my ($dbh, $table, $rows, $reselect, $replace) = @_;
|
2008-02-13 04:18:50 +03:00
|
|
|
|
return 0 unless
|
2008-09-23 20:14:01 +04:00
|
|
|
|
$dbh && $table &&
|
2008-02-13 04:18:50 +03:00
|
|
|
|
$rows && ref($rows) eq 'ARRAY' && @$rows;
|
2008-09-02 17:30:08 +04:00
|
|
|
|
my $conn_id = undef;
|
2008-08-15 21:31:24 +04:00
|
|
|
|
if ($reselect)
|
|
|
|
|
{
|
2008-02-13 04:18:50 +03:00
|
|
|
|
my $i = 0;
|
2008-09-02 17:30:08 +04:00
|
|
|
|
$conn_id = $dbh->{mysql_connection_id};
|
|
|
|
|
@$_{'ji','jin'} = ($conn_id, ++$i) foreach @$rows;
|
2008-02-13 04:18:50 +03:00
|
|
|
|
}
|
|
|
|
|
my @f = keys %{$rows->[0]};
|
2008-09-05 20:46:22 +04:00
|
|
|
|
my $sql = ($replace ? 'INSERT' : 'REPLACE').
|
2008-09-23 20:14:01 +04:00
|
|
|
|
' INTO `'.$table.'` (`'.join('`,`',@f).'`) VALUES '.
|
2008-02-13 04:18:50 +03:00
|
|
|
|
join(',',('('.(join(',', ('?') x scalar(@f))).')') x scalar(@$rows));
|
|
|
|
|
my @bind = map { @$_{@f} } @$rows;
|
|
|
|
|
my $st = $dbh->do($sql, {}, @bind);
|
|
|
|
|
return $st if !$st || !$reselect;
|
2008-08-15 21:31:24 +04:00
|
|
|
|
if (ref($reselect) eq 'ARRAY')
|
|
|
|
|
{
|
2008-02-13 04:18:50 +03:00
|
|
|
|
$reselect = '`'.join('`,`',@$reselect).'`';
|
2008-08-15 21:31:24 +04:00
|
|
|
|
}
|
|
|
|
|
elsif ($reselect ne '*')
|
|
|
|
|
{
|
2008-02-13 04:18:50 +03:00
|
|
|
|
$reselect = "`$reselect`";
|
|
|
|
|
}
|
2008-02-14 03:39:17 +03:00
|
|
|
|
# осуществляем reselect данных
|
2008-09-23 20:14:01 +04:00
|
|
|
|
$sql = "SELECT $reselect FROM `$table` WHERE `ji`=? ORDER BY `jin` ASC";
|
2008-09-02 17:30:08 +04:00
|
|
|
|
@bind = ($conn_id);
|
2008-02-14 03:39:17 +03:00
|
|
|
|
my $resel = $dbh->selectall_hashref($sql, [], {}, @bind);
|
2008-08-15 21:31:24 +04:00
|
|
|
|
for (my $i = 0; $i < @$resel; $i++)
|
|
|
|
|
{
|
2008-02-14 03:39:17 +03:00
|
|
|
|
$rows->[$i]->{$_} = $resel->[$i]->{$_} for keys %{$resel->[$i]};
|
|
|
|
|
}
|
2008-09-23 20:14:01 +04:00
|
|
|
|
$sql = "UPDATE `$table` SET `ji`=NULL, `jin`=NULL WHERE `ji`=?";
|
2008-02-14 03:39:17 +03:00
|
|
|
|
$dbh->do($sql, {}, @bind);
|
|
|
|
|
return $st;
|
2008-02-13 04:18:50 +03:00
|
|
|
|
}
|
|
|
|
|
|
2008-10-13 02:49:29 +04:00
|
|
|
|
# вещь, о которой все мы, пользователи MySQL, давно мечтали - возможность
|
|
|
|
|
# сделать SELECT t1.*, t2.*, t3.* и при этом успешно разделить поля таблиц,
|
|
|
|
|
# распределив их по хешам. Только надо делать SELECT t1.*, 0 AS '_', t2.* и т.п,
|
|
|
|
|
# т.е. поля разных таблиц разделять неким разделителем, и указывать его
|
|
|
|
|
# качестве $split. $names - имена отдельных хешей.
|
|
|
|
|
sub multiselectall_hashref
|
|
|
|
|
{
|
|
|
|
|
my ($dbh, $query, $bind, $split, $names) = @_;
|
|
|
|
|
return undef unless ref($dbh) && $query && $split && $names && @$names;
|
|
|
|
|
$bind ||= [];
|
|
|
|
|
unless (ref $query)
|
|
|
|
|
{
|
|
|
|
|
# запрос преображаем в stmt
|
|
|
|
|
$query = $dbh->prepare_cached($query);
|
|
|
|
|
return undef unless $query;
|
|
|
|
|
}
|
|
|
|
|
# делаем запрос к базе
|
2008-10-16 19:27:36 +04:00
|
|
|
|
$query->execute(@$bind);
|
|
|
|
|
my $rows = $query->fetchall_arrayref({});
|
2008-10-13 02:49:29 +04:00
|
|
|
|
return [] unless $rows && @$rows;
|
2008-10-16 19:27:36 +04:00
|
|
|
|
my $nh;
|
|
|
|
|
# DIRTY HACK :-)
|
|
|
|
|
unless ((tied %$query)->{__hack_split_multiselect})
|
2008-10-13 02:49:29 +04:00
|
|
|
|
{
|
2008-10-16 19:27:36 +04:00
|
|
|
|
# массив имён ещё не построен, построим
|
2008-10-13 02:49:29 +04:00
|
|
|
|
$nh = [[]];
|
2008-10-16 19:27:36 +04:00
|
|
|
|
my $n = [ @{$query->{$query->{FetchHashKeyName}}} ];
|
2008-10-13 02:49:29 +04:00
|
|
|
|
my $i = 0;
|
2008-10-16 19:27:36 +04:00
|
|
|
|
foreach (@{$query->{$query->{FetchHashKeyName}}})
|
2008-10-13 02:49:29 +04:00
|
|
|
|
{
|
2008-10-16 19:27:36 +04:00
|
|
|
|
if ($_ eq $split)
|
2008-10-13 02:49:29 +04:00
|
|
|
|
{
|
|
|
|
|
$i++;
|
2008-10-16 19:27:36 +04:00
|
|
|
|
$nh->[$i] = [];
|
2008-10-13 02:49:29 +04:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2008-10-16 19:27:36 +04:00
|
|
|
|
push @{$nh->[$i]}, $_;
|
2008-10-13 02:49:29 +04:00
|
|
|
|
}
|
|
|
|
|
}
|
2008-10-16 19:27:36 +04:00
|
|
|
|
(tied %$query)->{__hack_split_multiselect} = $nh;
|
2008-10-13 02:49:29 +04:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2008-10-16 19:27:36 +04:00
|
|
|
|
# или возьмём из объекта запроса
|
|
|
|
|
$nh = (tied %$query)->{__hack_split_multiselect};
|
2008-10-13 02:49:29 +04:00
|
|
|
|
}
|
|
|
|
|
# преобразуем строки
|
|
|
|
|
my ($row, $nr, $i);
|
|
|
|
|
foreach $row (@$rows)
|
|
|
|
|
{
|
|
|
|
|
$nr = {};
|
|
|
|
|
for $i (0..$#$names)
|
|
|
|
|
{
|
2008-10-16 19:27:36 +04:00
|
|
|
|
last unless $names->[$i] && $nh->[$i];
|
2008-10-13 02:49:29 +04:00
|
|
|
|
$nr->{$names->[$i]} = {};
|
2008-10-16 19:27:36 +04:00
|
|
|
|
@{$nr->{$names->[$i]}}{@{$nh->[$i]}} = @$row{@{$nh->[$i]}};
|
2008-10-13 02:49:29 +04:00
|
|
|
|
}
|
|
|
|
|
$row = $nr;
|
|
|
|
|
}
|
|
|
|
|
# возвращаем результат
|
|
|
|
|
return $rows;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# вычисление MD5 хеша от файла
|
2008-08-15 21:31:24 +04:00
|
|
|
|
sub filemd5
|
|
|
|
|
{
|
2007-05-29 19:40:59 +04:00
|
|
|
|
my ($file) = @_;
|
|
|
|
|
my $f;
|
|
|
|
|
my $r;
|
2008-08-15 21:31:24 +04:00
|
|
|
|
if (open $f, "<$file")
|
|
|
|
|
{
|
2007-05-29 19:40:59 +04:00
|
|
|
|
my $ctx = Digest::MD5->new;
|
|
|
|
|
$ctx->addfile($f);
|
|
|
|
|
$r = $ctx->hexdigest;
|
|
|
|
|
close $f;
|
|
|
|
|
}
|
|
|
|
|
return $r;
|
|
|
|
|
}
|
|
|
|
|
|
2008-10-13 02:49:29 +04:00
|
|
|
|
# тоже <ни фига не нужный велосипед>, экранирование символов для MySQL,
|
|
|
|
|
# да ещё и несколько кривое
|
2008-08-15 21:31:24 +04:00
|
|
|
|
sub mysql_quote
|
|
|
|
|
{
|
2007-06-07 03:09:26 +04:00
|
|
|
|
my ($a) = @_;
|
|
|
|
|
$a =~ s/\'/\'\'/gso;
|
2008-02-13 04:18:50 +03:00
|
|
|
|
$a =~ s/\\/\\\\/gso;
|
2007-06-07 03:09:26 +04:00
|
|
|
|
return "'$a'";
|
|
|
|
|
}
|
|
|
|
|
|
2008-10-13 02:49:29 +04:00
|
|
|
|
# экранирование кавычек
|
2008-08-15 21:31:24 +04:00
|
|
|
|
sub quotequote
|
|
|
|
|
{
|
|
|
|
|
my ($a) = @_;
|
|
|
|
|
$a =~ s/\'|\"/\\$&/gso;
|
|
|
|
|
return $a;
|
|
|
|
|
}
|
|
|
|
|
|
2008-10-13 02:49:29 +04:00
|
|
|
|
# Dumper без переводов строки
|
2008-08-15 21:31:24 +04:00
|
|
|
|
sub dumper_no_lf
|
|
|
|
|
{
|
2008-03-13 17:04:57 +03:00
|
|
|
|
my $r = Data::Dumper::Dumper (@_);
|
|
|
|
|
$r =~ s/\s+/ /giso;
|
|
|
|
|
return $r;
|
|
|
|
|
}
|
|
|
|
|
|
2007-05-21 03:34:53 +04:00
|
|
|
|
1;
|
2008-09-06 02:38:55 +04:00
|
|
|
|
__END__
|