Completed
Pull Request — master (#2252)
by ྅༻ Ǭɀħ
02:27
created

MO::import_from_reader()   D

Complexity

Conditions 14
Paths 23

Size

Total Lines 87
Code Lines 48

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 14
eloc 48
nc 23
nop 1
dl 0
loc 87
rs 4.9516
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * This file is part of the POMO package.
4
 *
5
 * @copyright 2014 POMO
6
 * @license GPL
7
 */
8
9
namespace POMO;
10
11
use POMO\Streams\Reader;
12
use POMO\Streams\FileReader;
13
use POMO\Translations\GettextTranslations;
14
use POMO\Translations\EntryTranslations;
15
16
/**
17
 * Class for working with MO files
18
 */
19
class MO extends GettextTranslations
20
{
21
    public $_nplurals = 2;
22
23
    /**
24
     * Fills up with the entries from MO file $filename
25
     *
26
     * @param  string $filename MO file to load
27
     * @return bool   Success
28
     */
29
    public function import_from_file($filename)
30
    {
31
        $reader = new FileReader($filename);
32
        if (!$reader->is_resource()) {
33
            return false;
34
        }
35
36
        return $this->import_from_reader($reader);
37
    }
38
39
    public function export_to_file($filename)
40
    {
41
        $fh = fopen($filename, 'wb');
42
        if (!$fh) {
43
            return false;
44
        }
45
        $res = $this->export_to_file_handle($fh);
46
        fclose($fh);
47
48
        return $res;
49
    }
50
51
    public function export()
52
    {
53
        $tmp_fh = fopen("php://temp", 'r+');
54
        if (!$tmp_fh) {
55
            return false;
56
        }
57
        $this->export_to_file_handle($tmp_fh);
58
        rewind($tmp_fh);
59
60
        return stream_get_contents($tmp_fh);
61
    }
62
63
    public function is_entry_good_for_export($entry)
64
    {
65
        if (empty($entry->translations)) {
66
            return false;
67
        }
68
69
        if (!array_filter($entry->translations)) {
70
            return false;
71
        }
72
73
        return true;
74
    }
75
76
    public function export_to_file_handle($fh)
77
    {
78
        $entries = array_filter(
79
            $this->entries,
80
            array($this, 'is_entry_good_for_export')
81
        );
82
        ksort($entries);
83
        $magic = 0x950412de;
84
        $revision = 0;
85
        $total = count($entries) + 1; // all the headers are one entry
86
        $originals_lenghts_addr = 28;
87
        $translations_lenghts_addr = $originals_lenghts_addr + 8 * $total;
88
        $size_of_hash = 0;
89
        $hash_addr = $translations_lenghts_addr + 8 * $total;
90
        $current_addr = $hash_addr;
91
        fwrite($fh, pack(
92
                'V*',
93
                $magic,
94
                $revision,
95
                $total,
96
                $originals_lenghts_addr,
97
                $translations_lenghts_addr,
98
                $size_of_hash,
99
                $hash_addr
100
            ));
101
        fseek($fh, $originals_lenghts_addr);
102
103
        // headers' msgid is an empty string
104
        fwrite($fh, pack('VV', 0, $current_addr));
105
        $current_addr++;
106
        $originals_table = chr(0);
107
108
        $reader = new Reader();
109
110 View Code Duplication
        foreach ($entries as $entry) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
111
            $originals_table .= $this->export_original($entry) . chr(0);
112
            $length = $reader->strlen($this->export_original($entry));
113
            fwrite($fh, pack('VV', $length, $current_addr));
114
            $current_addr += $length + 1; // account for the NULL byte after
115
        }
116
117
        $exported_headers = $this->export_headers();
118
        fwrite($fh, pack(
119
            'VV',
120
            $reader->strlen($exported_headers),
121
            $current_addr)
122
        );
123
        $current_addr += strlen($exported_headers) + 1;
124
        $translations_table = $exported_headers . chr(0);
125
126 View Code Duplication
        foreach ($entries as $entry) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
127
            $translations_table .= $this->export_translations($entry).chr(0);
128
            $length = $reader->strlen($this->export_translations($entry));
129
            fwrite($fh, pack('VV', $length, $current_addr));
130
            $current_addr += $length + 1;
131
        }
132
133
        fwrite($fh, $originals_table);
134
        fwrite($fh, $translations_table);
135
136
        return true;
137
    }
138
139
    public function export_original($entry)
140
    {
141
        //TODO: warnings for control characters
142
        $exported = $entry->singular;
143
        if ($entry->is_plural) {
144
            $exported .= chr(0).$entry->plural;
145
        }
146
        if (!is_null($entry->context)) {
147
            $exported = $entry->context.chr(4).$exported;
148
        }
149
150
        return $exported;
151
    }
152
153
    public function export_translations($entry)
154
    {
155
        //TODO: warnings for control characters
156
        return implode(chr(0), $entry->translations);
157
    }
158
159
    public function export_headers()
160
    {
161
        $exported = '';
162
        foreach ($this->headers as $header => $value) {
163
            $exported .= "$header: $value\n";
164
        }
165
166
        return $exported;
167
    }
168
169
    public function get_byteorder($magic)
170
    {
171
        // The magic is 0x950412de
172
        $magic_little = (int) - 1794895138;
173
        $magic_little_64 = (int) 2500072158;
174
        // 0xde120495
175
        $magic_big = ((int) - 569244523) & 0xFFFFFFFF;
176
        if ($magic_little == $magic || $magic_little_64 == $magic) {
177
            return 'little';
178
        } elseif ($magic_big == $magic) {
179
            return 'big';
180
        } else {
181
            return false;
182
        }
183
    }
184
185
    public function import_from_reader($reader)
186
    {
187
        $endian_string = $this->get_byteorder($reader->readint32());
188
        if (false === $endian_string) {
189
            return false;
190
        }
191
        $reader->setEndian($endian_string);
192
193
        $endian = ('big' == $endian_string) ? 'N' : 'V';
194
195
        $header = $reader->read(24);
196
        if ($reader->strlen($header) != 24) {
197
            return false;
198
        }
199
200
        // parse header
201
        $header = unpack("{$endian}revision/{$endian}total/{$endian}originals_lenghts_addr/{$endian}translations_lenghts_addr/{$endian}hash_length/{$endian}hash_addr", $header);
202
        if (!is_array($header)) {
203
            return false;
204
        }
205
206
        // support revision 0 of MO format specs, only
207
        if ($header['revision'] != 0) {
208
            return false;
209
        }
210
211
        // seek to data blocks
212
        $reader->seekto($header['originals_lenghts_addr']);
213
214
        // read originals' indices
215
        $originals_lengths_length = $header['translations_lenghts_addr'] - $header['originals_lenghts_addr'];
216
        if ($originals_lengths_length != $header['total'] * 8) {
217
            return false;
218
        }
219
220
        $originals = $reader->read($originals_lengths_length);
221
        if ($reader->strlen($originals) != $originals_lengths_length) {
222
            return false;
223
        }
224
225
        // read translations' indices
226
        $translations_lenghts_length = $header['hash_addr'] - $header['translations_lenghts_addr'];
227
        if ($translations_lenghts_length != $header['total'] * 8) {
228
            return false;
229
        }
230
231
        $translations = $reader->read($translations_lenghts_length);
232
        if ($reader->strlen($translations) != $translations_lenghts_length) {
233
            return false;
234
        }
235
236
        // transform raw data into set of indices
237
        $originals    = $reader->str_split($originals, 8);
238
        $translations = $reader->str_split($translations, 8);
239
240
        // skip hash table
241
        $strings_addr = $header['hash_addr'] + $header['hash_length'] * 4;
242
243
        $reader->seekto($strings_addr);
244
245
        $strings = $reader->read_all();
246
        $reader->close();
247
248
        for ($i = 0; $i < $header['total']; $i++) {
249
            $o = unpack("{$endian}length/{$endian}pos", $originals[$i]);
250
            $t = unpack("{$endian}length/{$endian}pos", $translations[$i]);
251
            if (!$o || !$t) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $o of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
Bug Best Practice introduced by
The expression $t of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
252
                return false;
253
            }
254
255
            // adjust offset due to reading strings to separate space before
256
            $o['pos'] -= $strings_addr;
257
            $t['pos'] -= $strings_addr;
258
259
            $original    = $reader->substr($strings, $o['pos'], $o['length']);
260
            $translation = $reader->substr($strings, $t['pos'], $t['length']);
261
262
            if ('' === $original) {
263
                $this->set_headers($this->make_headers($translation));
264
            } else {
265
                $entry = &$this->make_entry($original, $translation);
266
                $this->entries[$entry->key()] = &$entry;
267
            }
268
        }
269
270
        return true;
271
    }
272
273
    /**
274
     * Build a EntryTranslations from original string and translation strings,
275
     * found in a MO file
276
     *
277
     * @param string $original    original string to translate from MO file.
278
     *                            Might contain 0x04 as context separator or
279
     *                            0x00 as singular/plural separator
280
     * @param string $translation translation string from MO file.Might contain
281
     *                            0x00 as a plural translations separator
282
     * @retrun EntryTranslations New entry
283
     */
284
    public static function &make_entry($original, $translation) {
285
        $entry = new EntryTranslations();
286
        // look for context
287
        $parts = explode(chr(4), $original);
288
        if (isset($parts[1])) {
289
            $original = $parts[1];
290
            $entry->context = $parts[0];
291
        }
292
        // look for plural original
293
        $parts = explode(chr(0), $original);
294
        $entry->singular = $parts[0];
295
        if (isset($parts[1])) {
296
            $entry->is_plural = true;
297
            $entry->plural = $parts[1];
298
        }
299
        // plural translations are also separated by \0
300
        $entry->translations = explode(chr(0), $translation);
301
302
        return $entry;
303
    }
304
305
    public function select_plural_form($count)
306
    {
307
        return $this->gettext_select_plural_form($count);
308
    }
309
310
    public function get_plural_forms_count()
311
    {
312
        return $this->_nplurals;
313
    }
314
}
315