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   27250 use 5.008;
  11         1742  
10 11     11   461 use strict;
  11         25  
  11         1860  
11 11     11   67 use warnings;
  11         28  
  11         348  
12 11     11   57 use base qw(Locale::Maketext Exporter);
  11         20  
  11         6143  
13 11     11   136074 use vars qw($VERSION @ISA %Lexicon @EXPORT @EXPORT_OK);
  11         27  
  11         1137  
14             $VERSION = 1.30;
15             @EXPORT = qw(read_mo);
16             @EXPORT_OK = @EXPORT;
17             # Prototype declaration
18             sub read_mo($);
19              
20 11     11   2281 use Encode qw(encode decode FB_DEFAULT);
  11         40799  
  11         761  
21 11     11   77 use File::Spec::Functions qw(catfile);
  11         22  
  11         598  
22 11     11   72 no strict qw(refs);
  11         21  
  11         310  
23              
24 11     11   56 use vars qw(%CACHE $REREAD_MO $MO_FILE);
  11         22  
  11         759  
25             %CACHE = qw();
26             $REREAD_MO = 0;
27             $MO_FILE = "";
28 11     11   69 use vars qw(@SYSTEM_LOCALEDIRS);
  11         21  
  11         30481  
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 252 local ($_, %_);
35 52         78 my $self;
36 52         125 ($self, $_) = @_;
37            
38             # This is not a static method
39 52 50       138 return if ref($self) eq "";
40            
41             # Set the output encoding
42 52 100       150 if (@_ > 1) {
43 48 100       98 if (defined $_) {
44 19         35 $self->{"ENCODING"} = $_;
45             } else {
46 29         63 delete $self->{"ENCODING"};
47             }
48 48         98 $self->{"USERSET_ENCODING"} = $_;
49             }
50            
51             # Return the encoding
52 52 100       149 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 81 local ($_, %_);
58 29         43 my $self;
59 29         73 ($self, $_) = @_;
60            
61             # This is not a static method
62 29 50       91 return if ref($self) eq "";
63            
64             # Set the encoding used in the keys
65 29 50       72 if (@_ > 1) {
66 29 100       69 if (defined $_) {
67 2         5 $self->{"KEY_ENCODING"} = $_;
68             } else {
69 27         70 delete $self->{"KEY_ENCODING"};
70             }
71             }
72            
73             # Return the encoding
74 29 100       90 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 48552 local ($_, %_);
80 155         286 my ($self, $class);
81 155   33     617 $class = ref($_[0]) || $_[0];
82 155         369 $self = bless {}, $class;
83 155         517 $self->subclass_init;
84 155         620 $self->init;
85 155         958 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 254 local ($_, %_);
92 155         239 my ($self, $class);
93 155         245 $self = $_[0];
94 155         275 $class = ref($self);
95             # Initialize the instance lexicon
96 155         537 $self->{"Lexicon"} = {};
97             # Initialize the LOCALEDIRS registry
98 155         285 $self->{"LOCALEDIRS"} = {};
99             # Initialize the MO timestamp
100 155         297 $self->{"REREAD_MO"} = $REREAD_MO;
101             # Initialize the DIE_FOR_LOOKUP_FAILURES setting
102 155         278 $self->{"DIE_FOR_LOOKUP_FAILURES"} = 0;
103 155         970 $self->SUPER::fail_with($self->can("failure_handler_auto"));
104             # Initialize the ENCODE_FAILURE setting
105 155         1305 $self->{"ENCODE_FAILURE"} = FB_DEFAULT;
106             # Initialize the MO_FILE value of this instance
107 155         372 $self->{"MO_FILE"} = "";
108 155 100       244 ${"$class\::MO_FILE"} = "" if !defined ${"$class\::MO_FILE"};
  43         175  
  155         758  
109             # Find the locale name, for this subclass
110 155         495 $self->{"LOCALE"} = $class;
111 155         824 $self->{"LOCALE"} =~ s/^.*:://;
112 155         599 $self->{"LOCALE"} =~ s/(_)(.*)$/$1 . uc $2/e;
  63         419  
113             # Map i_default to C
114 155 50       427 $self->{"LOCALE"} = "C" if $self->{"LOCALE"} eq "i_default";
115             # Set the category. Currently this is always LC_MESSAGES
116 155         302 $self->{"CATEGORY"} = "LC_MESSAGES";
117             # Default key encoding is US-ASCII
118 155         262 $self->{"KEY_ENCODING"} = "US-ASCII";
119 155         362 return;
120             }
121              
122             # bindtextdomain: Bind a text domain to a locale directory
123             sub bindtextdomain : method {
124 71     71 1 406 local ($_, %_);
125 71         131 my ($self, $DOMAIN, $LOCALEDIR);
126 71         174 ($self, $DOMAIN, $LOCALEDIR) = @_;
127            
128             # This is not a static method
129 71 50       190 return if ref($self) eq "";
130            
131             # Return null for this rare case
132             return if !defined $LOCALEDIR
133 71 100 100     177 && !exists ${$self->{"LOCALEDIRS"}}{$DOMAIN};
  2         19  
134            
135             # Register the DOMAIN and its LOCALEDIR
136 70 100       177 ${$self->{"LOCALEDIRS"}}{$DOMAIN} = $LOCALEDIR if defined $LOCALEDIR;
  69         186  
137            
138             # Return the registry
139 70         123 return ${$self->{"LOCALEDIRS"}}{$DOMAIN};
  70         194  
140             }
141              
142             # textdomain: Set the current text domain
143             sub textdomain : method {
144 82     82 1 636 local ($_, %_);
145 82         157 my ($self, $class, $DOMAIN, $LOCALEDIR, $mo_file);
146 82         201 ($self, $DOMAIN) = @_;
147            
148             # This is not a static method
149 82 50       248 return if ref($self) eq "";
150             # Find the class name
151 82         143 $class = ref($self);
152            
153             # Return the current domain
154 82 100       187 return $self->{"DOMAIN"} if !defined $DOMAIN;
155            
156             # Set the timestamp of this read in this instance
157 78         145 $self->{"REREAD_MO"} = $REREAD_MO;
158             # Set the current domain
159 78         151 $self->{"DOMAIN"} = $DOMAIN;
160            
161             # Clear it
162 78         160 $self->{"Lexicon"} = {};
163 78         124 %{"$class\::Lexicon"} = qw();
  78         295  
164 78         147 $self->{"MO_FILE"} = "";
165 78         111 ${"$class\::MO_FILE"} = "";
  78         197  
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       119 if (!exists ${$self->{"LOCALEDIRS"}}{$DOMAIN}) {
  78         225  
171 4         7 undef $mo_file;
172 4         10 foreach $LOCALEDIR (@SYSTEM_LOCALEDIRS) {
173             $_ = catfile($LOCALEDIR, $self->{"LOCALE"},
174 13         80 $self->{"CATEGORY"}, "$DOMAIN.mo");
175 13 100 66     280 if (-f $_ && -r $_) {
176 1         3 $mo_file = $_;
177 1         3 last;
178             }
179             }
180             # Not found at last
181 4 100       24 return $DOMAIN if !defined $mo_file;
182            
183             # This domain was registered
184             } else {
185 74         519 $mo_file = catfile(${$self->{"LOCALEDIRS"}}{$DOMAIN},
186 74         126 $self->{"LOCALE"}, $self->{"CATEGORY"}, "$DOMAIN.mo");
187             }
188            
189             # Record it
190 75         173 ${"$class\::MO_FILE"} = $mo_file;
  75         215  
191 75         147 $self->{"MO_FILE"} = $mo_file;
192            
193             # Read the MO file
194             # Cached
195 75 100       256 if (!$self->_is_using_cache($mo_file)) {
196 52         100 my ($enc, @stats, $mtime, $size);
197             # Read it
198 52         146 %_ = read_mo($mo_file);
199            
200             # Successfully read
201 52 100       219 if (scalar(keys %_) > 0) {
202             # Decode it
203             # Find the encoding of that MO file
204 47 50       419 if ($_{""} =~ /^Content-Type: text\/plain; charset=(.*)$/im) {
205 47         162 $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 47         339 $_{$_} = decode($enc, $_{$_}) foreach keys %_;
212             }
213            
214             # Cache them
215 52         86368 @stats = stat $mo_file;
216 52 100       231 if (@stats > 0) {
217 49         163 ($mtime, $size) = @stats[9,7];
218             } else {
219 3         8 ($mtime, $size) = (undef, undef);
220             }
221 52         661 $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       284 if (defined $CACHE{$mo_file}->{"encoding"}) {
231 70         177 $self->{"MO_ENCODING"} = $CACHE{$mo_file}->{"encoding"};
232             } else {
233 5         11 delete $self->{"MO_ENCODING"};
234             }
235             # Respect the MO file encoding unless there is a user preferrence
236 75 100       203 if (!exists $self->{"USERSET_ENCODING"}) {
237 70 100       155 if (exists $self->{"MO_ENCODING"}) {
238 65         136 $self->{"ENCODING"} = $self->{"MO_ENCODING"};
239             } else {
240 5         9 delete $self->{"ENCODING"};
241             }
242             }
243 75         173 $self->{"Lexicon"} = $CACHE{$mo_file}->{"Lexicon"};
244 75         107 %{"$class\::Lexicon"} = %{$CACHE{$mo_file}->{"Lexicon"}};
  75         574  
  75         284  
245 75         610 $self->clear_isa_scan;
246            
247 75         596 return $DOMAIN;
248             }
249              
250             # _is_using_cache: Return whether we are using our cache.
251             sub _is_using_cache : method {
252 75     75   139 local ($_, %_);
253 75         145 my ($self, $mo_file, @stats, $mtime, $size);
254 75         145 ($self, $mo_file) = @_;
255            
256             # NO if we do not have such a cache.
257 75 100       328 return undef unless exists $CACHE{$mo_file};
258            
259 25         570 @stats = stat $mo_file;
260             # The MO file does not exist previously.
261 25 50 33     247 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         66 ($mtime, $size) = @stats[9,7];
270             return $mtime == $CACHE{$mo_file}->{"mtime"}
271 25   66     188 && $size == $CACHE{$mo_file}->{"size"};
272             }
273             }
274              
275             # maketext: Encode after maketext
276             sub maketext : method {
277 194     194 1 4045 local ($_, %_);
278 194         334 my ($self, $key, @param, $class, $keyd);
279 194         420 ($self, $key, @param) = @_;
280            
281             # This is not a static method - NOW
282 194 50       465 return if ref($self) eq "";
283             # Find the class name
284 194         324 $class = ref($self);
285            
286             # MO file should be re-read
287 194 100       473 if ($self->{"REREAD_MO"} < $REREAD_MO) {
288 2         4 $self->{"REREAD_MO"} = $REREAD_MO;
289 2 50       58 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       281 if (${"$class\::MO_FILE"} ne $self->{"MO_FILE"}) {
  194         821  
300 16         26 ${"$class\::MO_FILE"} = $self->{"MO_FILE"};
  16         36  
301 16         28 %{"$class\::Lexicon"} = %{$self->{"Lexicon"}};
  16         68  
  16         42  
302             }
303            
304             # Decode the source text
305 194         341 $keyd = $key;
306             $keyd = decode($self->{"KEY_ENCODING"}, $keyd, $self->{"ENCODE_FAILURE"})
307 194 100 66     953 if exists $self->{"KEY_ENCODING"} && !Encode::is_utf8($key);
308             # Maketext
309 194         5561 $_ = $self->SUPER::maketext($keyd, @param);
310             # Output to the requested encoding
311 185 100 100     8924 if (exists $self->{"ENCODING"}) {
    100 66        
312 74         199 $_ = encode($self->{"ENCODING"}, $_, $self->{"ENCODE_FAILURE"});
313             # Pass through the empty/invalid lexicon
314 111         473 } elsif ( scalar(keys %{$self->{"Lexicon"}}) == 0
315             && exists $self->{"KEY_ENCODING"}
316             && !Encode::is_utf8($key)) {
317 7         25 $_ = encode($self->{"KEY_ENCODING"}, $_, $self->{"ENCODE_FAILURE"});
318             }
319            
320 184         13276 return $_;
321             }
322              
323             # pmaketext: Maketext with context
324             sub pmaketext : method {
325 9     9 1 49 local ($_, %_);
326 9         16 my ($self, $ctxt, $key, @param);
327 9         22 ($self, $ctxt, $key, @param) = @_;
328             # This is not a static method - NOW
329 9 50       24 return if ref($self) eq "";
330             # This is actually a wrapper to the maketext() method
331 9         41 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 53     53 1 264 local ($_, %_);
338 53         91 my ($mo_file, $len, $FH, $content, $tmpl);
339 53         99 $mo_file = $_[0];
340            
341             # Avild being stupid
342 53 100 66     1771 return unless -f $mo_file && -r $mo_file;
343             # Read the MO file
344 50         719 $len = (stat $mo_file)[7];
345 50 50       1897 open $FH, $mo_file or return; # GNU gettext never fails!
346 50         228 binmode $FH;
347 50 50       1208 defined($_ = read $FH, $content, $len)
348             or return;
349 50 50       532 close $FH or return;
350            
351             # Find the byte order of the MO file creator
352 50         206 $_ = substr($content, 0, 4);
353             # Little endian
354 50 100       223 if ($_ eq "\xde\x12\x04\x95") {
    100          
355 45         81 $tmpl = "V";
356             # Big endian
357             } elsif ($_ eq "\x95\x04\x12\xde") {
358 3         6 $tmpl = "N";
359             # Wrong magic number. Not a valid MO file.
360             } else {
361 2         14 return;
362             }
363            
364             # Check the MO format revision number
365 48         203 $_ = unpack $tmpl, substr($content, 4, 4);
366             # There is only one revision now: revision 0.
367 48 50       137 return if $_ > 0;
368            
369 48         93 my ($num, $offo, $offt);
370             # Number of messages
371 48         107 $num = unpack $tmpl, substr($content, 8, 4);
372             # Offset to the beginning of the original messages
373 48         104 $offo = unpack $tmpl, substr($content, 12, 4);
374             # Offset to the beginning of the translated messages
375 48         95 $offt = unpack $tmpl, substr($content, 16, 4);
376 48         102 %_ = qw();
377 48         147 for ($_ = 0; $_ < $num; $_++) {
378 643         927 my ($len, $off, $stro, $strt);
379             # The first word is the length of the message
380 643         1180 $len = unpack $tmpl, substr($content, $offo+$_*8, 4);
381             # The second word is the offset of the message
382 643         1229 $off = unpack $tmpl, substr($content, $offo+$_*8+4, 4);
383             # Original message
384 643         1061 $stro = substr($content, $off, $len);
385            
386             # The first word is the length of the message
387 643         1104 $len = unpack $tmpl, substr($content, $offt+$_*8, 4);
388             # The second word is the offset of the message
389 643         1168 $off = unpack $tmpl, substr($content, $offt+$_*8+4, 4);
390             # Translated message
391 643         1147 $strt = substr($content, $off, $len);
392            
393             # Hash it
394 643         1868 $_{$stro} = $strt;
395             }
396            
397 48         669 return %_;
398             }
399              
400             # reload_text: Method to purge the lexicon cache
401             sub reload_text : method {
402 2     2 1 13 local ($_, %_);
403            
404             # Purge the text cache
405 2         22 %CACHE = qw();
406 2         7 $REREAD_MO = time;
407            
408 2         7 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 18 local ($_, %_);
416 2         3 my $self;
417 2         4 ($self, $_) = @_;
418            
419             # This is not a static method
420 2 50       7 return if ref($self) eq "";
421            
422             # Set the current setting
423 2 50       5 if (@_ > 1) {
424 2 50       15 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       8 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 184 local ($_, %_);
441 41         65 my $self;
442 41         81 ($self, $_) = @_;
443            
444             # This is not a static method
445 41 50       109 return if ref($self) eq "";
446            
447             # Set the current setting
448 41 50       114 if (@_ > 1) {
449 41 100       85 if ($_) {
450 4         8 $self->{"DIE_FOR_LOOKUP_FAILURES"} = 1;
451 4 100       9 if (exists $self->{"USERSET_FAIL"}) {
452 3         7 $self->{"fail"} = $self->{"USERSET_FAIL"};
453             } else {
454 1         3 delete $self->{"fail"};
455             }
456             } else {
457 37         215 $self->SUPER::fail_with($self->can("failure_handler_auto"));
458 37         319 $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       129 $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         4 my $self;
472 2         5 ($self, $_) = @_;
473            
474             # This is not a static method
475 2 50       7 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       8 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 4263 local ($_, %_);
490 69         126 my ($self, $key, @param, $r);
491 69         135 ($self, $key, @param) = @_;
492            
493             # This is not a static method
494 69 50       183 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         223 $key =~ s/^[^\x04]*\x04//;
500            
501 69 100       239 $self->{"failure_lex"} = {} if !exists $self->{"failure_lex"};
502 36         992 ${$self->{"failure_lex"}}{$key} = $self->_compile($key)
503 69 100       107 if !exists ${$self->{"failure_lex"}}{$key};
  69         285  
504 69         110 $_ = ${$self->{"failure_lex"}}{$key};
  69         140  
505            
506             # A scalar result
507 69 100       315 return $$_ if ref($_) eq "SCALAR";
508 8 50       33 return $_ unless ref($_) eq "CODE";
509             # A compiled subroutine
510             {
511 8         12 local $SIG{"__DIE__"};
  8         27  
512 8         15 $r = eval {
513 8         178 $_ = &$_($self, @param);
514 8         309 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       22 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         28 # OK
530             return $_;
531             }
532              
533             return 1;
534              
535             __END__