File Coverage

blib/lib/Locale/gettext_pp.pm
Criterion Covered Total %
statement 314 346 90.7
branch 116 196 59.1
condition 49 101 48.5
subroutine 42 42 100.0
pod 24 24 100.0
total 545 709 76.8


line stmt bran cond sub pod time code
1             #! /bin/false
2              
3             # vim: set autoindent shiftwidth=4 tabstop=4:
4              
5             # Pure Perl implementation of Uniforum message translation.
6             # Copyright (C) 2002-2017 Guido Flohr ,
7             # all rights reserved.
8              
9             # This program is free software: you can redistribute it and/or modify
10             # it under the terms of the GNU General Public License as published by
11             # the Free Software Foundation; either version 3 of the License, or
12             # (at your option) any later version.
13              
14             # This program is distributed in the hope that it will be useful,
15             # but WITHOUT ANY WARRANTY; without even the implied warranty of
16             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17             # GNU General Public License for more details.
18              
19             # You should have received a copy of the GNU General Public License
20             # along with this program. If not, see .
21              
22             package Locale::gettext_pp;
23              
24 22     22   5736 use strict;
  22         43  
  22         876  
25              
26             require 5.004;
27              
28 22         2012 use vars qw ($__gettext_pp_default_dir
29             $__gettext_pp_textdomain
30             $__gettext_pp_domain_bindings
31             $__gettext_pp_domain_codeset_bindings
32             $__gettext_pp_domains
33             $__gettext_pp_recoders
34             $__gettext_pp_unavailable_dirs
35             $__gettext_pp_domain_cache
36             $__gettext_pp_alias_cache
37 22     22   98 $__gettext_pp_context_glue);
  22         39  
38              
39 22     22   9111 use locale;
  22         11560  
  22         99  
40 22     22   724 use File::Spec;
  22         38  
  22         399  
41 22     22   583 use Locale::Messages;
  22         41  
  22         2542  
42              
43             BEGIN {
44 22     22   73 $__gettext_pp_textdomain = 'messages';
45 22         34 $__gettext_pp_domain_bindings = {};
46 22         103 $__gettext_pp_domain_codeset_bindings = {};
47 22         55 $__gettext_pp_domains = {};
48 22         50 $__gettext_pp_recoders = {};
49 22         43 $__gettext_pp_unavailable_dirs = {};
50 22         40 $__gettext_pp_domain_cache = {};
51 22         31 $__gettext_pp_alias_cache = {};
52             # The separator between msgctxt and msgid in a .mo file. */
53 22         33 $__gettext_pp_context_glue = "\004";
54            
55 22         42 $__gettext_pp_default_dir = '';
56            
57 22         48 for my $dir (qw (/usr/share/locale /usr/local/share/locale)) {
58 22 50       2710300 if (-d $dir) {
59 22         59 $__gettext_pp_default_dir = $dir;
60 22         946 last;
61             }
62             }
63             }
64              
65             BEGIN {
66 22     22   9488 require POSIX;
67 22         115778 require Exporter;
68 22     22   11149 use IO::Handle;
  22         117802  
  22         4093  
69 22         8162 require Locale::Recode;
70              
71 22         56 local $@;
72 22         45 my ($has_messages, $five_ok);
73            
74 22         1378 $has_messages = eval '&POSIX::LC_MESSAGES';
75              
76 22 50 33     259 unless (defined $has_messages && length $has_messages) {
77 0   0     0 $five_ok = ! grep {my $x = eval "&POSIX::$_" || 0; $x eq '5';}
  0         0  
  0         0  
78             qw (LC_CTYPE
79             LC_NUMERIC
80             LC_TIME
81             LC_COLLATE
82             LC_MONETARY
83             LC_ALL);
84 0 0       0 if ($five_ok) {
85 0         0 $five_ok = POSIX::setlocale (5, '');
86             }
87             }
88            
89 22 50 33     126 if (defined $has_messages && length $has_messages) {
    0          
90 22     1521 1 1578 eval <<'EOF';
  1521         3864  
  1521         4528  
91             sub LC_MESSAGES()
92             {
93             local $!; # Do not clobber errno!
94            
95             return &POSIX::LC_MESSAGES;
96             }
97             EOF
98             } elsif ($five_ok) {
99 0         0 eval <<'EOF';
100             sub LC_MESSAGES()
101             {
102             local $!; # Do not clobber errno!
103              
104             # Hack: POSIX.pm deems LC_MESSAGES an invalid macro until
105             # Perl 5.8.0. However, on LC_MESSAGES should be 5 ...
106             return 5;
107             }
108             EOF
109             } else {
110 0         0 eval <<'EOF';
111             sub LC_MESSAGES()
112             {
113             local $!; # Do not clobber errno!
114              
115             # This fallback value is widely used,
116             # when LC_MESSAGES is not available.
117             return 1729;
118             }
119             EOF
120             }
121             }
122              
123 22     22   140 use vars qw (%EXPORT_TAGS @EXPORT_OK @ISA $VERSION);
  22         38  
  22         83784  
124              
125             %EXPORT_TAGS = (locale_h => [ qw (
126             gettext
127             dgettext
128             dcgettext
129             ngettext
130             dngettext
131             dcngettext
132             pgettext
133             dpgettext
134             dcpgettext
135             npgettext
136             dnpgettext
137             dcnpgettext
138             textdomain
139             bindtextdomain
140             bind_textdomain_codeset
141             )
142             ],
143             libintl_h => [ qw (LC_CTYPE
144             LC_NUMERIC
145             LC_TIME
146             LC_COLLATE
147             LC_MONETARY
148             LC_MESSAGES
149             LC_ALL)
150             ],
151             );
152              
153             @EXPORT_OK = qw (gettext
154             dgettext
155             dcgettext
156             ngettext
157             dngettext
158             dcngettext
159             pgettext
160             dpgettext
161             dcpgettext
162             npgettext
163             dnpgettext
164             dcnpgettext
165             textdomain
166             bindtextdomain
167             bind_textdomain_codeset
168             nl_putenv
169             setlocale
170             LC_CTYPE
171             LC_NUMERIC
172             LC_TIME
173             LC_COLLATE
174             LC_MONETARY
175             LC_MESSAGES
176             LC_ALL);
177             @ISA = qw (Exporter);
178              
179             my $has_nl_langinfo;
180              
181             sub __load_catalog;
182             sub __load_domain;
183             sub __locale_category;
184             sub __untaint_plural_header;
185             sub __compile_plural_function;
186              
187             sub LC_NUMERIC()
188             {
189 2     2 1 8 &POSIX::LC_NUMERIC;
190             }
191              
192             sub LC_CTYPE()
193             {
194 2     2 1 16 &POSIX::LC_CTYPE;
195             }
196              
197             sub LC_TIME()
198             {
199 2     2 1 8 &POSIX::LC_TIME;
200             }
201              
202             sub LC_COLLATE()
203             {
204 2     2 1 7 &POSIX::LC_COLLATE;
205             }
206              
207             sub LC_MONETARY()
208             {
209 2     2 1 7 &POSIX::LC_MONETARY;
210             }
211              
212             sub LC_ALL()
213             {
214 17     17 1 160 &POSIX::LC_ALL;
215             }
216              
217             sub textdomain(;$)
218             {
219 208     208 1 305 my $new_domain = shift;
220            
221 208 100 100     510 $__gettext_pp_textdomain = $new_domain if defined $new_domain &&
222             length $new_domain;
223            
224 208         463 return $__gettext_pp_textdomain;
225             }
226              
227             sub bindtextdomain($;$)
228             {
229 50     50 1 123 my ($domain, $directory) = @_;
230              
231 50         75 my $retval;
232 50 50 33     241 if (defined $domain && length $domain) {
233 50 100 100     199 if (defined $directory && length $directory) {
    100          
234 37         120 $retval = $__gettext_pp_domain_bindings->{$domain}
235             = $directory;
236             } elsif (exists $__gettext_pp_domain_bindings->{$domain}) {
237 11         19 $retval = $__gettext_pp_domain_bindings->{$domain};
238             } else {
239 2         4 $retval = $__gettext_pp_default_dir;
240             }
241 50 50 33     199 $retval = '/usr/share/locale' unless defined $retval &&
242             length $retval;
243 50         140 return $retval;
244             } else {
245 0         0 return;
246             }
247             }
248              
249             sub bind_textdomain_codeset($;$)
250             {
251 2     2 1 6 my ($domain, $codeset) = @_;
252            
253 2 50 33     14 if (defined $domain && length $domain) {
254 2 50 33     8 if (defined $codeset && length $codeset) {
    0          
255 2         8 return $__gettext_pp_domain_codeset_bindings->{$domain} = $codeset;
256             } elsif (exists $__gettext_pp_domain_codeset_bindings->{$domain}) {
257 0         0 return $__gettext_pp_domain_codeset_bindings->{$domain};
258             }
259             }
260            
261 0         0 return;
262             }
263              
264             sub gettext($)
265             {
266 97     97 1 170 my ($msgid) = @_;
267              
268 97         191 return dcnpgettext ('', undef, $msgid, undef, undef, undef);
269             }
270              
271             sub dgettext($$)
272             {
273 12     12 1 25 my ($domainname, $msgid) = @_;
274              
275 12         28 return dcnpgettext ($domainname, undef, $msgid, undef, undef, undef);
276             }
277              
278             sub dcgettext($$$)
279             {
280 12     12 1 28 my ($domainname, $msgid, $category) = @_;
281              
282 12         30 return dcnpgettext ($domainname, undef, $msgid, undef, undef, undef);
283             }
284              
285             sub ngettext($$$)
286             {
287 83     83 1 157 my ($msgid, $msgid_plural, $n) = @_;
288              
289 83         166 return dcnpgettext ('', undef, $msgid, $msgid_plural, $n, undef);
290             }
291              
292             sub dngettext($$$$)
293             {
294 83     83 1 138 my ($domainname, $msgid, $msgid_plural, $n) = @_;
295              
296 83         135 return dcnpgettext ($domainname, undef, $msgid, $msgid_plural, $n, undef);
297             }
298              
299             sub dcngettext($$$$$)
300             {
301 83     83 1 152 my ($domainname, $msgid, $msgid_plural, $n, $category) = @_;
302              
303 83         134 return dcnpgettext ($domainname, undef, $msgid, $msgid_plural, $n, , $category);
304             }
305              
306              
307             sub pgettext($$)
308             {
309 3     3 1 6 my ($msgctxt, $msgid) = @_;
310              
311 3         7 return dcnpgettext ('', $msgctxt, $msgid, undef, undef, undef);
312             }
313              
314             sub dpgettext($$$)
315             {
316 4     4 1 7 my ($domainname, $msgctxt, $msgid) = @_;
317              
318 4         9 return dcnpgettext ($domainname, $msgctxt, $msgid, undef, undef, undef);
319             }
320              
321             sub dcpgettext($$$$)
322             {
323 5     5 1 11 my ($domainname, $msgctxt, $msgid, $category) = @_;
324              
325 5         7 return dcnpgettext ($domainname, $msgctxt, $msgid, undef, undef, undef);
326             }
327              
328             sub npgettext($$$$)
329             {
330 91     91 1 165 my ($msgctxt, $msgid, $msgid_plural, $n) = @_;
331              
332 91         183 return dcnpgettext ('', $msgctxt, $msgid, $msgid_plural, $n, undef);
333             }
334              
335             sub dnpgettext($$$$$)
336             {
337 91     91 1 170 my ($domainname, $msgctxt, $msgid, $msgid_plural, $n) = @_;
338              
339 91         164 return dcnpgettext ($domainname, $msgctxt, $msgid, $msgid_plural, $n, undef);
340             }
341              
342             # This is the actual implementation of dncpgettext. It is also used by the
343             # corresponding function in Locale::gettext_dumb.
344             sub _dcnpgettext_impl {
345 664     664   1266 my ($domainname, $msgctxt, $msgid, $msgid_plural, $n, $category,
346             $locale) = @_;
347              
348 664 50       1180 return unless defined $msgid;
349              
350 664         852 my $plural = defined $msgid_plural;
351 664         12977 Locale::Messages::turn_utf_8_off($msgid);
352 664 100       5201 Locale::Messages::turn_utf_8_off($msgctxt) if defined $msgctxt;
353 664 100       1385 my $msg_ctxt_id = defined $msgctxt ? join($__gettext_pp_context_glue, ($msgctxt, $msgid)) : $msgid;
354            
355 664         2244 local $!; # Do not clobber errno!
356            
357             # This is also done in __load_domain but we need a proper value.
358 664 100 66     2205 $domainname = $__gettext_pp_textdomain
359             unless defined $domainname && length $domainname;
360            
361             # Category is always LC_MESSAGES (other categories are ignored).
362 664         859 my $category_name = 'LC_MESSAGES';
363 664         10616 $category = LC_MESSAGES;
364              
365 664         1237 my $domains = __load_domain ($domainname, $category, $category_name,
366             $locale);
367            
368 664         1066 my @trans = ();
369 664         834 my $domain;
370             my $found;
371 664         1199 foreach my $this_domain (@$domains) {
372 9 100 66     31 if ($this_domain && defined $this_domain->{messages}->{$msg_ctxt_id}) {
373 5         11 @trans = @{$this_domain->{messages}->{$msg_ctxt_id}};
  5         11  
374 5         10 shift @trans;
375 5         7 $domain = $this_domain;
376 5         5 $found = 1;
377 5         10 last;
378             }
379             }
380 664 100       1611 @trans = ($msgid, $msgid_plural) unless @trans;
381            
382 664         929 my $trans = $trans[0];
383 664 100       1048 if ($plural) {
384 522 50       815 if ($domain) {
385 0         0 my $nplurals = 0;
386 0         0 ($nplurals, $plural) = &{$domain->{plural_func}} ($n);
  0         0  
387 0 0       0 $plural = 0 unless defined $plural;
388 0 0       0 $nplurals = 0 unless defined $nplurals;
389 0 0       0 $plural = 0 if $nplurals <= $plural;
390             } else {
391 522   100     968 $plural = $n != 1 || 0;
392             }
393            
394 522 50       998 $trans = $trans[$plural] if defined $trans[$plural];
395             }
396            
397 664 100 66     1178 if ($found && defined $domain->{po_header}->{charset}) {
398 5         10 my $input_codeset = $domain->{po_header}->{charset};
399             # Convert into output charset.
400 5         9 my $output_codeset = $__gettext_pp_domain_codeset_bindings->{$domainname};
401              
402 5 50       14 $output_codeset = $ENV{OUTPUT_CHARSET} unless defined $output_codeset;
403             $output_codeset = __get_codeset ($category, $category_name,
404             $domain->{locale_id})
405 5 50       16 unless defined $output_codeset;
406            
407 5 50       12 unless (defined $output_codeset) {
408             # Still no point.
409 5         12 my $lc_ctype = __locale_category (POSIX::LC_CTYPE(),
410             'LC_CTYPE');
411 5 50       15 $output_codeset = $1
412             if $lc_ctype =~ /^[a-z]{2}(?:_[A-Z]{2})?\.([^@]+)/;
413             }
414              
415             # No point. :-(
416             $output_codeset = $domain->{po_header}->{charset}
417 5 50       15 unless defined $output_codeset;
418            
419 5 100       12 if (exists $__gettext_pp_domain_cache->{$output_codeset}) {
420 3         5 $output_codeset = $__gettext_pp_domain_cache->{$output_codeset};
421             } else {
422 2 50       7 $output_codeset = 'utf-8' if lc $output_codeset eq 'utf8';
423             $output_codeset =
424 2         11 $__gettext_pp_domain_cache->{$output_codeset} =
425             Locale::Recode->resolveAlias ($output_codeset);
426             }
427            
428 5 50 33     22 if (defined $output_codeset &&
429             $output_codeset ne $domain->{po_header}->{charset}) {
430             # We have to convert.
431 0         0 my $recoder;
432            
433 0 0       0 if (exists
434             $__gettext_pp_recoders->{$input_codeset}->{$output_codeset}) {
435 0         0 $recoder = $__gettext_pp_recoders->{$input_codeset}->{$output_codeset};
436             } else {
437             $recoder =
438 0         0 $__gettext_pp_recoders->{$input_codeset}->{$output_codeset} =
439             Locale::Recode->new (from => $input_codeset,
440             to => $output_codeset,
441             );
442             }
443            
444 0         0 $recoder->recode ($trans);
445             }
446             }
447            
448 664         12531 return $trans;
449             }
450              
451             sub dcnpgettext ($$$$$$) {
452 655     655 1 1033 return &_dcnpgettext_impl;
453             }
454              
455             sub nl_putenv ($)
456             {
457 525     525 1 768 my ($envspec) = @_;
458 525 50       896 return unless defined $envspec;
459 525 50       812 return unless length $envspec;
460 525 50       905 return if substr ($envspec, 0, 1) eq '=';
461            
462 525         1096 my ($var, $value) = split /=/, $envspec, 2;
463              
464             # In Perl we *could* set empty environment variables even under
465             # MS-DOS, but for compatibility reasons, we implement the
466             # brain-damaged behavior of the Microsoft putenv().
467 525 50       1025 if ($^O eq 'MSWin32') {
468 0 0       0 $value = '' unless defined $value;
469 0 0       0 if (length $value) {
470 0         0 $ENV{$var} = $value;
471             } else {
472 0         0 delete $ENV{$var};
473             }
474             } else {
475 525 100       737 if (defined $value) {
476 407         1202 $ENV{$var} = $value;
477             } else {
478 118         360 delete $ENV{$var};
479             }
480             }
481              
482 525         870 return 1;
483             }
484              
485             sub setlocale($;$) {
486 126     126 1 495 require POSIX;
487 126         3016 &POSIX::setlocale;
488             }
489              
490             sub __selected_locales {
491 9     9   17 my ($locale, $category, $category_name) = @_;
492              
493 9         12 my @locales;
494             my $cache_key;
495              
496 9 100 66     41 if (defined $ENV{LANGUAGE} && length $ENV{LANGUAGE}) {
    50          
497 2         5 @locales = split /:/, $ENV{LANGUAGE};
498 2         5 $cache_key = $ENV{LANGUAGE};
499             } elsif (!defined $locale) {
500             # The system does not have LC_MESSAGES. Guess the value.
501 0         0 @locales = $cache_key = __locale_category ($category,
502             $category_name);
503             } else {
504 7         17 @locales = $cache_key = $locale;
505             }
506              
507 9         42 return $cache_key, @locales;
508             }
509              
510             sub __extend_locales {
511 2     2   5 my (@locales) = @_;
512              
513 2         4 my @tries = @locales;
514 2         4 my %locale_lookup = map { $_ => $_ } @tries;
  2         9  
515              
516 2         5 foreach my $locale (@locales) {
517 2 100       11 if ($locale =~ /^([a-z][a-z])
518             (?:(_[A-Z][A-Z])?
519             (\.[-_A-Za-z0-9]+)?
520             )?
521             (\@[-_A-Za-z0-9]+)?$/x) {
522            
523 1 50       5 if (defined $3) {
524 0 0       0 defined $2 ?
525             push @tries, $1 . $2 . $3 : push @tries, $1 . $3;
526 0         0 $locale_lookup{$tries[-1]} = $locale;
527             }
528 1 50       4 if (defined $2) {
529 1         4 push @tries, $1 . $2;
530 1         4 $locale_lookup{$1 . $2} = $locale;
531             }
532 1 50       3 if (defined $1) {
533 1 50       4 push @tries, $1 if defined $1;
534 1         4 $locale_lookup{$1} = $locale;
535             }
536             }
537             }
538              
539 2         7 return \@tries, \%locale_lookup;
540             }
541              
542             sub __load_domain {
543 664     664   1421 my ($domainname, $category, $category_name, $locale) = @_;
544              
545             # If no locale was selected for the requested locale category,
546             # l10n is disabled completely. This matches the behavior of GNU
547             # gettext.
548 664 50       10069 if ($category != LC_MESSAGES) {
549             # Not supported.
550 0         0 return [];
551             }
552            
553 664 100 66     2182 if (!defined $locale && $category != 1729) {
554 655         1864 $locale = POSIX::setlocale ($category);
555 655 50 33     2041 if (!defined $locale || 'C' eq $locale || 'POSIX' eq $locale) {
      33        
556 655         1306 return [];
557             }
558             }
559            
560 9 50 33     47 $domainname = $__gettext_pp_textdomain
561             unless defined $domainname && length $domainname;
562              
563 9         19 my $dir = bindtextdomain ($domainname, '');
564 9 50 33     28 $dir = $__gettext_pp_default_dir unless defined $dir && length $dir;
565              
566 9 50 33     27 return [] unless defined $dir && length $dir;
567              
568 9         20 my ($cache_key, @locales) = __selected_locales $locale, $category, $category_name;
569              
570             # Have we looked that one up already?
571 9         27 my $domains = $__gettext_pp_domain_cache->{$dir}->{$cache_key}->{$category_name}->{$domainname};
572 9 100       25 return $domains if defined $domains;
573 2 50       5 return [] unless @locales;
574            
575 2         3 my @dirs = ($dir);
576 2         7 my ($tries, $lookup) = __extend_locales @locales;
577              
578 2 50 33     11 push @dirs, $__gettext_pp_default_dir
579             if $__gettext_pp_default_dir && $dir ne $__gettext_pp_default_dir;
580            
581 2         4 my %seen;
582             my %loaded;
583 2         5 foreach my $basedir (@dirs) {
584 4         8 foreach my $try (@$tries) {
585             # If we had already found a catalog for "xy_XY", do not try it
586             # again.
587 8 100       25 next if $loaded{$try};
588              
589 4         58 my $fulldir = File::Spec->catfile($basedir, $try, $category_name);
590 4 50       19 next if $seen{$fulldir}++;
591              
592             # If the cache for unavailable directories is removed,
593             # the three lines below should be replaced by:
594             # 'next unless -d $fulldir;'
595 4 50       10 next if $__gettext_pp_unavailable_dirs->{$fulldir};
596 4 100 50     111 ++$__gettext_pp_unavailable_dirs->{$fulldir} and next
597             unless -d $fulldir;
598 2         21 my $filename = File::Spec->catfile($fulldir, "$domainname.mo");
599 2         16 my $domain = __load_catalog $filename, $try;
600 2 50       6 next unless $domain;
601            
602 2         4 $loaded{$try} = 1;
603              
604 2         5 $domain->{locale_id} = $lookup->{$try};
605 2         6 push @$domains, $domain;
606             }
607             }
608              
609 2 100       7 $domains = [] unless defined $domains;
610            
611             $__gettext_pp_domain_cache->{$dir}
612             ->{$cache_key}
613             ->{$category_name}
614 2         7 ->{$domainname} = $domains;
615              
616 2         10 return $domains;
617             }
618              
619             sub __load_catalog
620             {
621 2     2   13 my ($filename, $locale) = @_;
622            
623             # Alternatively we could check the filename for evil characters ...
624             # (Important for CGIs).
625 2 50 33     61 return unless -f $filename && -r $filename;
626            
627 2         9 local $/;
628 2         8 local *HANDLE;
629            
630 2 50       66 open HANDLE, "<$filename"
631             or return;
632 2         9 binmode HANDLE;
633 2         62 my $raw = ;
634 2         19 close HANDLE;
635            
636             # Corrupted?
637 2 50 33     14 return if ! defined $raw || length $raw < 28;
638            
639 2         4 my $filesize = length $raw;
640            
641             # Read the magic number in order to determine the byte order.
642 2         6 my $domain = {
643             filename => $filename
644             };
645 2         4 my $unpack = 'N';
646 2         13 $domain->{magic} = unpack $unpack, substr $raw, 0, 4;
647            
648 2 50       7 if ($domain->{magic} == 0xde120495) {
    0          
649 2         4 $unpack = 'V';
650             } elsif ($domain->{magic} != 0x950412de) {
651 0         0 return;
652             }
653 2         7 my $domain_unpack = $unpack x 6;
654            
655 2         10 my ($revision, $num_strings, $msgids_off, $msgstrs_off,
656             $hash_size, $hash_off) =
657             unpack (($unpack x 6), substr $raw, 4, 24);
658            
659 2         5 my $major = $revision >> 16;
660 2 50       5 return if $major != 0; # Invalid revision number.
661            
662 2         4 $domain->{revision} = $revision;
663 2         5 $domain->{num_strings} = $num_strings;
664 2         3 $domain->{msgids_off} = $msgids_off;
665 2         4 $domain->{msgstrs_off} = $msgstrs_off;
666 2         3 $domain->{hash_size} = $hash_size;
667 2         5 $domain->{hash_off} = $hash_off;
668            
669 2 50       8 return if $msgids_off + 4 * $num_strings > $filesize;
670 2 50       4 return if $msgstrs_off + 4 * $num_strings > $filesize;
671            
672 2         13 my @orig_tab = unpack (($unpack x (2 * $num_strings)),
673             substr $raw, $msgids_off, 8 * $num_strings);
674 2         9 my @trans_tab = unpack (($unpack x (2 * $num_strings)),
675             substr $raw, $msgstrs_off, 8 * $num_strings);
676            
677 2         4 my $messages = {};
678            
679 2         6 for (my $count = 0; $count < 2 * $num_strings; $count += 2) {
680 22         33 my $orig_length = $orig_tab[$count];
681 22         30 my $orig_offset = $orig_tab[$count + 1];
682 22         30 my $trans_length = $trans_tab[$count];
683 22         28 my $trans_offset = $trans_tab[$count + 1];
684            
685 22 50       38 return if $orig_offset + $orig_length > $filesize;
686 22 50       33 return if $trans_offset + $trans_length > $filesize;
687            
688 22         47 my @origs = split /\000/, substr $raw, $orig_offset, $orig_length;
689 22         46 my @trans = split /\000/, substr $raw, $trans_offset, $trans_length;
690            
691             # The singular is the key, the plural plus all translations is the
692             # value.
693 22         34 my $msgid = $origs[0];
694 22 100 66     68 $msgid = '' unless defined $msgid && length $msgid;
695 22         41 my $msgstr = [ $origs[1], @trans ];
696 22         72 $messages->{$msgid} = $msgstr;
697             }
698            
699 2         7 $domain->{messages} = $messages;
700            
701             # Try to find po header information.
702 2         2 my $po_header = {};
703 2         5 my $null_entry = $messages->{''}->[1];
704 2 50       5 if ($null_entry) {
705 2         16 my @lines = split /\n/, $null_entry;
706 2         6 foreach my $line (@lines) {
707 17         43 my ($key, $value) = split /:/, $line, 2;
708 17         49 $key =~ s/-/_/g;
709 17         47 $po_header->{lc $key} = $value;
710             }
711             }
712 2         5 $domain->{po_header} = $po_header;
713            
714 2 50       5 if (exists $domain->{po_header}->{content_type}) {
715 2         5 my $content_type = $domain->{po_header}->{content_type};
716 2 50       12 if ($content_type =~ s/.*=//) {
717 2         6 $domain->{po_header}->{charset} = $content_type;
718             }
719             }
720            
721 2   100     8 my $code = $domain->{po_header}->{plural_forms} || '';
722            
723             # Whitespace, locale-independent.
724 2         11 my $s = '[ \011-\015]';
725              
726             # Untaint the plural header.
727             # Keep line breaks as is (Perl 5_005 compatibility).
728             $code = $domain->{po_header}->{plural_forms}
729 2         7 = __untaint_plural_header $code;
730              
731 2         7 $domain->{plural_func} = __compile_plural_function $code;
732              
733 2 50 33     24 unless (defined $domain->{po_header}->{charset}
      33        
734             && length $domain->{po_header}->{charset}
735             && $locale =~ /^(?:[a-z][a-z])
736             (?:(?:_[A-Z][A-Z])?
737             (\.[-_A-Za-z0-9]+)?
738             )?
739             (?:\@[-_A-Za-z0-9]+)?$/x) {
740 0         0 $domain->{po_header}->{charset} = $1;
741             }
742            
743 2 50       5 if (defined $domain->{po_header}->{charset}) {
744             $domain->{po_header}->{charset} =
745 2         16 Locale::Recode->resolveAlias ($domain->{po_header}->{charset});
746             }
747            
748 2         14 return $domain;
749             }
750              
751             sub __locale_category
752             {
753 5     5   12 my ($category, $category_name) = @_;
754            
755 5         6 local $@;
756 5         9 my $value = eval {POSIX::setlocale ($category)};
  5         14  
757            
758             # We support only XPG syntax, i. e.
759             # language[_territory[.codeset]][@modifier].
760 5 50 33     31 undef $value unless (defined $value &&
      33        
761             length $value &&
762             $value =~ /^[a-z][a-z]
763             (?:_[A-Z][A-Z]
764             (?:\.[-_A-Za-z0-9]+)?
765             )?
766             (?:\@[-_A-Za-z0-9]+)?$/x);
767              
768 5 50       19 unless ($value) {
769 5         41 $value = $ENV{LC_ALL};
770 5 100 66     19 $value = $ENV{$category_name} unless defined $value && length $value;
771 5 100 66     15 $value = $ENV{LANG} unless defined $value && length $value;
772 5 100 66     31 return 'C' unless defined $value && length $value;
773             }
774            
775 2 50 33     12 return $value if $value ne 'C' && $value ne 'POSIX';
776             }
777              
778             sub __get_codeset
779             {
780 5     5   11 my ($category, $category_name, $locale_id) = @_;
781              
782 5         7 local $@;
783 5 100       11 unless (defined $has_nl_langinfo) {
784 1         3 eval {
785 1         472 require I18N::Langinfo;
786             };
787 1         607 $has_nl_langinfo = !$@;
788             }
789              
790 5 50       12 if ($has_nl_langinfo) {
791             # Try to set the locale via the specified id.
792 5         8 my $saved_locale = eval { POSIX::setlocale (LC_ALL) };
  5         11  
793 5         12 my $had_lc_all = exists $ENV{LC_ALL};
794 5 100       9 my $saved_lc_all = $ENV{LC_ALL} if $had_lc_all;
795              
796             # Now try to set the locale via the environment. There is no
797             # point in calling the langinfo routines if this fails.
798 5         25 $ENV{LC_ALL} = $locale_id;
799 5         9 my $codeset;
800 5         7 my $lc_all = eval { POSIX::setlocale (LC_ALL, $locale_id); };
  5         9  
801 5 50       16 $codeset = I18N::Langinfo::langinfo (I18N::Langinfo::CODESET())
802             if defined $lc_all;
803              
804             # Restore environment.
805 5 50       10 if ($saved_locale) {
806 5         6 eval { POSIX::setlocale (LC_ALL, $saved_locale); }
  5         9  
807             }
808 5 100       12 if ($had_lc_all) {
809 1 50       23 $ENV{LC_ALL} = $saved_lc_all if $had_lc_all;
810             } else {
811 4         31 delete $ENV{LC_ALL};
812             }
813 5         16 return $codeset;
814             }
815              
816 0         0 return;
817             }
818            
819             sub __untaint_plural_header {
820 3     3   16 my ($code) = @_;
821              
822             # Whitespace, locale-independent.
823 3         72 my $s = '[ \t\r\n\013\014]';
824              
825 3 100       174 if ($code =~ m{^($s*
826             nplurals$s*=$s*[0-9]+
827             $s*;$s*
828             plural$s*=$s*(?:$s|[-\?\|\&=!<>+*/\%:;a-zA-Z0-9_\(\)])+
829             )}xms) {
830 2         12 return $1;
831             }
832              
833 1         6 return '';
834             }
835              
836             sub __compile_plural_function {
837 3     3   129 my ($code) = @_;
838              
839             # The leading and trailing space is necessary to be able to match
840             # against word boundaries.
841 3         6 my $plural_func;
842            
843 3 100       11 if (length $code) {
844 2         8 my $code = ' ' . $code . ' ';
845 2         31 $code =~
846             s/(?<=[^_a-zA-Z0-9])[_a-z][_A-Za-z0-9]*(?=[^_a-zA-Z0-9])/\$$&/gs;
847            
848 2         18 $code = "sub { my \$n = shift || 0;
849             my (\$plural, \$nplurals);
850             $code;
851             return (\$nplurals, \$plural ? \$plural : 0); }";
852            
853             # Now try to evaluate the code. There is no need to run the code in
854             # a Safe compartment. The above substitutions should have destroyed
855             # all evil code. Corrections are welcome!
856             #warn $code;
857 2         245 $plural_func = eval $code;
858             #warn $@ if $@;
859 2 50       12 undef $plural_func if $@;
860             }
861            
862             # Default is Germanic plural (which is incorrect for French).
863 3 100       103 $plural_func = eval "sub { (2, 1 != shift || 0) }" unless $plural_func;
864              
865 3         11 return $plural_func;
866             }
867              
868             1;
869              
870             __END__