Completed
Push — feature/high_five ( cf7883...85675c )
by Raúl
05:22
created

Parser::parse()   F

Complexity

Conditions 48
Paths 15874

Size

Total Lines 239
Code Lines 144

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 239
rs 2
c 0
b 0
f 0
cc 48
eloc 144
nc 15874
nop 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
namespace Sepia\PoParser;
4
5
use Sepia\PoParser\Catalog\EntryFactory;
6
use Sepia\PoParser\PoReader\FileHandler;
7
use Sepia\PoParser\PoReader\InterfaceHandler;
8
use Sepia\PoParser\PoReader\StringHandler;
9
10
/**
11
 *    Copyright (c) 2012 Raúl Ferràs [email protected]
12
 *    All rights reserved.
13
 *
14
 *    Redistribution and use in source and binary forms, with or without
15
 *    modification, are permitted provided that the following conditions
16
 *    are met:
17
 *    1. Redistributions of source code must retain the above copyright
18
 *       notice, this list of conditions and the following disclaimer.
19
 *    2. Redistributions in binary form must reproduce the above copyright
20
 *       notice, this list of conditions and the following disclaimer in the
21
 *       documentation and/or other materials provided with the distribution.
22
 *    3. Neither the name of copyright holders nor the names of its
23
 *       contributors may be used to endorse or promote products derived
24
 *       from this software without specific prior written permission.
25
 *
26
 *    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27
 *    ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
28
 *    TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29
 *    PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL COPYRIGHT HOLDERS OR CONTRIBUTORS
30
 *    BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31
 *    CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32
 *    SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33
 *    INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34
 *    CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35
 *    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36
 *    POSSIBILITY OF SUCH DAMAGE.
37
 *
38
 * https://github.com/raulferras/PHP-po-parser
39
 *
40
 * Class to parse .po file and extract its strings.
41
 *
42
 * @version 5.0
43
 */
44
class Parser
45
{
46
    /** @var InterfaceHandler */
47
    protected $sourceHandler;
48
49
    /**
50
     * Reads and parses a string
51
     *
52
     * @param string $string po content
53
     *
54
     * @throws \Exception.
55
     * @return Parser
56
     */
57
    public static function parseString($string)
58
    {
59
        $parser = new Parser(new StringHandler($string));
60
        $parser->parse();
61
62
        return $parser;
63
    }
64
65
    /**
66
     * Reads and parses a file
67
     *
68
     * @param string $filePath
69
     *
70
     * @throws \Exception.
71
     * @return Catalog
72
     */
73
    public static function parseFile($filePath)
74
    {
75
        $parser = new Parser(new FileHandler($filePath));
76
77
        return $parser->parse();
78
    }
79
80
    public function __construct(InterfaceHandler $sourceHandler)
81
    {
82
        $this->sourceHandler = $sourceHandler;
83
    }
84
85
    /**
86
     * Reads and parses strings of a .po file.
87
     *
88
     * @param InterfaceHandler . Optional
89
     *
90
     * @throws \Exception, \InvalidArgumentException
91
     * @return Catalog
92
     */
93
    public function parse()
94
    {
95
        $catalog = new Catalog();
96
        $entry = array();
97
98
        // A new entry has been just inserted.
99
        $justNewEntry = false;
100
        $firstLine = true;
101
102
        // Used to remember last key in a multiline previous entry.
103
        $lastPreviousKey = null;
104
        $state = null;
105
        $lineNumber = 0;
106
107
        while (!$this->sourceHandler->ended()) {
108
            $line = trim($this->sourceHandler->getNextLine());
109
            $split = preg_split('/\s+/ ', $line, 2);
110
            $key = $split[0];
111
112
            // If a blank line is found, or a new msgid when already got one
113
            if ($line === '' || ($key === 'msgid' && isset($entry['msgid']))) {
114
                // Two consecutive blank lines
115
                if ($justNewEntry) {
116
                    $lineNumber++;
117
                    continue;
118
                }
119
120
                if ($firstLine) {
121
                    $firstLine = false;
122
                    if (self::isHeader($entry)) {
123
                        $catalog->addHeaders(explode('\\n', $entry['msgstr']));
124
                    } else {
125
                        $catalog->addEntry(EntryFactory::createFromArray($entry));
126
                    }
127
                } else {
128
                    // A new entry is found!
129
                    $catalog->addEntry(EntryFactory::createFromArray($entry));
130
                }
131
132
                $entry = array();
133
                $state = null;
134
                $justNewEntry = true;
135
                $lastPreviousKey = null;
136
                if ($line === '') {
137
                    $lineNumber++;
138
                    continue;
139
                }
140
            }
141
142
            $justNewEntry = false; // ?
143
            $data = isset($split[1]) ? $split[1] : null;
144
145
            switch ($key) {
146
                // Flagged translation
147
                case '#,':
148
                    $entry['flags'] = preg_split('/,\s*/', $data);
149
                    break;
150
151
                // # Translator comments
152
                case '#':
153
                    $entry['tcomment'] = !isset($entry['tcomment']) ? array() : $entry['tcomment'];
154
                    $entry['tcomment'][] = $data;
155
                    break;
156
157
                // #. Comments extracted from source code
158
                case '#.':
159
                    $entry['ccomment'] = !isset($entry['ccomment']) ? array() : $entry['ccomment'];
160
                    $entry['ccomment'][] = $data;
161
                    break;
162
163
                // Reference
164
                case '#:':
165
                    $entry['reference'][] = addslashes($data);
166
                    break;
167
168
169
                case '#|':      // #| Previous untranslated string
170
                case '#~':      // #~ Old entry
171
                case '#~|':     // #~| Previous-Old untranslated string. Reported by @Cellard
172
                    $modifier = '';
0 ignored issues
show
Unused Code introduced by
$modifier is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
173
                    switch ($key) {
174
                        case '#|':
175
                            $key = 'previous';
176
                            break;
177
                        case '#~':
178
                            $key = 'obsolete';
179
                            break;
180
                        case '#~|':
181
                            $key = 'previous-obsolete';
182
                            break;
183
                    }
184
185
                    $tmpParts = explode(' ', $data);
186
                    $tmpKey = $tmpParts[0];
187
188
                    if (!in_array($tmpKey, array('msgid', 'msgid_plural', 'msgstr', 'msgctxt'), true)) {
189
                        // If there is a multi-line previous string we must remember
190
                        // what key was first line.
191
                        $tmpKey = $lastPreviousKey;
192
                        $str = $data;
193
                    } else {
194
                        $str = implode(' ', array_slice($tmpParts, 1));
195
                    }
196
197
                    //array('obsolete' => true, 'msgid' => '', 'msgstr' => '');
198
199
                    if (strpos($key, 'obsolete') !== false) {
200
                        $entry['obsolete'] = true;
201
                        switch ($tmpKey) {
202
                            case 'msgid':
203
                                if (!isset($entry['msgid'])) {
204
                                    $entry['msgid'] = '';
205
                                }
206
                                $entry['msgid'].= trim($str, '"');
207
                                $lastPreviousKey = $tmpKey;
208
                                break;
209
210
                            case 'msgstr':
211
                                if (!isset($entry['msgstr'])) {
212
                                    $entry['msgstr'] = '';
213
                                }
214
                                $entry['msgstr'].= trim($str, '"');
215
                                $lastPreviousKey = $tmpKey;
216
                                break;
217
218
                            case 'msgctxt':
219
                                $entry['msgctxt'] = trim($str, '"');
220
                                $lastPreviousKey = $tmpKey;
221
                                break;
222
223
                            default:
224
                                break;
225
                        }
226
                    } else {
227
                        $entry[$key] = isset($entry[$key]) ? $entry[$key] : array('msgid' => '', 'msgstr' => '');
228
                    }
229
230
                    if ($key !== 'obsolete') {
231
                        switch ($tmpKey) {
232
                            case 'msgid':
233
                            case 'msgid_plural':
234
                            case 'msgstr':
235
                                $entry[$key][$tmpKey] = trim($str, '"');
236
                                $lastPreviousKey = $tmpKey;
237
                                break;
238
239
                            default:
240
                                $entry[$key][$tmpKey] = $str;
241
                                break;
242
                        }
243
                    }
244
                    break;
245
246
247
                // context
248
                // Allows disambiguations of different messages that have same msgid.
249
                // Example:
250
                //
251
                // #: tools/observinglist.cpp:700
252
                // msgctxt "First letter in 'Scope'"
253
                // msgid "S"
254
                // msgstr ""
255
                //
256
                // #: skycomponents/horizoncomponent.cpp:429
257
                // msgctxt "South"
258
                // msgid "S"
259
                // msgstr ""
260
                case 'msgctxt':
261
                    // untranslated-string
262
                case 'msgid':
263
                    // untranslated-string-plural
264
                case 'msgid_plural':
265
                    $state = $key;
266
                    if (!isset($entry[$state])) {
267
                        $entry[$state] = '';
268
                    }
269
270
                    $entry[$state] .= trim($data, '"');
271
                    break;
272
                // translated-string
273
                case 'msgstr':
274
                    $state = 'msgstr';
275
                    $entry[$state] = trim($data, '"');
276
                    break;
277
278
                default:
279
                    if (strpos($key, 'msgstr[') !== false) {
280
                        // translated-string-case-n
281
                        $state = $key;
282
                        $entry[$state] = trim($data, '"');
283
                    } else {
284
                        // "multiline" lines
285
                        switch ($state) {
286
                            case 'msgctxt':
287
                            case 'msgid':
288
                            case 'msgid_plural':
289
                            case (strpos($state, 'msgstr[') !== false):
290
                                if (!isset($entry[$state]) === false) {
291
                                    $entry[$state] = '';
292
                                }
293
294
                                if (is_string($entry[$state])) {
295
                                    // Convert it to array
296
                                    //$entry[$state] = array($entry[$state]);
297
                                    $entry[$state] = trim($entry[$state], '"');
298
                                }
299
                                $entry[$state] .= trim($line, '"');
300
                                break;
301
302
                            case 'msgstr':
303
                                // Special fix where msgid is ""
304
                                $entry['msgstr'] .= trim($line, '"');
305
                                /*if ($entry['msgid'] === "\"\"") {
306
                                    $entry['msgstr'].= trim($line, '"');
307
                                } else {
308
                                    $entry['msgstr'].= $line;
309
                                }*/
310
                                break;
311
312
                            default:
313
                                throw new \Exception(
314
                                    'Parser: Parse error! Unknown key "'.$key.'" on line '.($lineNumber + 1)
315
                                );
316
                        }
317
                    }
318
                    break;
319
            }
320
321
            $lineNumber++;
322
        }
323
        $this->sourceHandler->close();
324
325
        // add final entry
326
        if ($state === 'msgstr') {
327
            $catalog->addEntry(EntryFactory::createFromArray($entry));
328
        }
329
330
        return $catalog;
331
    }
332
333
334
    /**
335
     * Checks if entry is a header by
336
     *
337
     * @param array $entry
338
     *
339
     * @return bool
340
     */
341
    protected static function isHeader(array $entry)
342
    {
343
        if (empty($entry) || !isset($entry['msgstr'])) {
344
            return false;
345
        }
346
347
        $headerKeys = array(
348
            'Project-Id-Version:' => false,
349
            //  'Report-Msgid-Bugs-To:' => false,
350
            //  'POT-Creation-Date:'    => false,
351
            'PO-Revision-Date:' => false,
352
            //  'Last-Translator:'      => false,
353
            //  'Language-Team:'        => false,
354
            'MIME-Version:' => false,
355
            //  'Content-Type:'         => false,
356
            //  'Content-Transfer-Encoding:' => false,
357
            //  'Plural-Forms:'         => false
358
        );
359
        $count = count($headerKeys);
360
        $keys = array_keys($headerKeys);
361
362
        $headerItems = 0;
363
        $lines = explode("\\n", $entry['msgstr']);
364
365
        foreach ($lines as $str) {
366
            $tokens = explode(':', $str);
367
            $tokens[0] = trim($tokens[0], '"').':';
368
369
            if (in_array($tokens[0], $keys, true)) {
370
                $headerItems++;
371
                unset($headerKeys[$tokens[0]]);
372
                $keys = array_keys($headerKeys);
373
            }
374
        }
375
376
        return $headerItems === $count;
377
    }
378
}
379