File Coverage

blib/lib/Locale/Maketext/Lexicon/Gettext.pm
Criterion Covered Total %
statement 85 92 92.3
branch 51 66 77.2
condition 24 34 70.5
subroutine 10 11 90.9
pod 0 6 0.0
total 170 209 81.3


line stmt bran cond sub pod time code
1             package Locale::Maketext::Lexicon::Gettext;
2             $Locale::Maketext::Lexicon::Gettext::VERSION = '1.00';
3 9     9   56 use strict;
  9         18  
  9         1416  
4              
5             # ABSTRACT: PO and MO file parser for Maketext
6              
7              
8             my ( $InputEncoding, $OutputEncoding, $DoEncoding );
9              
10 1     1 0 68 sub input_encoding {$InputEncoding}
11 0     0 0 0 sub output_encoding {$OutputEncoding}
12              
13             sub parse {
14 70     70 0 127 my $self = shift;
15 70         99 my ( %var, $key, @ret );
16 0         0 my @metadata;
17 0         0 my @comments;
18 0         0 my @fuzzy;
19              
20 70         123 $InputEncoding = $OutputEncoding = $DoEncoding = undef;
21              
22 9     9   67 use Carp;
  9         17  
  9         19385  
23 70 50       200 Carp::cluck "Undefined source called\n" unless defined $_[0];
24              
25             # Check for magic string of MO files
26 70 100 100     852 return parse_mo( join( '', @_ ) )
27             if ( $_[0] =~ /^\x95\x04\x12\xde/ or $_[0] =~ /^\xde\x12\x04\x95/ );
28              
29 12         45 local $^W; # no 'uninitialized' warnings, please.
30              
31 12         98 require Locale::Maketext::Lexicon;
32 12         41 my $KeepFuzzy = Locale::Maketext::Lexicon::option('keep_fuzzy');
33 12   100     61 my $UseFuzzy = $KeepFuzzy
34             || Locale::Maketext::Lexicon::option('use_fuzzy');
35 12         33 my $AllowEmpty = Locale::Maketext::Lexicon::option('allow_empty');
36             my $process = sub {
37 50 100 100 50   307 if ( length( $var{msgstr} ) and ( $UseFuzzy or !$var{fuzzy} ) ) {
    50 33        
38 48         125 push @ret, ( map transform($_), @var{ 'msgid', 'msgstr' } );
39             }
40             elsif ($AllowEmpty) {
41 0         0 push @ret, ( transform( $var{msgid} ), '' );
42             }
43 49 100       125 if ( $var{msgid} eq '' ) {
44 10         34 push @metadata, parse_metadata( $var{msgstr} );
45             }
46             else {
47 39         82 push @comments, $var{msgid}, $var{msgcomment};
48             }
49 49 100 100     170 if ( $KeepFuzzy && $var{fuzzy} ) {
50 4         6 push @fuzzy, $var{msgid}, 1;
51             }
52 49         178 %var = ();
53 12         77 };
54              
55             # Parse PO files
56 12         33 foreach (@_) {
57 308         1602 s/[\015\012]*\z//; # fix CRLF issues
58              
59             /^(msgid|msgstr) +"(.*)" *$/
60             ? do { # leading strings
61 100         259 $var{$1} = $2;
62 100         193 $key = $1;
63             }
64             :
65              
66             /^"(.*)" *$/
67             ? do { # continued strings
68 120         669 $var{$key} .= $1;
69             }
70             :
71              
72             /^# (.*)$/
73             ? do { # user comments
74 2         9 $var{msgcomment} .= $1 . "\n";
75             }
76             :
77              
78             /^#, +(.*) *$/
79             ? do { # control variables
80 9         78 $var{$_} = 1 for split( /,\s+/, $1 );
81             }
82             :
83              
84             /^ *$/ && %var
85 308 100 66     1593 ? do { # interpolate string escapes
    100          
    100          
    100          
    100          
86 42         80 $process->($_);
87             }
88             : ();
89              
90             }
91              
92             # do not silently skip last entry
93 12 100       65 $process->() if keys %var != 0;
94              
95 11 50       35 push @ret, map { transform($_) } @var{ 'msgid', 'msgstr' }
  0         0  
96             if length $var{msgstr};
97 11 50       72 push @metadata, parse_metadata( $var{msgstr} )
98             if $var{msgid} eq '';
99              
100             return wantarray
101 11 100       265 ? ( { @metadata, @ret }, {@comments}, {@fuzzy} )
102             : ( { @metadata, @ret } );
103              
104             }
105              
106             sub parse_metadata {
107             return map {
108 80     80 0 186 (/^([^\x00-\x1f\x80-\xff :=]+):\s*(.*)$/)
109             ? ( $1 eq 'Content-Type' )
110 595 100       5030 ? do {
    50          
111 69         147 my $enc = $2;
112 69 50       419 if ( $enc =~ /\bcharset=\s*([-\w]+)/i ) {
113 69   50     245 $InputEncoding = $1 || '';
114 69   50     251 $OutputEncoding
115             = Locale::Maketext::Lexicon::encoding()
116             || '';
117 69 100       378 $InputEncoding = 'utf8'
118             if $InputEncoding =~ /^utf-?8$/i;
119 69 50       154 $OutputEncoding = 'utf8'
120             if $OutputEncoding =~ /^utf-?8$/i;
121 69 50 33     190 if (Locale::Maketext::Lexicon::option('decode')
      66        
122             and ( !$OutputEncoding
123             or $InputEncoding ne $OutputEncoding )
124             )
125             {
126 59 50       168 require Encode::compat if $] < 5.007001;
127 59         1512 require Encode;
128 59         14860 $DoEncoding = 1;
129             }
130             }
131 69         250 ( "__Content-Type", $enc );
132             }
133             : ( "__$1", $2 )
134             : ();
135             } split( /\r*\n+\r*/, transform(pop) );
136             }
137              
138             sub transform {
139 411     411 0 626 my $str = shift;
140              
141 411 100 66     1420 if ( $DoEncoding and $InputEncoding ) {
142 234 100       865 $str
143             = ( $InputEncoding eq 'utf8' )
144             ? Encode::decode_utf8($str)
145             : Encode::decode( $InputEncoding, $str );
146             }
147              
148 410         178909 $str =~ s/\\([0x]..|c?.)/qq{"\\$1"}/eeg;
  188         10167  
149              
150 410 50 66     1644 if ( $DoEncoding and $OutputEncoding ) {
151 0 0       0 $str
152             = ( $OutputEncoding eq 'utf8' )
153             ? Encode::encode_utf8($str)
154             : Encode::encode( $OutputEncoding, $str );
155             }
156              
157 410         727 return _gettext_to_maketext($str);
158             }
159              
160             sub _gettext_to_maketext {
161 434     434   639 my $str = shift;
162 434         1417 $str =~ s{([\~\[\]])}{~$1}g;
163 434         10903 $str =~ s{
164             ([%\\]%) # 1 - escaped sequence
165             |
166             % (?:
167             ([A-Za-z#*]\w*) # 2 - function call
168             \(([^\)]*)\) # 3 - arguments
169             |
170             ([1-9]\d*|\*) # 4 - variable
171             )
172             }{
173 78 100       433 $1 ? $1
    50          
174             : $2 ? "\[$2,"._unescape($3)."]"
175             : "[_$4]"
176             }egx;
177 434         5601 $str;
178             }
179              
180             sub _unescape {
181 22 100       121 join( ',',
182 9     9   34 map { /\A(\s*)%([1-9]\d*|\*)(\s*)\z/ ? "$1_$2$3" : $_ }
183             split( /,/, $_[0] ) );
184             }
185              
186             # This subroutine was derived from Locale::Maketext::Gettext::readmo()
187             # under the Perl License; the original author is Yi Ma Mao (IMACAT).
188             sub parse_mo {
189 59     59 0 662 my $content = shift;
190 59 100       196 my $tmpl = ( substr( $content, 0, 4 ) eq "\xde\x12\x04\x95" ) ? 'V' : 'N';
191              
192             # Check the MO format revision number
193             # There is only one revision now: revision 0.
194 59 50       239 return if unpack( $tmpl, substr( $content, 4, 4 ) ) > 0;
195              
196 59         82 my ( $num, $offo, $offt );
197              
198             # Number of strings
199 59         105 $num = unpack $tmpl, substr( $content, 8, 4 );
200              
201             # Offset to the beginning of the original strings
202 59         103 $offo = unpack $tmpl, substr( $content, 12, 4 );
203              
204             # Offset to the beginning of the translated strings
205 59         99 $offt = unpack $tmpl, substr( $content, 16, 4 );
206              
207 59         78 my ( @metadata, @ret );
208 59         155 for ( 0 .. $num - 1 ) {
209 118         133 my ( $len, $off, $stro, $strt );
210              
211             # The first word is the length of the string
212 118         388 $len = unpack $tmpl, substr( $content, $offo + $_ * 8, 4 );
213              
214             # The second word is the offset of the string
215 118         238 $off = unpack $tmpl, substr( $content, $offo + $_ * 8 + 4, 4 );
216              
217             # Original string
218 118         170 $stro = substr( $content, $off, $len );
219              
220             # The first word is the length of the string
221 118         206 $len = unpack $tmpl, substr( $content, $offt + $_ * 8, 4 );
222              
223             # The second word is the offset of the string
224 118         215 $off = unpack $tmpl, substr( $content, $offt + $_ * 8 + 4, 4 );
225              
226             # Translated string
227 118         201 $strt = substr( $content, $off, $len );
228              
229             # Hash it
230 118 100       347 push @metadata, parse_metadata($strt) if $stro eq '';
231 118 50       602 push @ret, ( map transform($_), $stro, $strt ) if length $strt;
232             }
233              
234 59         1298 return { @metadata, @ret };
235             }
236              
237             1;
238              
239             __END__