File Coverage

lib/Log/Report/Lexicon/MOTcompact.pm
Criterion Covered Total %
statement 75 80 93.7
branch 22 54 40.7
condition 2 5 40.0
subroutine 11 13 84.6
pod 6 6 100.0
total 116 158 73.4


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