package DBD::PO::db; ## no critic (Capitalization)
use strict;
use warnings;
our $VERSION = '2.05';
use DBD::File;
use parent qw(-norequire DBD::File::db);
use Carp qw(croak);
use Params::Validate qw(:all);
use Storable qw(dclone);
use SQL::Statement; # for SQL::Parser
use SQL::Parser;
use DBD::PO::Locale::PO;
use DBD::PO::Text::PO qw($EOL_DEFAULT $SEPARATOR_DEFAULT $CHARSET_DEFAULT);
our $imp_data_size = 0; ## no critic (PackageVars)
my (@HEADER_KEYS, @HEADER_FORMATS, @HEADER_DEFAULTS, @HEADER_REGEX);
{
my @header = (
[ project_id_version => 'Project-Id-Version: %s' ],
[ report_msgid_bugs_to => 'Report-Msgid-Bugs-To: %s <%s>' ],
[ pot_creation_date => 'POT-Creation-Date: %s' ],
[ po_revision_date => 'PO-Revision-Date: %s' ],
[ last_translator => 'Last-Translator: %s <%s>' ],
[ language_team => 'Language-Team: %s <%s>' ],
[ mime_version => 'MIME-Version: %s' ],
[ content_type => 'Content-Type: %s; charset=%s' ],
[ content_transfer_encoding => 'Content-Transfer-Encoding: %s' ],
[ plural_forms => 'Plural-Forms: %s' ],
[ extended => '%s: %s' ],
);
@HEADER_KEYS = map {$_->[0]} @header;
@HEADER_FORMATS = map {$_->[1]} @header;
@HEADER_DEFAULTS = (
undef,
undef,
undef,
undef,
undef,
undef,
'1.0',
['text/plain', undef],
'8bit',
undef,
undef,
);
@HEADER_REGEX = (
qr{\A \QProject-Id-Version:\E \s* (.*) \s* \z}xmsi,
[
qr{\A \QReport-Msgid-Bugs-To:\E \s* ([^<]*) \s+ < ([^>]*) > \s* \z}xmsi,
qr{\A \QReport-Msgid-Bugs-To:\E \s* (.*) () \s* \z}xmsi,
],
qr{\A \QPOT-Creation-Date:\E \s* (.*) \s* \z}xmsi,
qr{\A \QPO-Revision-Date:\E \s* (.*) \s* \z}xmsi,
[
qr{\A \QLast-Translator:\E \s* ([^<]*) \s+ < ([^>]*) > \s* \z}xmsi,
qr{\A \QLast-Translator:\E \s* (.*) () \s* \z}xmsi,
],
[
qr{\A \QLanguage-Team:\E \s* ([^<]*) \s+ < ([^>]*) > \s* \z}xmsi,
qr{\A \QLanguage-Team:\E \s* (.*) () \s* \z}xmsi,
],
qr{\A \QMIME-Version:\E \s* (.*) \s* \z}xmsi,
qr{\A \QContent-Type:\E \s* ([^;]*); \s* charset=(\S*) \s* \z}xmsi,
qr{\A \QContent-Transfer-Encoding:\E \s* (.*) \s* \z}xmsi,
qr{\A \QPlural-Forms:\E \s* (.*) \s* \z}xmsi,
qr{\A ([^:]*) : \s* (.*) \s* \z}xms,
);
}
my $maketext_to_gettext_scalar = sub {
my $string = shift;
defined $string
or return;
$string =~ s{
\[ \s*
(?:
( [A-Za-z*\#] [A-Za-z_]* ) # $1 - function call
\s* , \s*
_ ( [1-9]\d* ) # $2 - variable
( [^\]]* ) # $3 - arguments
| # or
_ ( [1-9]\d* ) # $4 - variable
)
\s* \]
}
{
$4 ? "%$4" : "%$1(%$2$3)"
}xmsge;
return $string;
};
sub maketext_to_gettext {
my ($self, @strings) = @_;
return
@strings > 1
? map { $maketext_to_gettext_scalar->($_) } @strings
: @strings
? $maketext_to_gettext_scalar->( $strings[0] )
: ();
}
sub quote {
my($self, $string, $type) = @_;
defined $string
or return 'NULL';
if (
defined($type)
&& (
$type == DBI::SQL_NUMERIC()
|| $type == DBI::SQL_DECIMAL()
|| $type == DBI::SQL_INTEGER()
|| $type == DBI::SQL_SMALLINT()
|| $type == DBI::SQL_FLOAT()
|| $type == DBI::SQL_REAL()
|| $type == DBI::SQL_DOUBLE()
|| $type == DBI::SQL_TINYINT()
)
) {
return $string;
}
my $is_quoted;
for (
$string =~ s{\\}{\\\\}xmsg,
$string =~ s{'}{\\'}xmsg,
) {
$is_quoted ||= $_;
}
return $is_quoted
? "'_Q_U_O_T_E_D_:$string'"
: "'$string'";
}
## no critic (MagicNumbers)
my %hash2array = (
'Project-Id-Version' => 0,
'Report-Msgid-Bugs-To-Name' => [1, 0],
'Report-Msgid-Bugs-To-Mail' => [1, 1],
'POT-Creation-Date' => 2,
'PO-Revision-Date' => 3,
'Last-Translator-Name' => [4, 0],
'Last-Translator-Mail' => [4, 1],
'Language-Team-Name' => [5, 0],
'Language-Team-Mail' => [5, 1],
'MIME-Version' => 6,
'Content-Type' => [7, 0],
charset => [7, 1],
'Content-Transfer-Encoding' => 8,
'Plural-Forms' => 9,
);
my $index_extended = 10;
## use critic (MagicNumbers)
my $valid_keys_regex = '(?xsm-i:\A (?: '
. join(
q{|},
map {
quotemeta $_
} keys %hash2array, 'extended'
)
. ' ) \z)';
sub _hash2array {
my ($hash_data, $charset) = @_;
caller eq __PACKAGE__
or croak 'Do not call a private sub';
validate_with(
params => $hash_data,
spec => {
(
map {
($_ => {type => SCALAR, optional => 1});
} keys %hash2array
),
extended => {type => ARRAYREF, optional => 1},
},
);
my $array_data = dclone(\@HEADER_DEFAULTS);
$array_data->[ $hash2array{charset}->[0] ]->[$hash2array{charset}->[1] ]
= $charset;
KEY:
for my $key (keys %{$hash_data}) {
if ($key eq 'extended') {
$array_data->[$index_extended] = $hash_data->{extended};
next KEY;
}
if (ref $hash2array{$key} eq 'ARRAY') {
$array_data->[ $hash2array{$key}->[0] ]->[ $hash2array{$key}->[1] ]
= $hash_data->{$key};
next KEY;
}
$array_data->[ $hash2array{$key} ] = $hash_data->{$key};
}
return $array_data;
};
sub get_all_header_keys {
return [keys %hash2array];
}
sub build_header_msgstr { ## no critic (ArgUnpacking)
my ($dbh, $anything) = validate_pos(
@_,
{isa => 'DBI::db'},
{type => UNDEF | ARRAYREF | HASHREF},
);
my $charset = $dbh->FETCH('po_charset')
? $dbh->FETCH('po_charset')
: $CHARSET_DEFAULT;
my $array_data = ref $anything eq 'HASH'
? _hash2array($anything, $charset)
: $anything;
my @header;
HEADER_KEY:
for my $index (0 .. $#HEADER_KEYS) {
my $data = $array_data->[$index]
|| $HEADER_DEFAULTS[$index];
defined $data
or next HEADER_KEY;
my $key = $HEADER_KEYS[$index];
my $format = $HEADER_FORMATS[$index];
my @data = defined $data
? (
ref $data eq 'ARRAY'
? @{ $data }
: $data
)
: ();
if ($key eq 'content_type') {
if ($charset) {
$data[1] = $charset;
}
}
@data
or next HEADER_KEY;
if ($key eq 'extended') {
@data % 2
and croak "$key pairs are not pairwise";
while (my ($name, $value) = splice @data, 0, 2) {
push @header, sprintf $format, $name, $value;
}
}
else {
my $row = sprintf $format, map {defined $_ ? $_ : q{}} @data;
$row =~ s{\s* <> \z}{}xms; # delete an empty mail address
push @header, $row;
}
}
return join "\n", @header;
}
sub get_header_msgstr { ## no critic (ArgUnpacking)
my ($dbh, $hash_ref) = validate_pos(
@_,
{isa => 'DBI::db'},
{type => HASHREF},
);
my $sth = $dbh->prepare(<<"EOT") or croak $dbh->errstr();
SELECT msgstr
FROM $hash_ref->{table}
WHERE msgid = ''
EOT
$sth->execute()
or croak $sth->errstr();
my ($msgstr) = $sth->fetchrow_array()
or croak $sth->errstr();
$sth->finish()
or croak $sth->errstr();
return $msgstr;
}
sub split_header_msgstr { ## no critic (ArgUnpacking)
my ($dbh, $anything) = validate_pos(
@_,
{isa => 'DBI::db'},
{type => SCALAR | HASHREF},
);
my $msgstr = (ref $anything eq 'HASH')
? $dbh->func($anything, 'get_header_msgstr')
: $anything;
my $po = DBD::PO::Locale::PO->new(
eol => defined $dbh->FETCH('eol')
? $dbh->FETCH('eol')
: $EOL_DEFAULT,
);
my $separator = defined $dbh->FETCH('separator')
? $dbh->FETCH('separator')
: $SEPARATOR_DEFAULT;
my @cols;
my @lines = split m{\Q$separator\E}xms, $msgstr;
LINE:
while (1) {
my $line = shift @lines;
defined $line
or last LINE;
# run the regex for the selected column
my $index = 0;
HEADER_REGEX:
for my $header_regex (@HEADER_REGEX) {
if (! $header_regex) {
++$index;
next HEADER_REGEX;
}
my @result;
# more regexes are necessary
if (ref $header_regex eq 'ARRAY') {
# run from special to more common regex
INNER_REGEX:
for my $inner_regex ( @{$header_regex} ) {
@result = $line =~ $inner_regex;
last INNER_REGEX if @result;
}
}
# only 1 regex is necessary
else {
@result = $line =~ $header_regex;
}
# save the result to the selected column
if (@result) {
# some columns are multiline
defined $cols[$index]
? (
ref $cols[$index] eq 'ARRAY'
? push @{ $cols[$index] }, @result
: do {
$cols[$index] = [ $cols[$index], @result ];
}
)
: (
$cols[$index] = @result > 1
? \@result
: $result[0]
);
next LINE;
}
++$index;
}
}
return \@cols;
}
sub get_header_msgstr_data { ## no critic (ArgUnpacking)
my ($dbh, $anything, $key) = validate_pos(
@_,
{isa => 'DBI::db'},
{type => ARRAYREF | SCALAR | HASHREF},
{
type => SCALAR | ARRAYREF,
callbacks => {
check_keys => sub {
my $check_key = shift;
if (ref $check_key eq 'ARRAY') {
return 1;
}
else {
return $check_key =~ $valid_keys_regex;
}
},
},
},
);
my $array_ref = (ref $anything eq 'ARRAY')
? $anything
: $dbh->func($anything, 'split_header_msgstr');
if (ref $key eq 'ARRAY') {
return [
map {
get_header_msgstr_data($dbh, $array_ref, $_);
} @{$key}
];
}
my $index = $key eq 'extended'
? $index_extended
: $hash2array{$key};
if (ref $index eq 'ARRAY') {
return $array_ref->[ $index->[0] ]->[ $index->[1] ];
}
return $array_ref->[$index];
}
1;
__END__
=head1 NAME
DBD::PO::db - database class for DBD::PO
$Id: db.pm 380 2009-05-02 07:05:20Z steffenw $
$HeadURL: https://dbd-po.svn.sourceforge.net/svnroot/dbd-po/trunk/DBD-PO/lib/DBD/PO/db.pm $
=head1 VERSION
2.05
=head1 SYNOPSIS
do not use
=head1 DESCRIPTION
database class for DBD::PO
=head1 SUBROUTINES/METHODS
=head2 method maketext_to_gettext
=head2 method quote
=head2 method get_all_header_keys
=head2 method build_header_msgstr
=head2 method get_header_msgstr
=head2 method split_header_msgstr
=head2 method get_header_msgstr_data
=head1 DIAGNOSTICS
none
=head1 CONFIGURATION AND ENVIRONMENT
none
=head1 DEPENDENCIES
parent
Carp
Storable
L<DBD::File>
L<Params::Validate>
L<SQL::Statement>
L<SQL::Parser>
L<DBD::PO::Locale::PO>
=head1 INCOMPATIBILITIES
not known
=head1 BUGS AND LIMITATIONS
not known
=head1 AUTHOR
Steffen Winkler
=head1 LICENSE AND COPYRIGHT
Copyright (c) 2008 - 2009,
Steffen Winkler
C<< <steffenw at cpan.org> >>.
All rights reserved.
This module is free software;
you can redistribute it and/or modify it
under the same terms as Perl itself.
=cut