File Coverage

blib/lib/DBD/PO/db.pm
Criterion Covered Total %
statement 28 30 93.3
branch n/a
condition n/a
subroutine 10 10 100.0
pod n/a
total 38 40 95.0


line stmt bran cond sub pod time code
1             package DBD::PO::db; ## no critic (Capitalization)
2            
3 1     1   7035 use strict;
  1         2  
  1         32  
4 1     1   5 use warnings;
  1         2  
  1         44  
5            
6             our $VERSION = '2.05';
7            
8 1     1   4 use DBD::File;
  1         3  
  1         24  
9 1     1   5 use parent qw(-norequire DBD::File::db);
  1         2  
  1         7  
10            
11 1     1   54 use Carp qw(croak);
  1         3  
  1         50  
12 1     1   7 use Params::Validate qw(:all);
  1         1  
  1         266  
13 1     1   1232 use Storable qw(dclone);
  1         3553  
  1         77  
14 1     1   9 use SQL::Statement; # for SQL::Parser
  1         2  
  1         23  
15 1     1   5 use SQL::Parser;
  1         1  
  1         19  
16 1     1   55 use DBD::PO::Locale::PO;
  0            
  0            
17             use DBD::PO::Text::PO qw($EOL_DEFAULT $SEPARATOR_DEFAULT $CHARSET_DEFAULT);
18            
19             our $imp_data_size = 0; ## no critic (PackageVars)
20            
21             my (@HEADER_KEYS, @HEADER_FORMATS, @HEADER_DEFAULTS, @HEADER_REGEX);
22             {
23             my @header = (
24             [ project_id_version => 'Project-Id-Version: %s' ],
25             [ report_msgid_bugs_to => 'Report-Msgid-Bugs-To: %s <%s>' ],
26             [ pot_creation_date => 'POT-Creation-Date: %s' ],
27             [ po_revision_date => 'PO-Revision-Date: %s' ],
28             [ last_translator => 'Last-Translator: %s <%s>' ],
29             [ language_team => 'Language-Team: %s <%s>' ],
30             [ mime_version => 'MIME-Version: %s' ],
31             [ content_type => 'Content-Type: %s; charset=%s' ],
32             [ content_transfer_encoding => 'Content-Transfer-Encoding: %s' ],
33             [ plural_forms => 'Plural-Forms: %s' ],
34             [ extended => '%s: %s' ],
35             );
36             @HEADER_KEYS = map {$_->[0]} @header;
37             @HEADER_FORMATS = map {$_->[1]} @header;
38             @HEADER_DEFAULTS = (
39             undef,
40             undef,
41             undef,
42             undef,
43             undef,
44             undef,
45             '1.0',
46             ['text/plain', undef],
47             '8bit',
48             undef,
49             undef,
50             );
51             @HEADER_REGEX = (
52             qr{\A \QProject-Id-Version:\E \s* (.*) \s* \z}xmsi,
53             [
54             qr{\A \QReport-Msgid-Bugs-To:\E \s* ([^<]*) \s+ < ([^>]*) > \s* \z}xmsi,
55             qr{\A \QReport-Msgid-Bugs-To:\E \s* (.*) () \s* \z}xmsi,
56             ],
57             qr{\A \QPOT-Creation-Date:\E \s* (.*) \s* \z}xmsi,
58             qr{\A \QPO-Revision-Date:\E \s* (.*) \s* \z}xmsi,
59             [
60             qr{\A \QLast-Translator:\E \s* ([^<]*) \s+ < ([^>]*) > \s* \z}xmsi,
61             qr{\A \QLast-Translator:\E \s* (.*) () \s* \z}xmsi,
62             ],
63             [
64             qr{\A \QLanguage-Team:\E \s* ([^<]*) \s+ < ([^>]*) > \s* \z}xmsi,
65             qr{\A \QLanguage-Team:\E \s* (.*) () \s* \z}xmsi,
66             ],
67             qr{\A \QMIME-Version:\E \s* (.*) \s* \z}xmsi,
68             qr{\A \QContent-Type:\E \s* ([^;]*); \s* charset=(\S*) \s* \z}xmsi,
69             qr{\A \QContent-Transfer-Encoding:\E \s* (.*) \s* \z}xmsi,
70             qr{\A \QPlural-Forms:\E \s* (.*) \s* \z}xmsi,
71             qr{\A ([^:]*) : \s* (.*) \s* \z}xms,
72             );
73             }
74            
75             my $maketext_to_gettext_scalar = sub {
76             my $string = shift;
77            
78             defined $string
79             or return;
80             $string =~ s{
81             \[ \s*
82             (?:
83             ( [A-Za-z*\#] [A-Za-z_]* ) # $1 - function call
84             \s* , \s*
85             _ ( [1-9]\d* ) # $2 - variable
86             ( [^\]]* ) # $3 - arguments
87             | # or
88             _ ( [1-9]\d* ) # $4 - variable
89             )
90             \s* \]
91             }
92             {
93             $4 ? "%$4" : "%$1(%$2$3)"
94             }xmsge;
95            
96             return $string;
97             };
98            
99             sub maketext_to_gettext {
100             my ($self, @strings) = @_;
101            
102             return
103             @strings > 1
104             ? map { $maketext_to_gettext_scalar->($_) } @strings
105             : @strings
106             ? $maketext_to_gettext_scalar->( $strings[0] )
107             : ();
108             }
109            
110             sub quote {
111             my($self, $string, $type) = @_;
112            
113             defined $string
114             or return 'NULL';
115             if (
116             defined($type)
117             && (
118             $type == DBI::SQL_NUMERIC()
119             || $type == DBI::SQL_DECIMAL()
120             || $type == DBI::SQL_INTEGER()
121             || $type == DBI::SQL_SMALLINT()
122             || $type == DBI::SQL_FLOAT()
123             || $type == DBI::SQL_REAL()
124             || $type == DBI::SQL_DOUBLE()
125             || $type == DBI::SQL_TINYINT()
126             )
127             ) {
128             return $string;
129             }
130             my $is_quoted;
131             for (
132             $string =~ s{\\}{\\\\}xmsg,
133             $string =~ s{'}{\\'}xmsg,
134             ) {
135             $is_quoted ||= $_;
136             }
137            
138             return $is_quoted
139             ? "'_Q_U_O_T_E_D_:$string'"
140             : "'$string'";
141             }
142            
143             ## no critic (MagicNumbers)
144             my %hash2array = (
145             'Project-Id-Version' => 0,
146             'Report-Msgid-Bugs-To-Name' => [1, 0],
147             'Report-Msgid-Bugs-To-Mail' => [1, 1],
148             'POT-Creation-Date' => 2,
149             'PO-Revision-Date' => 3,
150             'Last-Translator-Name' => [4, 0],
151             'Last-Translator-Mail' => [4, 1],
152             'Language-Team-Name' => [5, 0],
153             'Language-Team-Mail' => [5, 1],
154             'MIME-Version' => 6,
155             'Content-Type' => [7, 0],
156             charset => [7, 1],
157             'Content-Transfer-Encoding' => 8,
158             'Plural-Forms' => 9,
159             );
160             my $index_extended = 10;
161             ## use critic (MagicNumbers)
162            
163             my $valid_keys_regex = '(?xsm-i:\A (?: '
164             . join(
165             q{|},
166             map {
167             quotemeta $_
168             } keys %hash2array, 'extended'
169             )
170             . ' ) \z)';
171            
172             sub _hash2array {
173             my ($hash_data, $charset) = @_;
174             caller eq __PACKAGE__
175             or croak 'Do not call a private sub';
176             validate_with(
177             params => $hash_data,
178             spec => {
179             (
180             map {
181             ($_ => {type => SCALAR, optional => 1});
182             } keys %hash2array
183             ),
184             extended => {type => ARRAYREF, optional => 1},
185             },
186             );
187            
188             my $array_data = dclone(\@HEADER_DEFAULTS);
189             $array_data->[ $hash2array{charset}->[0] ]->[$hash2array{charset}->[1] ]
190             = $charset;
191             KEY:
192             for my $key (keys %{$hash_data}) {
193             if ($key eq 'extended') {
194             $array_data->[$index_extended] = $hash_data->{extended};
195             next KEY;
196             }
197             if (ref $hash2array{$key} eq 'ARRAY') {
198             $array_data->[ $hash2array{$key}->[0] ]->[ $hash2array{$key}->[1] ]
199             = $hash_data->{$key};
200             next KEY;
201             }
202             $array_data->[ $hash2array{$key} ] = $hash_data->{$key};
203             }
204            
205             return $array_data;
206             };
207            
208             sub get_all_header_keys {
209             return [keys %hash2array];
210             }
211            
212             sub build_header_msgstr { ## no critic (ArgUnpacking)
213             my ($dbh, $anything) = validate_pos(
214             @_,
215             {isa => 'DBI::db'},
216             {type => UNDEF | ARRAYREF | HASHREF},
217             );
218            
219             my $charset = $dbh->FETCH('po_charset')
220             ? $dbh->FETCH('po_charset')
221             : $CHARSET_DEFAULT;
222             my $array_data = ref $anything eq 'HASH'
223             ? _hash2array($anything, $charset)
224             : $anything;
225             my @header;
226             HEADER_KEY:
227             for my $index (0 .. $#HEADER_KEYS) {
228             my $data = $array_data->[$index]
229             || $HEADER_DEFAULTS[$index];
230             defined $data
231             or next HEADER_KEY;
232             my $key = $HEADER_KEYS[$index];
233             my $format = $HEADER_FORMATS[$index];
234             my @data = defined $data
235             ? (
236             ref $data eq 'ARRAY'
237             ? @{ $data }
238             : $data
239             )
240             : ();
241             if ($key eq 'content_type') {
242             if ($charset) {
243             $data[1] = $charset;
244             }
245             }
246             @data
247             or next HEADER_KEY;
248             if ($key eq 'extended') {
249             @data % 2
250             and croak "$key pairs are not pairwise";
251             while (my ($name, $value) = splice @data, 0, 2) {
252             push @header, sprintf $format, $name, $value;
253             }
254             }
255             else {
256             my $row = sprintf $format, map {defined $_ ? $_ : q{}} @data;
257             $row =~ s{\s* <> \z}{}xms; # delete an empty mail address
258             push @header, $row;
259             }
260             }
261            
262             return join "\n", @header;
263             }
264            
265             sub get_header_msgstr { ## no critic (ArgUnpacking)
266             my ($dbh, $hash_ref) = validate_pos(
267             @_,
268             {isa => 'DBI::db'},
269             {type => HASHREF},
270             );
271            
272             my $sth = $dbh->prepare(<<"EOT") or croak $dbh->errstr();
273             SELECT msgstr
274             FROM $hash_ref->{table}
275             WHERE msgid = ''
276             EOT
277             $sth->execute()
278             or croak $sth->errstr();
279             my ($msgstr) = $sth->fetchrow_array()
280             or croak $sth->errstr();
281             $sth->finish()
282             or croak $sth->errstr();
283            
284             return $msgstr;
285             }
286            
287             sub split_header_msgstr { ## no critic (ArgUnpacking)
288             my ($dbh, $anything) = validate_pos(
289             @_,
290             {isa => 'DBI::db'},
291             {type => SCALAR | HASHREF},
292             );
293            
294             my $msgstr = (ref $anything eq 'HASH')
295             ? $dbh->func($anything, 'get_header_msgstr')
296             : $anything;
297            
298             my $po = DBD::PO::Locale::PO->new(
299             eol => defined $dbh->FETCH('eol')
300             ? $dbh->FETCH('eol')
301             : $EOL_DEFAULT,
302             );
303             my $separator = defined $dbh->FETCH('separator')
304             ? $dbh->FETCH('separator')
305             : $SEPARATOR_DEFAULT;
306             my @cols;
307             my @lines = split m{\Q$separator\E}xms, $msgstr;
308             LINE:
309             while (1) {
310             my $line = shift @lines;
311             defined $line
312             or last LINE;
313             # run the regex for the selected column
314             my $index = 0;
315             HEADER_REGEX:
316             for my $header_regex (@HEADER_REGEX) {
317             if (! $header_regex) {
318             ++$index;
319             next HEADER_REGEX;
320             }
321             my @result;
322             # more regexes are necessary
323             if (ref $header_regex eq 'ARRAY') {
324             # run from special to more common regex
325             INNER_REGEX:
326             for my $inner_regex ( @{$header_regex} ) {
327             @result = $line =~ $inner_regex;
328             last INNER_REGEX if @result;
329             }
330             }
331             # only 1 regex is necessary
332             else {
333             @result = $line =~ $header_regex;
334             }
335             # save the result to the selected column
336             if (@result) {
337             # some columns are multiline
338             defined $cols[$index]
339             ? (
340             ref $cols[$index] eq 'ARRAY'
341             ? push @{ $cols[$index] }, @result
342             : do {
343             $cols[$index] = [ $cols[$index], @result ];
344             }
345             )
346             : (
347             $cols[$index] = @result > 1
348             ? \@result
349             : $result[0]
350             );
351             next LINE;
352             }
353             ++$index;
354             }
355             }
356            
357             return \@cols;
358             }
359            
360             sub get_header_msgstr_data { ## no critic (ArgUnpacking)
361             my ($dbh, $anything, $key) = validate_pos(
362             @_,
363             {isa => 'DBI::db'},
364             {type => ARRAYREF | SCALAR | HASHREF},
365             {
366             type => SCALAR | ARRAYREF,
367             callbacks => {
368             check_keys => sub {
369             my $check_key = shift;
370             if (ref $check_key eq 'ARRAY') {
371             return 1;
372             }
373             else {
374             return $check_key =~ $valid_keys_regex;
375             }
376             },
377             },
378             },
379             );
380            
381             my $array_ref = (ref $anything eq 'ARRAY')
382             ? $anything
383             : $dbh->func($anything, 'split_header_msgstr');
384            
385             if (ref $key eq 'ARRAY') {
386             return [
387             map {
388             get_header_msgstr_data($dbh, $array_ref, $_);
389             } @{$key}
390             ];
391             }
392            
393             my $index = $key eq 'extended'
394             ? $index_extended
395             : $hash2array{$key};
396             if (ref $index eq 'ARRAY') {
397             return $array_ref->[ $index->[0] ]->[ $index->[1] ];
398             }
399            
400             return $array_ref->[$index];
401             }
402            
403             1;
404            
405             __END__