File Coverage

lib/Log/Report/Lexicon/MOTcompact.pm
Criterion Covered Total %
statement 78 83 93.9
branch 22 54 40.7
condition 2 5 40.0
subroutine 12 14 85.7
pod 6 6 100.0
total 120 162 74.0


line stmt bran cond sub pod time code
1             # Copyrights 2007-2018 by [Mark Overmeer ].
2             # For other contributors see ChangeLog.
3             # See the manual pages for details on the licensing terms.
4             # Pod stripped from pm file by OODoc 2.02.
5             # This code is part of distribution Log-Report-Lexicon. Meta-POD processed
6             # with OODoc into POD and HTML manual-pages. See README.md
7             # Copyright Mark Overmeer. Licensed under the same terms as Perl itself.
8              
9             package Log::Report::Lexicon::MOTcompact;
10 1     1   5 use vars '$VERSION';
  1         1  
  1         54  
11             $VERSION = '1.11';
12              
13 1     1   5 use base 'Log::Report::Lexicon::Table';
  1         1  
  1         86  
14              
15 1     1   4 use warnings;
  1         1  
  1         32  
16 1     1   5 use strict;
  1         1  
  1         25  
17              
18 1     1   5 use Log::Report 'log-report-lexicon';
  1         2  
  1         5  
19 1     1   221 use Fcntl qw(SEEK_SET);
  1         2  
  1         47  
20 1     1   5 use Encode qw(find_encoding);
  1         2  
  1         32  
21              
22 1     1   5 use constant MAGIC_NUMBER => 0x95_04_12_DE;
  1         1  
  1         1015  
23              
24              
25             sub read($@)
26 1     1 1 3 { my ($class, $fn, %args) = @_;
27              
28 1         2 my $charset = $args{charset};
29 1 50 33     11 $charset = $1
30             if !$charset && $fn =~ m!\.([\w-]+)(?:\@[^/\\]+)?\.g?mo$!i;
31              
32 1         1 my $enc;
33 1 50       2 if(defined $charset)
34 0 0       0 { $enc = find_encoding($charset)
35             or error __x"unsupported explicit charset {charset} for {fn}"
36             , charset => $charset, fn => $fn;
37             }
38              
39 1         2 my (%index, %locs);
40 1         3 my %self =
41             +( index => \%index # fully prepared ::PO objects
42             , locs => \%locs # know where to find it
43             , filename => $fn
44             );
45 1         2 my $self = bless \%self, $class;
46              
47 1         1 my $fh;
48 1 50       38 open $fh, "<:raw", $fn
49             or fault __x"cannot read mo from file {fn}", fn => $fn;
50              
51             # The magic number will tell us the byte-order
52             # See http://www.gnu.org/software/gettext/manual/html_node/MO-Files.html
53             # Found in a bug-report that msgctxt are prepended to the msgid with
54             # a separating EOT (4)
55 1         2 my ($magic, $superblock, $originals, $translations);
56 1 50       12 CORE::read $fh, $magic, 4
57             or fault __x"cannot read magic from {fn}", fn => $fn;
58              
59 1 0       3 my $byteorder
    50          
60             = $magic eq pack('V', MAGIC_NUMBER) ? 'V'
61             : $magic eq pack('N', MAGIC_NUMBER) ? 'N'
62             : error __x"unsupported file type (magic number is {magic%x})"
63             , magic => $magic;
64              
65             # The superblock contains pointers to strings
66 1 50       4 CORE::read $fh, $superblock, 6*4 # 6 times a 32 bit int
67             or fault __x"cannot read superblock from {fn}", fn => $fn;
68              
69 1         6 my ( $format_rev, $nr_strings, $offset_orig, $offset_trans
70             , $size_hash, $offset_hash ) = unpack $byteorder x 6, $superblock;
71              
72             # warn "($format_rev, $nr_strings, $offset_orig, $offset_trans
73             # , $size_hash, $offset_hash)";
74              
75             # Read location of all originals
76 1 50       12 seek $fh, $offset_orig, SEEK_SET
77             or fault __x"cannot seek to {loc} in {fn} for originals"
78             , loc => $offset_orig, fn => $fn;
79              
80 1 50       9 CORE::read $fh, $originals, $nr_strings*8 # each string 2*4 bytes
81             or fault __x"cannot read originals from {fn}, need {size} at {loc}"
82             , fn => $fn, loc => $offset_orig, size => $nr_strings*4;
83              
84 1         4 my @origs = unpack $byteorder.'*', $originals;
85              
86             # Read location of all translations
87 1 50       11 seek $fh, $offset_trans, SEEK_SET
88             or fault __x"cannot seek to {loc} in {fn} for translations"
89             , loc => $offset_orig, fn => $fn;
90              
91 1 50       9 CORE::read $fh, $translations, $nr_strings*8 # each string 2*4 bytes
92             or fault __x"cannot read translations from {fn}, need {size} at {loc}"
93             , fn => $fn, loc => $offset_trans, size => $nr_strings*4;
94              
95 1         4 my @trans = unpack $byteorder.'*', $translations;
96              
97             # We need the originals as index to the translations (unless there
98             # is a HASH build-in... which is not defined)
99             # The strings are strictly ordered, the spec tells me, to allow binary
100             # search. Better swiftly process the whole block into a hash.
101 1         2 my ($orig_start, $orig_end) = ($origs[1], $origs[-1]+$origs[-2]);
102              
103 1 50       11 seek $fh, $orig_start, SEEK_SET
104             or fault __x"cannot seek to {loc} in {fn} for msgid strings"
105             , loc => $orig_start, fn => $fn;
106              
107 1         2 my ($orig_block, $trans_block);
108 1         1 my $orig_block_size = $orig_end - $orig_start;
109 1 50       8 CORE::read $fh, $orig_block, $orig_block_size
110             or fault __x"cannot read msgids from {fn}, need {size} at {loc}"
111             , fn => $fn, loc => $orig_start, size => $orig_block_size;
112              
113 1         2 my ($trans_start, $trans_end) = ($trans[1], $trans[-1]+$trans[-2]);
114 1 50       11 seek $fh, $trans_start, SEEK_SET
115             or fault __x"cannot seek to {loc} in {fn} for transl strings"
116             , loc => $trans_start, fn => $fn;
117              
118 1         2 my $trans_block_size = $trans_end - $trans_start;
119 1 50       8 CORE::read $fh, $trans_block, $trans_block_size
120             or fault __x"cannot read translations from {fn}, need {size} at {loc}"
121             , fn => $fn, loc => $trans_start, size => $trans_block_size;
122              
123 1         3 while(@origs)
124 13         19 { my ($id_len, $id_loc) = (shift @origs, shift @origs);
125 13         17 my $msgid_b = substr $orig_block, $id_loc-$orig_start, $id_len;
126 13 50       1261 my $msgctxt_b = $msgid_b =~ s/(.*)\x04// ? $1 : '';
127              
128 13         18 my ($trans_len, $trans_loc) = (shift @trans, shift @trans);
129 13         15 my $msgstr_b = substr $trans_block, $trans_loc - $trans_start, $trans_len;
130              
131 13 100       17 unless(defined $charset)
132 1 50       3 { $msgid_b eq ''
133             or error __x"the header is not the first entry, needed for charset in {fn}", fn => $fn;
134              
135 1 50       7 $charset = $msgstr_b =~ m/^content-type:.*?charset=["']?([\w-]+)/mi
136             ? $1 : error __x"cannot detect charset in {fn}", fn => $fn;
137 1         6 trace "auto-detected charset $charset for $fn";
138              
139 1 50       22 $enc = find_encoding($charset)
140             or error __x"unsupported charset {charset} in {fn}"
141             , charset => $charset, fn => $fn;
142             }
143              
144 13         41 my $msgid = $enc->decode($msgid_b);
145 13         17 my $msgctxt = $enc->decode($msgctxt_b);
146 13         51 my @msgstr = map $enc->decode($_), split /\0x00/, $msgstr_b;
147 13 50       60 $index{"$msgid#$msgctxt"} = @msgstr > 1 ? \@msgstr : $msgstr[0];
148             }
149              
150 1 50       16 close $fh
151             or failure __x"failed reading from file {fn}", fn => $fn;
152              
153 1         8 $self->{origcharset} = $charset;
154 1         6 $self->setupPluralAlgorithm;
155 1         7 $self;
156             }
157              
158             #---------
159              
160 0     0 1 0 sub index() {shift->{index}}
161 2     2 1 376 sub filename() {shift->{filename}}
162 1     1 1 4 sub originalCharset() {shift->{origcharset}}
163              
164             #---------------
165              
166             sub msgid($;$)
167 2     2 1 4 { my ($self, $msgid, $msgctxt) = @_;
168 2   50     9 my $tag = $msgid.'#'.($msgctxt//'');
169 2         6 $self->{index}{$tag};
170             }
171              
172              
173             sub msgstr($;$$)
174 0 0   0 1   { my $po = $_[0]->msgid($_[1], $_[3])
175             or return undef;
176              
177 0 0         ref $po # no plurals defined
178             or return $po;
179              
180             # speed!!!
181             $po->[$_[0]->{algo}->(defined $_[2] ? $_[2] : 1)]
182 0 0         || $po->[$_[0]->{algo}->(1)];
    0          
183             }
184              
185             1;