File Coverage

blib/lib/Locale/Maketext/Gettext.pm
Criterion Covered Total %
statement 259 268 96.6
branch 93 124 75.0
condition 18 27 66.6
subroutine 25 25 100.0
pod 12 14 85.7
total 407 458 88.8


line stmt bran cond sub pod time code
1             # Locale::Maketext::Gettext - Joins the gettext and Maketext frameworks
2              
3             # Copyright (c) 2003-2019 imacat. All rights reserved. This program is free
4             # software; you can redistribute it and/or modify it under the same terms
5             # as Perl itself.
6             # First written: 2003-04-23
7              
8             package Locale::Maketext::Gettext;
9 11     11   34887 use 5.008;
  11         1699  
10 11     11   52 use strict;
  11         20  
  11         1843  
11 11     11   48 use warnings;
  11         26  
  11         343  
12 11     11   50 use base qw(Locale::Maketext Exporter);
  11         16  
  11         5775  
13 11     11   115890 use vars qw($VERSION @ISA %Lexicon @EXPORT @EXPORT_OK);
  11         28  
  11         999  
14             $VERSION = 1.29;
15             @EXPORT = qw(read_mo);
16             @EXPORT_OK = @EXPORT;
17             # Prototype declaration
18             sub read_mo($);
19              
20 11     11   2161 use Encode qw(encode decode FB_DEFAULT);
  11         37241  
  11         715  
21 11     11   68 use File::Spec::Functions qw(catfile);
  11         22  
  11         416  
22 11     11   55 no strict qw(refs);
  11         24  
  11         271  
23              
24 11     11   51 use vars qw(%CACHE $REREAD_MO $MO_FILE);
  11         18  
  11         696  
25             %CACHE = qw();
26             $REREAD_MO = 0;
27             $MO_FILE = "";
28 11     11   55 use vars qw(@SYSTEM_LOCALEDIRS);
  11         21  
  11         25403  
29             @SYSTEM_LOCALEDIRS = qw(/usr/share/locale /usr/lib/locale
30             /usr/local/share/locale /usr/local/lib/locale);
31              
32             # encoding: Set or retrieve the output encoding
33             sub encoding : method {
34 52     52 1 219 local ($_, %_);
35 52         69 my $self;
36 52         103 ($self, $_) = @_;
37            
38             # This is not a static method
39 52 50       122 return if ref($self) eq "";
40            
41             # Set the output encoding
42 52 100       121 if (@_ > 1) {
43 48 100       92 if (defined $_) {
44 19         34 $self->{"ENCODING"} = $_;
45             } else {
46 29         48 delete $self->{"ENCODING"};
47             }
48 48         82 $self->{"USERSET_ENCODING"} = $_;
49             }
50            
51             # Return the encoding
52 52 100       180 return exists $self->{"ENCODING"}? $self->{"ENCODING"}: undef;
53             }
54              
55             # key_encoding: Specify the encoding used in the keys
56             sub key_encoding : method {
57 29     29 1 63 local ($_, %_);
58 29         40 my $self;
59 29         61 ($self, $_) = @_;
60            
61             # This is not a static method
62 29 50       88 return if ref($self) eq "";
63            
64             # Set the encoding used in the keys
65 29 50       77 if (@_ > 1) {
66 29 100       55 if (defined $_) {
67 2         5 $self->{"KEY_ENCODING"} = $_;
68             } else {
69 27         49 delete $self->{"KEY_ENCODING"};
70             }
71             }
72            
73             # Return the encoding
74 29 100       84 return exists $self->{"KEY_ENCODING"}? $self->{"KEY_ENCODING"}: undef;
75             }
76              
77             # new: Initialize the language handler
78             sub new : method {
79 155     155 0 60969 local ($_, %_);
80 155         249 my ($self, $class);
81 155   33     522 $class = ref($_[0]) || $_[0];
82 155         293 $self = bless {}, $class;
83 155         443 $self->subclass_init;
84 155         492 $self->init;
85 155         740 return $self;
86             }
87              
88             # subclass_init: Initialize at the subclass level, so that it can be
89             # inherited by calling $self->SUPER:subclass_init
90             sub subclass_init : method {
91 155     155 0 210 local ($_, %_);
92 155         248 my ($self, $class);
93 155         203 $self = $_[0];
94 155         228 $class = ref($self);
95             # Initialize the instance lexicon
96 155         442 $self->{"Lexicon"} = {};
97             # Initialize the LOCALEDIRS registry
98 155         281 $self->{"LOCALEDIRS"} = {};
99             # Initialize the MO timestamp
100 155         253 $self->{"REREAD_MO"} = $REREAD_MO;
101             # Initialize the DIE_FOR_LOOKUP_FAILURES setting
102 155         211 $self->{"DIE_FOR_LOOKUP_FAILURES"} = 0;
103 155         876 $self->SUPER::fail_with($self->can("failure_handler_auto"));
104             # Initialize the ENCODE_FAILURE setting
105 155         1111 $self->{"ENCODE_FAILURE"} = FB_DEFAULT;
106             # Initialize the MO_FILE value of this instance
107 155         248 $self->{"MO_FILE"} = "";
108 155 100       201 ${"$class\::MO_FILE"} = "" if !defined ${"$class\::MO_FILE"};
  43         160  
  155         622  
109             # Find the locale name, for this subclass
110 155         392 $self->{"LOCALE"} = $class;
111 155         696 $self->{"LOCALE"} =~ s/^.*:://;
112 155         513 $self->{"LOCALE"} =~ s/(_)(.*)$/$1 . uc $2/e;
  63         354  
113             # Map i_default to C
114 155 50       341 $self->{"LOCALE"} = "C" if $self->{"LOCALE"} eq "i_default";
115             # Set the category. Currently this is always LC_MESSAGES
116 155         255 $self->{"CATEGORY"} = "LC_MESSAGES";
117             # Default key encoding is US-ASCII
118 155         208 $self->{"KEY_ENCODING"} = "US-ASCII";
119 155         300 return;
120             }
121              
122             # bindtextdomain: Bind a text domain to a locale directory
123             sub bindtextdomain : method {
124 71     71 1 323 local ($_, %_);
125 71         101 my ($self, $DOMAIN, $LOCALEDIR);
126 71         145 ($self, $DOMAIN, $LOCALEDIR) = @_;
127            
128             # This is not a static method
129 71 50       167 return if ref($self) eq "";
130            
131             # Return null for this rare case
132             return if !defined $LOCALEDIR
133 71 100 100     144 && !exists ${$self->{"LOCALEDIRS"}}{$DOMAIN};
  2         16  
134            
135             # Register the DOMAIN and its LOCALEDIR
136 70 100       143 ${$self->{"LOCALEDIRS"}}{$DOMAIN} = $LOCALEDIR if defined $LOCALEDIR;
  69         167  
137            
138             # Return the registry
139 70         94 return ${$self->{"LOCALEDIRS"}}{$DOMAIN};
  70         165  
140             }
141              
142             # textdomain: Set the current text domain
143             sub textdomain : method {
144 82     82 1 644 local ($_, %_);
145 82         128 my ($self, $class, $DOMAIN, $LOCALEDIR, $mo_file);
146 82         240 ($self, $DOMAIN) = @_;
147            
148             # This is not a static method
149 82 50       204 return if ref($self) eq "";
150             # Find the class name
151 82         130 $class = ref($self);
152            
153             # Return the current domain
154 82 100       160 return $self->{"DOMAIN"} if !defined $DOMAIN;
155            
156             # Set the timestamp of this read in this instance
157 78         123 $self->{"REREAD_MO"} = $REREAD_MO;
158             # Set the current domain
159 78         131 $self->{"DOMAIN"} = $DOMAIN;
160            
161             # Clear it
162 78         132 $self->{"Lexicon"} = {};
163 78         110 %{"$class\::Lexicon"} = qw();
  78         239  
164 78         144 $self->{"MO_FILE"} = "";
165 78         97 ${"$class\::MO_FILE"} = "";
  78         170  
166            
167             # The format is "{LOCALEDIR}/{LOCALE}/{CATEGORY}/{DOMAIN}.mo"
168             # Search the system locale directories if the domain was not
169             # registered yet
170 78 100       215 if (!exists ${$self->{"LOCALEDIRS"}}{$DOMAIN}) {
  78         303  
171 4         8 undef $mo_file;
172 4         12 foreach $LOCALEDIR (@SYSTEM_LOCALEDIRS) {
173             $_ = catfile($LOCALEDIR, $self->{"LOCALE"},
174 13         131 $self->{"CATEGORY"}, "$DOMAIN.mo");
175 13 100 66     330 if (-f $_ && -r $_) {
176 1         4 $mo_file = $_;
177 1         3 last;
178             }
179             }
180             # Not found at last
181 4 100       20 return $DOMAIN if !defined $mo_file;
182            
183             # This domain was registered
184             } else {
185 74         466 $mo_file = catfile(${$self->{"LOCALEDIRS"}}{$DOMAIN},
186 74         87 $self->{"LOCALE"}, $self->{"CATEGORY"}, "$DOMAIN.mo");
187             }
188            
189             # Record it
190 75         148 ${"$class\::MO_FILE"} = $mo_file;
  75         209  
191 75         122 $self->{"MO_FILE"} = $mo_file;
192            
193             # Read the MO file
194             # Cached
195 75 100       274 if (!$self->_is_using_cache($mo_file)) {
196 51         89 my ($enc, @stats, $mtime, $size);
197             # Read it
198 51         116 %_ = read_mo($mo_file);
199            
200             # Successfully read
201 51 100       177 if (scalar(keys %_) > 0) {
202             # Decode it
203             # Find the encoding of that MO file
204 46 50       340 if ($_{""} =~ /^Content-Type: text\/plain; charset=(.*)$/im) {
205 46         128 $enc = $1;
206             # Default to US-ASCII
207             } else {
208 0         0 $enc = "US-ASCII";
209             }
210             # Set the current encoding to the encoding of the MO file
211 46         267 $_{$_} = decode($enc, $_{$_}) foreach keys %_;
212             }
213            
214             # Cache them
215 51         74980 @stats = stat $mo_file;
216 51 100       204 if (@stats > 0) {
217 48         133 ($mtime, $size) = @stats[9,7];
218             } else {
219 3         7 ($mtime, $size) = (undef, undef);
220             }
221 51         657 $CACHE{$mo_file} = {
222             "Lexicon" => {%_},
223             "encoding" => $enc,
224             "mtime" => $mtime,
225             "size" => $size,
226             };
227             }
228            
229             # Respect the existing output encoding
230 75 100       223 if (defined $CACHE{$mo_file}->{"encoding"}) {
231 70         223 $self->{"MO_ENCODING"} = $CACHE{$mo_file}->{"encoding"};
232             } else {
233 5         9 delete $self->{"MO_ENCODING"};
234             }
235             # Respect the MO file encoding unless there is a user preferrence
236 75 100       167 if (!exists $self->{"USERSET_ENCODING"}) {
237 70 100       124 if (exists $self->{"MO_ENCODING"}) {
238 65         114 $self->{"ENCODING"} = $self->{"MO_ENCODING"};
239             } else {
240 5         8 delete $self->{"ENCODING"};
241             }
242             }
243 75         164 $self->{"Lexicon"} = $CACHE{$mo_file}->{"Lexicon"};
244 75         102 %{"$class\::Lexicon"} = %{$CACHE{$mo_file}->{"Lexicon"}};
  75         510  
  75         226  
245 75         546 $self->clear_isa_scan;
246            
247 75         514 return $DOMAIN;
248             }
249              
250             # _is_using_cache: Return whether we are using our cache.
251             sub _is_using_cache : method {
252 75     75   115 local ($_, %_);
253 75         108 my ($self, $mo_file, @stats, $mtime, $size);
254 75         119 ($self, $mo_file) = @_;
255            
256             # NO if we do not have such a cache.
257 75 100       307 return undef unless exists $CACHE{$mo_file};
258            
259 25         497 @stats = stat $mo_file;
260             # The MO file does not exist previously.
261 25 50 33     167 if (!defined $CACHE{$mo_file}->{"mtime"}
262             || !defined $CACHE{$mo_file}->{"size"}) {
263             # Use the cache if the MO file still does not exist.
264 0         0 return (@stats == 0);
265            
266             # The MO file exists previously.
267             } else {
268             # Use the cache if the MO file did not change.
269 25         60 ($mtime, $size) = @stats[9,7];
270             return $mtime == $CACHE{$mo_file}->{"mtime"}
271 25   66     157 && $size == $CACHE{$mo_file}->{"size"};
272             }
273             }
274              
275             # maketext: Encode after maketext
276             sub maketext : method {
277 194     194 1 3896 local ($_, %_);
278 194         298 my ($self, $key, @param, $class, $keyd);
279 194         334 ($self, $key, @param) = @_;
280            
281             # This is not a static method - NOW
282 194 50       404 return if ref($self) eq "";
283             # Find the class name
284 194         268 $class = ref($self);
285            
286             # MO file should be re-read
287 194 100       449 if ($self->{"REREAD_MO"} < $REREAD_MO) {
288 2         3 $self->{"REREAD_MO"} = $REREAD_MO;
289 2 50       7 defined($_ = $self->textdomain) and $self->textdomain($_);
290             }
291            
292             # If the instance lexicon is changed.
293             # Maketext uses a class lexicon. We have to copy the instance
294             # lexicon into the class lexicon. This is slow. Mass memory
295             # copy sucks. Avoid create several language handles for a
296             # single localization subclass whenever possible.
297             # Maketext uses class lexicon in order to track the inheritance.
298             # It is hard to change it.
299 194 100       266 if (${"$class\::MO_FILE"} ne $self->{"MO_FILE"}) {
  194         704  
300 16         22 ${"$class\::MO_FILE"} = $self->{"MO_FILE"};
  16         33  
301 16         19 %{"$class\::Lexicon"} = %{$self->{"Lexicon"}};
  16         62  
  16         38  
302             }
303            
304             # Decode the source text
305 194         289 $keyd = $key;
306             $keyd = decode($self->{"KEY_ENCODING"}, $keyd, $self->{"ENCODE_FAILURE"})
307 194 100 66     818 if exists $self->{"KEY_ENCODING"} && !Encode::is_utf8($key);
308             # Maketext
309 194         4708 $_ = $self->SUPER::maketext($keyd, @param);
310             # Output to the requested encoding
311 185 100 100     7541 if (exists $self->{"ENCODING"}) {
    100 66        
312 74         176 $_ = encode($self->{"ENCODING"}, $_, $self->{"ENCODE_FAILURE"});
313             # Pass through the empty/invalid lexicon
314 111         395 } elsif ( scalar(keys %{$self->{"Lexicon"}}) == 0
315             && exists $self->{"KEY_ENCODING"}
316             && !Encode::is_utf8($key)) {
317 7         20 $_ = encode($self->{"KEY_ENCODING"}, $_, $self->{"ENCODE_FAILURE"});
318             }
319            
320 184         11507 return $_;
321             }
322              
323             # pmaketext: Maketext with context
324             sub pmaketext : method {
325 9     9 1 47 local ($_, %_);
326 9         11 my ($self, $ctxt, $key, @param);
327 9         20 ($self, $ctxt, $key, @param) = @_;
328             # This is not a static method - NOW
329 9 50       19 return if ref($self) eq "";
330             # This is actually a wrapper to the maketext() method
331 9         36 return $self->maketext("$ctxt\x04$key", @param);
332             }
333              
334             # read_mo: Subroutine to read and parse the MO file
335             # Refer to gettext documentation section 8.3
336             sub read_mo($) {
337 52     52 1 221 local ($_, %_);
338 52         88 my ($mo_file, $len, $FH, $content, $tmpl);
339 52         87 $mo_file = $_[0];
340            
341             # Avild being stupid
342 52 100 66     1574 return unless -f $mo_file && -r $mo_file;
343             # Read the MO file
344 49         593 $len = (stat $mo_file)[7];
345 49 50       1688 open $FH, $mo_file or return; # GNU gettext never fails!
346 49         203 binmode $FH;
347 49 50       1031 defined($_ = read $FH, $content, $len)
348             or return;
349 49 50       470 close $FH or return;
350            
351             # Find the byte order of the MO file creator
352 49         173 $_ = substr($content, 0, 4);
353             # Little endian
354 49 100       199 if ($_ eq "\xde\x12\x04\x95") {
    100          
355 44         75 $tmpl = "V";
356             # Big endian
357             } elsif ($_ eq "\x95\x04\x12\xde") {
358 3         4 $tmpl = "N";
359             # Wrong magic number. Not a valid MO file.
360             } else {
361 2         13 return;
362             }
363            
364             # Check the MO format revision number
365 47         174 $_ = unpack $tmpl, substr($content, 4, 4);
366             # There is only one revision now: revision 0.
367 47 50       127 return if $_ > 0;
368            
369 47         76 my ($num, $offo, $offt);
370             # Number of messages
371 47         92 $num = unpack $tmpl, substr($content, 8, 4);
372             # Offset to the beginning of the original messages
373 47         85 $offo = unpack $tmpl, substr($content, 12, 4);
374             # Offset to the beginning of the translated messages
375 47         85 $offt = unpack $tmpl, substr($content, 16, 4);
376 47         85 %_ = qw();
377 47         119 for ($_ = 0; $_ < $num; $_++) {
378 639         787 my ($len, $off, $stro, $strt);
379             # The first word is the length of the message
380 639         985 $len = unpack $tmpl, substr($content, $offo+$_*8, 4);
381             # The second word is the offset of the message
382 639         965 $off = unpack $tmpl, substr($content, $offo+$_*8+4, 4);
383             # Original message
384 639         909 $stro = substr($content, $off, $len);
385            
386             # The first word is the length of the message
387 639         957 $len = unpack $tmpl, substr($content, $offt+$_*8, 4);
388             # The second word is the offset of the message
389 639         924 $off = unpack $tmpl, substr($content, $offt+$_*8+4, 4);
390             # Translated message
391 639         943 $strt = substr($content, $off, $len);
392            
393             # Hash it
394 639         1487 $_{$stro} = $strt;
395             }
396            
397 47         534 return %_;
398             }
399              
400             # reload_text: Method to purge the lexicon cache
401             sub reload_text : method {
402 2     2 1 10 local ($_, %_);
403            
404             # Purge the text cache
405 2         23 %CACHE = qw();
406 2         4 $REREAD_MO = time;
407            
408 2         5 return;
409             }
410              
411             # fail_with: A wrapper to the fail_with() of Locale::Maketext, in order
412             # to record the preferred failure handler of the user, so that
413             # die_for_lookup_failures() knows where to return to.
414             sub fail_with : method {
415 2     2 1 17 local ($_, %_);
416 2         2 my $self;
417 2         4 ($self, $_) = @_;
418            
419             # This is not a static method
420 2 50       5 return if ref($self) eq "";
421            
422             # Set the current setting
423 2 50       6 if (@_ > 1) {
424 2 50       13 if (defined $_) {
425 2         5 $self->{"USERSET_FAIL"} = $_;
426 2 50       5 $self->SUPER::fail_with($_) if $self->{"DIE_FOR_LOOKUP_FAILURES"};
427             } else {
428 0         0 delete $self->{"USERSET_FAIL"};
429 0 0       0 delete $self->{"fail"} if $self->{"DIE_FOR_LOOKUP_FAILURES"};
430             }
431             }
432            
433             # Return the current setting
434 2 50       6 return exists $self->{"USERSET_FAIL"}? $self->{"USERSET_FAIL"}: undef;
435             }
436              
437             # die_for_lookup_failures: Whether we should die for lookup failure
438             # The default is no. GNU gettext never fails.
439             sub die_for_lookup_failures : method {
440 41     41 1 427 local ($_, %_);
441 41         52 my $self;
442 41         77 ($self, $_) = @_;
443            
444             # This is not a static method
445 41 50       110 return if ref($self) eq "";
446            
447             # Set the current setting
448 41 50       87 if (@_ > 1) {
449 41 100       76 if ($_) {
450 4         9 $self->{"DIE_FOR_LOOKUP_FAILURES"} = 1;
451 4 100       8 if (exists $self->{"USERSET_FAIL"}) {
452 3         6 $self->{"fail"} = $self->{"USERSET_FAIL"};
453             } else {
454 1         2 delete $self->{"fail"};
455             }
456             } else {
457 37         178 $self->SUPER::fail_with($self->can("failure_handler_auto"));
458 37         266 $self->{"DIE_FOR_LOOKUP_FAILURES"} = 0;
459             }
460             }
461            
462             # Return the current setting
463             return exists $self->{"DIE_FOR_LOOKUP_FAILURES"}?
464 41 50       119 $self->{"DIE_FOR_LOOKUP_FAILURES"}: undef;
465             }
466              
467             # encode_failure: What to do if the text is out of your output encoding
468             # Refer to Encode on possible values of this check
469             sub encode_failure : method {
470 2     2 1 14 local ($_, %_);
471 2         2 my $self;
472 2         4 ($self, $_) = @_;
473            
474             # This is not a static method
475 2 50       5 return if ref($self) eq "";
476            
477             # Specify the action used in the keys
478 2 50       6 $self->{"ENCODE_FAILURE"} = $_ if @_ > 1;
479            
480             # Return the encoding
481 2 50       6 return $self->{"ENCODE_FAILURE"} if exists $self->{"ENCODE_FAILURE"};
482 0         0 return undef;
483             }
484              
485             # failure_handler_auto: Our local version of failure_handler_auto(),
486             # Copied and rewritten from Locale::Maketext, with bug#33938 patch applied.
487             # See http://rt.perl.org/rt3//Public/Bug/Display.html?id=33938
488             sub failure_handler_auto : method {
489 69     69 1 3883 local ($_, %_);
490 69         105 my ($self, $key, @param, $r);
491 69         120 ($self, $key, @param) = @_;
492            
493             # This is not a static method
494 69 50       144 return if ref($self) eq "";
495            
496             # Remove the context
497             # We assume there is no one using EOF either in the context or message.
498             # That does not work in GNU gettext, anyway.
499 69         190 $key =~ s/^[^\x04]*\x04//;
500            
501 69 100       168 $self->{"failure_lex"} = {} if !exists $self->{"failure_lex"};
502 36         859 ${$self->{"failure_lex"}}{$key} = $self->_compile($key)
503 69 100       97 if !exists ${$self->{"failure_lex"}}{$key};
  69         228  
504 69         99 $_ = ${$self->{"failure_lex"}}{$key};
  69         120  
505            
506             # A scalar result
507 69 100       264 return $$_ if ref($_) eq "SCALAR";
508 8 50       22 return $_ unless ref($_) eq "CODE";
509             # A compiled subroutine
510             {
511 8         9 local $SIG{"__DIE__"};
  8         25  
512 8         10 $r = eval {
513 8         138 $_ = &$_($self, @param);
514 8         263 return 1;
515             };
516             }
517            
518             # If we make it here, there was an exception thrown in the
519             # call to $value, and so scream:
520 8 50       17 if (!defined $r) {
521 0         0 $_ = $@;
522             # pretty up the error message
523 0         0 s<\s+at\s+\(eval\s+\d+\)\s+line\s+(\d+)\.?\n?>
524 0         0 <\n in bracket code [compiled line $1],>s;
525 0         0 Carp::croak "Error in maketexting \"$key\":\n$_ as used";
526             return;
527             }
528            
529 8         23 # OK
530             return $_;
531             }
532              
533             return 1;
534              
535             __END__