File Coverage

blib/lib/HTML/Template/Compiled/Plugin/I18N.pm
Criterion Covered Total %
statement 195 210 92.8
branch 74 98 75.5
condition 6 8 75.0
subroutine 26 27 96.3
pod 5 5 100.0
total 306 348 87.9


line stmt bran cond sub pod time code
1             package HTML::Template::Compiled::Plugin::I18N;
2            
3 16     16   1975334 use strict;
  16         47  
  16         770  
4 16     16   93 use warnings;
  16         34  
  16         784  
5            
6             our $VERSION = '1.04';
7            
8 16     16   125 use Carp qw(croak);
  16         49  
  16         1019  
9 16     16   8122 use English qw(-no_match_vars $EVAL_ERROR);
  16         44120  
  16         151  
10 16     16   20350 use Hash::Util qw(lock_keys);
  16         48422  
  16         114  
11 16     16   3692 use Data::Dumper;
  16         17548  
  16         849  
12 16     16   3076 use HTML::Template::Compiled;
  16         136733  
  16         136  
13 16     16   554 use HTML::Template::Compiled::Token;
  16         34  
  16         802  
14 16     16   12530 use HTML::Template::Compiled::Plugin::I18N::DefaultTranslator;
  16         48  
  16         1599  
15            
16             our (%init, %escape_sub_of); ## no critic (PackageVars)
17            
18             BEGIN {
19 16     16   95 lock_keys(
20             %init,
21             qw(
22             throw
23             allow_maketext
24             allow_gettext
25             allow_formatter
26             allow_unescaped
27             translator_class
28             escape_plugins
29             ),
30             );
31             }
32            
33             sub _require_via_string {
34 15     15   37 my $class = shift;
35            
36 15 50       1511 eval "require $class" ## no critic (stringy eval)
37             or _throw("Can not find package $class $EVAL_ERROR");
38            
39 15         683 return $class;
40             }
41            
42             # class method
43             sub init {
44 14     14 1 284 my ($class, %arg_of) = @_;
45            
46             # This escape plugins are already loaded.
47 14         223 %escape_sub_of = (
48             HTML => \&HTML::Template::Compiled::Utils::escape_html,
49             HTML_ALL => \&HTML::Template::Compiled::Utils::escape_html_all,
50             URI => \&HTML::Template::Compiled::Utils::escape_uri,
51             JS => \&HTML::Template::Compiled::Utils::escape_js,
52             DUMP => \&Dumper,
53             );
54            
55             # Get the escape subs for each plugin ...
56 14         68 my $escape_plugins = delete $arg_of{escape_plugins};
57 14 100       71 if ($escape_plugins) {
58 1 50       6 ref $escape_plugins eq 'ARRAY'
59             or croak 'Parameter escape_plugins is not an array reference';
60 1         3 for my $package ( @{$escape_plugins} ) {
  1         29  
61             # register plugins
62 1         3 my %escape = %{ _require_via_string($package)->register()->{escape} };
  1         4  
63             SUB:
64 1         16 for my $sub ( values %escape ) {
65             # code ref given
66 2 100       10 ref $sub eq 'CODE'
67             and next SUB;
68             # sub name given
69 16     16   5473 no strict qw(refs); ## no critic (NoStrict)
  16         34  
  16         573  
70 16     16   105 no warnings qw(redefine); ## no critic (NoWarnings)
  16         40  
  16         52935  
71 1         3 $sub = \&{$sub};
  1         5  
72             }
73 1         8 @escape_sub_of{ keys %escape } = values %escape;
74             }
75             }
76            
77             # ... and all the other boolenans and strings.
78 14         71 my @keys = keys %arg_of;
79 14         75 @init{@keys} = @arg_of{@keys};
80            
81             # Load the translator class.
82 14   100     412 $init{translator_class} ||= 'HTML::Template::Compiled::Plugin::I18N::DefaultTranslator';
83 14         67 _require_via_string($init{translator_class});
84            
85             # Register this plugin at HTC.
86 14         134 HTML::Template::Compiled->register(__PACKAGE__);
87            
88 14         414 return $class;
89             }
90            
91             # internal exception handler
92             sub _throw {
93 0     0   0 my @message = @_;
94            
95             return
96 0 0       0 ref $init{throw} eq 'CODE'
97             ? $init{throw}->(@message)
98             : croak @message;
99             }
100            
101             # Register this plugin at HTC.
102             sub register {
103 14     14 1 192 my ($class) = @_;
104            
105             return {
106             # opening and closing tags to bind to
107 14 100       354 tagnames => {
    100          
    100          
    100          
108             HTML::Template::Compiled::Token::OPENING_TAG() => {
109             TEXT => [
110             undef,
111             # attributes
112             qw(
113             NAME
114             VALUE
115             ESCAPE
116             ),
117             (
118             $init{allow_maketext}
119             ? qw(
120             _\d+
121             _\d+_VAR
122             )
123             : ()
124             ),
125             (
126             $init{allow_gettext}
127             ? qw(
128             PLURAL
129             PLURAL_VAR
130             COUNT
131             COUNT_VAR
132             CONTEXT
133             CONTEXT_VAR
134             _[A-Z][0-9A-Z_]*?
135             _[A-Z][0-9A-Z_]*?_VAR
136             )
137             : ()
138             ),
139             (
140             $init{allow_formatter}
141             ? qw(
142             FORMATTER
143             )
144             : ()
145             ),
146             (
147             $init{allow_unescaped}
148             ? qw(
149             UNESCAPED_[A-Z][0-9A-Z_]*?
150             UNESCAPED_[A-Z][0-9A-Z_]*?_VAR
151             )
152             : ()
153             ),
154             ],
155             },
156             },
157             compile => {
158             # methods to compile to
159             TEXT => {
160             # on opening tab
161             open => \&TEXT,
162             # if you need closing, uncomment and implement method
163             # close => \&close_text
164             },
165             },
166             };
167             }
168            
169             sub _lookup_variable {
170 33     33   64 my ($htc, $var_name) = @_;
171            
172 33         127 return $htc->get_compiler()->parse_var(
173             $htc,
174             var => $var_name,
175             method_call => $htc->method_call(),
176             deref => $htc->deref(),
177             formatter_path => $htc->formatter_path(),
178             );
179             }
180            
181             sub _calculate_escape {
182 55     55   567 my $arg_ref = shift;
183            
184 55         118 my @real_escapes;
185 55         184 ESCAPE:
186 55         100 for my $escape ( @{ $arg_ref->{escapes} } ) {
187             # a '0' ignores all before
188 95 100       257 if ($escape eq '0') {
189 66         116 @real_escapes = ();
190 66         169 next ESCAPE;
191             }
192 29         88 push @real_escapes, $escape;
193             }
194             # uc escape if no error
195 55         99 my @unknown_escapes;
196             ESCAPE:
197 55         124 for my $escape (@real_escapes) {
198 23 50       105 if ( exists $escape_sub_of{uc $escape} ) {
199 23         46 $escape = uc $escape;
200 23         69 next ESCAPE;
201             }
202 0         0 push @unknown_escapes, $escape;
203             }
204             # write back
205 55 50       184 if ( exists $arg_ref->{escape_ref} ) {
206 55         103 ${ $arg_ref->{escape_ref} } = \@real_escapes;
  55         135  
207             }
208            
209 55 50       230 return @unknown_escapes ? \@unknown_escapes : ();
210             }
211            
212             # Executes all needed escape subs.
213             sub _escape {
214 24     24   63 my ($string, @escapes) = @_;
215            
216             @escapes
217 24 50       80 or return $string;
218 24         55 for (@escapes) {
219 27         278 $string = $escape_sub_of{$_}->($string);
220             }
221            
222 24         774 return $string;
223             }
224            
225             # class method
226             sub escape {
227 24     24 1 3104 my (undef, $string, $escapes) = @_;
228            
229 24         100 return _escape($string, split m{,}xms, $escapes);
230             }
231            
232             # class method
233             sub expand_unescaped {
234 3     3 1 75 my (undef, $string, $arg_ref) = @_;
235            
236 3         6 my $regex = join q{|}, map { quotemeta $_ } keys %{$arg_ref};
  6         19  
  3         11  
237 3         127 $string =~ s{
238             \{ ($regex) \}
239             }{
240 6 50       41 defined $arg_ref->{$1} ? $arg_ref->{$1} : "{$1}"
241             }xmsge;
242            
243 3         17 return $string;
244             }
245            
246             # Prepare a string as Perl code.
247             sub _string_to_perl_code {
248 178     178   287 my $string = shift;
249            
250 178 50       401 defined $string
251             or return q{''};
252 178         347 $string =~ s{\\}{\\}xmsg;
253 178         265 $string =~ s{'}{\\'}xmsg;
254 178         256 $string =~ s{"}{\\"}xmsg;
255            
256 178         947 return "'$string'";
257             }
258            
259             # From here to subroutine TEXT: Caller is subroutine TEXT only.
260            
261             sub _parse_attributes { ## no critic (ExcessComplexity)
262 55     55   119 my ($attr_ref, $filename, $data_ref) = @_;
263            
264 55         97 my $package = __PACKAGE__;
265 55         211 ATTRIBUTE:
266 55         97 for my $name ( keys %{$attr_ref} ) {
267             # parse ESCAPE
268 128 100       367 if ($name eq 'ESCAPE') {
269 18 50       66 if ( length $attr_ref->{$name} ) {
270 18         138 $data_ref->{escape}->{array}
271             = [ split m{\|}xms, "0|$attr_ref->{$name}" ];
272             }
273             }
274 128 100       394 if ( $init{allow_maketext} ) {
275             # parse maketext placeholders
276             # as string constant _1 .. _n
277             # as variable _1_VAR .. _n_VAR
278 23         91 my $is_maketext
279             = my ($position, $is_variable)
280             = $name =~ m{\A _ (\d+) (_VAR)? \z}xms;
281 23 100       62 if ($is_maketext) {
282 12         28 my $index = $position - 1;
283             # _n, _n_VAR
284 12 50       54 if ( exists $data_ref->{maketext}->{array}->[$index] ) {
285 0         0 _throw( qq{Error in template $filename, plugin $package. Can not use maktext position $position twice. $name="$attr_ref->{$name}"} );
286             }
287 12         57 $data_ref->{maketext}->{array}->[$index] = {
288             is_variable => $is_variable,
289             value => $attr_ref->{$name},
290             };
291 12         39 next ATTRIBUTE;
292             }
293             }
294 116 100       339 if ( $init{allow_gettext} ) {
295             # parse gettext placeholders
296             # as string constant _name_1 .. _name_n
297             # as variable _name_1_VAR .. _name_n_VAR
298 50         177 my $is_gettext
299             = my ($key, $is_variable)
300             = $name =~ m{\A _ ([A-Z][0-9A-Z_]*?) (_VAR)? \z}xms;
301 50 100       118 if ($is_gettext) {
302             # _name, _name_VAR
303 16 50       85 if ( exists $data_ref->{gettext}->{hash}->{lc $key} ) {
304 0         0 _throw( qq{Error in template $filename, plugin $package. Can not use gettext key $key twice. $name="$attr_ref->{$name}"} );
305             }
306 16         85 $data_ref->{gettext}->{hash}->{lc $key} = {
307             is_variable => $is_variable,
308             value => $attr_ref->{$name},
309             };
310 16         51 next ATTRIBUTE;
311             }
312             # parse gettext plural
313             # as string constant PLURAL
314             # as variable PLURAL_VAR
315 34         95 my $is_plural
316             = ($is_variable)
317             = $name =~ m{\A PLURAL (_VAR)? \z}xms;
318 34 100       77 if ($is_plural) {
319 5 50       16 if ( exists $data_ref->{plural} ) {
320 0         0 _throw( qq{Error in template $filename, plugin $package. Can not use PLURAL/PLURAL_VAR twice. $name="$attr_ref->{$name}"} );
321             }
322 5         19 $data_ref->{plural} = {
323             is_variable => $is_variable,
324             value => $attr_ref->{$name},
325             };
326 5         14 next ATTRIBUTE;
327             }
328             # parse gettext count
329             # as string constant COUNT
330             # as variable COUNT_VAR
331 29         64 my $is_count
332             = ($is_variable)
333             = $name =~ m{\A COUNT (_VAR)? \z}xms;
334 29 100       68 if ($is_count) {
335 5 50       15 if ( exists $data_ref->{count} ) {
336 0         0 _throw( qq{Error in template $filename, plugin $package. Can not use COUNT/COUNT_VAR twice. $name="$attr_ref->{$name}"} );
337             }
338 5         19 $data_ref->{count} = {
339             is_variable => $is_variable,
340             value => $attr_ref->{$name},
341             };
342 5         48 next ATTRIBUTE;
343             }
344             # parse gettext context
345             # as string constant CONTEXT
346             # as variable CONTEXT_VAR
347 24         57 my $is_context
348             = ($is_variable)
349             = $name =~ m{\A CONTEXT (_VAR)? \z}xms;
350 24 100       75 if ($is_context) {
351 4 50       10 if ( exists $data_ref->{context} ) {
352 0         0 _throw( qq{Error in template $filename, plugin $package. Can not use CONTEXT/CONTEXT_VAR twice. $name="$attr_ref->{$name}"} );
353             }
354 4         15 $data_ref->{context} = {
355             is_variable => $is_variable,
356             value => $attr_ref->{$name},
357             };
358 4         14 next ATTRIBUTE;
359             }
360             }
361 86 100       244 if ( $init{allow_formatter} ) {
362             # parse FORMATTER
363 16 100       46 if ( $name eq 'FORMATTER' ) {
364 3 50       16 if ( exists $data_ref->{formatter}->{array} ) {
365 0         0 _throw( qq{Error in template $filename, plugin $package. Can not use FORMATTER twice. $name="$attr_ref->{$name}"} );
366             }
367 3         18 $data_ref->{formatter}->{array} = [
368             map {
369 3         13 {value => $_};
370             } split m{\|}xms, $attr_ref->{$name}
371             ];
372 3         13 next ATTRIBUTE;
373             }
374             }
375 83 100       310 if ( $init{allow_unescaped} ) {
376             # parse unescaped placeholders
377             # as string constant UNESCAPED_name_1 .. UNESCAPED_name_n
378             # as variable UNESCAPED_name_1_VAR .. UNESCAPED_name_n_VAR
379 22         109 my $is_unescaped
380             = my ($key, $is_variable)
381             = $name =~ m{\A UNESCAPED _ ([A-Z][0-9A-Z_]*?) (_VAR)? \z}xms;
382 22 100       78 if ($is_unescaped) {
383             # _name, _name_VAR
384 10 50       64 if ( exists $data_ref->{unescaped}->{hash}->{lc $key} ) {
385 0         0 _throw( qq{Error in template $filename, plugin $package. Can not use unescaped key $key twice. $name="$attr_ref->{$name}"} );
386             }
387 10         61 $data_ref->{unescaped}->{hash}->{lc $key} = {
388             is_variable => $is_variable,
389             value => $attr_ref->{$name},
390             };
391 10         41 next ATTRIBUTE;
392             }
393             }
394             }
395             # parse NAME/VALUE
396 55 50       386 $data_ref->{text} = {
    100          
397             exists $attr_ref->{NAME}
398             ? (
399             exists $attr_ref->{VALUE}
400             ? _throw(
401             qq{Error in template $filename, plugin $package. Do not use NAME and VALUE at the same time. NAME="$attr_ref->{NAME}" VALUE="$attr_ref->{VALUE}"}
402             )
403             : (
404             is_variable => 1,
405             value => $attr_ref->{NAME},
406             )
407             )
408             : (
409             value => $attr_ref->{VALUE},
410             )
411             };
412            
413 55         138 return;
414             }
415            
416             sub _check_escape {
417 55     55   105 my ($data_ref, $htc, $filename) = @_;
418            
419 55         102 my $package = __PACKAGE__;
420 18         253 my $unknown_escapes = _calculate_escape({
421             escapes => [
422             (
423             split m{\|}xms, $htc->get_default_escape()
424             ),
425             (
426             exists $data_ref->{escape}
427 55 100       265 ? @{ $data_ref->{escape}->{array} }
428             : ()
429             ),
430             ],
431             escape_ref => \$data_ref->{escape}->{array},
432             });
433 55 50       262 if ($unknown_escapes) {
434 0         0 my $escapes = join ', ', @{$unknown_escapes};
  0         0  
435 0         0 my $is_plural = @{$unknown_escapes} > 1;
  0         0  
436 0 0       0 _throw(
437             "Error in template $filename, plugin $package."
438             . (
439             $is_plural
440             ? "Escapes $escapes at ESCAPE are unknown."
441             : "Escape $escapes at ESCAPE is unknown."
442             )
443             );
444             }
445 55 100 66     317 if ( exists $data_ref->{escape} && ! @{ $data_ref->{escape}->{array} } ) {
  55         305  
446 35         98 delete $data_ref->{escape};
447             }
448            
449 55         142 return;
450             }
451            
452             sub _prepare_htc_code {
453 55     55   95 my ($data_ref, $htc) = @_;
454            
455 55         95 my $package = __PACKAGE__;
456            
457             # write code snippet
458             my $to_perl_code = sub {
459 165     165   232 my $data = shift;
460            
461 165 100       490 $data->{is_variable}
462             and return _lookup_variable($htc, $data->{value});
463 132 50       334 defined $data->{value}
464             or return 'undef';
465            
466 132         315 return _string_to_perl_code( $data->{value} );
467 55         272 };
468            
469             PREPARE_SCALAR:
470 55         143 for my $key ( qw(filename text plural count context) ) {
471 275 100       3902 exists $data_ref->{$key}
472             or next PREPARE_SCALAR;
473 124         200 my $data = $data_ref->{$key};
474 124         262 $data->{perl_code} = $to_perl_code->($data);
475             }
476            
477             PREPARE_ARRAY:
478 55         407 for my $key ( qw(maketext formatter) ) {
479 110 100       1372 exists $data_ref->{$key}
480             or next PREPARE_ARRAY;
481 11         20 my $data = $data_ref->{$key};
482 15         353 $data->{perl_code}
483             = q{[}
484             . (
485             join q{,}, map {
486 11         39 $to_perl_code->($_);
487 11         34 } @{ $data->{array} }
488             )
489             . q{]};
490             }
491            
492             PREPARE_HASH:
493 55         137 for my $key ( qw(gettext unescaped) ) {
494 110 100       1517 exists $data_ref->{$key}
495             or next PREPARE_HASH;
496 18         38 my $data = $data_ref->{$key};
497 26         633 $data->{perl_code}
498             = q[{]
499             . (
500             join q{,}, map {
501 18         70 _string_to_perl_code($_)
502             . ' => '
503             . $to_perl_code->( $data->{hash}->{$_} )
504 18         39 } keys %{ $data->{hash} }
505             )
506             . q[}];
507             }
508            
509             # store escape itself
510             PREPARE_JOINED_ARRAY:
511 55         271 for my $key ( qw(escape) ) {
512 55 100       211 exists $data_ref->{$key}
513             or next PREPARE_JOINED_ARRAY;
514 20         48 my $data = $data_ref->{$key};
515 20         89 $data->{perl_code}
516             = _string_to_perl_code(
517 20         39 join q{,}, @{ $data->{array} }
518             );
519             }
520            
521 55         292 return;
522             }
523            
524             sub TEXT {
525 55     55 1 120002 my ($htc, $token, $arg_ref) = @_;
526            
527 55         345 my $attr_ref = $token->get_attributes();
528 55         363 my $filename = $htc->get_filename();
529            
530 55         460 my %data = (
531             filename => {
532             value => $filename,
533             },
534             );
535 55         221 _parse_attributes($attr_ref, $filename, \%data);
536 55         195 _check_escape(\%data, $htc, $filename);
537 55         189 _prepare_htc_code(\%data, $htc);
538            
539             # necessary for HTC's caching mechanism
540 173 50 66     1284 my $inner_hash = join ', ', map {
541 55         175 ( $_ eq 'filename' || exists $data{$_} )
542             ? "$_ => $data{$_}->{perl_code}"
543             : ();
544             } keys %data;
545            
546 55         734 return <<"EO_CODE";
547             $arg_ref->{out} $init{translator_class}->translate({$inner_hash});
548             EO_CODE
549             }
550            
551             1;
552            
553             __END__