File Coverage

lib/Log/Report/Lexicon/MOTcompact.pm
Criterion Covered Total %
statement 77 83 92.7
branch 22 54 40.7
condition 2 5 40.0
subroutine 11 14 78.5
pod 6 6 100.0
total 118 162 72.8


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