Completed
Push — feature/5.0.1 ( 0e412a...2af28c )
by Raúl
02:28 queued 12s
created

Parser::parse()   C

Complexity

Conditions 12
Paths 56

Size

Total Lines 72
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 72
rs 5.519
c 0
b 0
f 0
cc 12
eloc 43
nc 56
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\Exception\ParseException;
7
use Sepia\PoParser\SourceHandler\FileSystem;
8
use Sepia\PoParser\SourceHandler\SourceHandler;
9
use Sepia\PoParser\SourceHandler\StringSource;
10
11
/**
12
 *    Copyright (c) 2012 Raúl Ferràs [email protected]
13
 *    All rights reserved.
14
 *
15
 *    Redistribution and use in source and binary forms, with or without
16
 *    modification, are permitted provided that the following conditions
17
 *    are met:
18
 *    1. Redistributions of source code must retain the above copyright
19
 *       notice, this list of conditions and the following disclaimer.
20
 *    2. Redistributions in binary form must reproduce the above copyright
21
 *       notice, this list of conditions and the following disclaimer in the
22
 *       documentation and/or other materials provided with the distribution.
23
 *    3. Neither the name of copyright holders nor the names of its
24
 *       contributors may be used to endorse or promote products derived
25
 *       from this software without specific prior written permission.
26
 *
27
 *    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28
 *    ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
29
 *    TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
30
 *    PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL COPYRIGHT HOLDERS OR CONTRIBUTORS
31
 *    BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32
 *    CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33
 *    SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34
 *    INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35
 *    CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36
 *    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37
 *    POSSIBILITY OF SUCH DAMAGE.
38
 *
39
 * https://github.com/raulferras/PHP-po-parser
40
 *
41
 * Class to parse .po file and extract its strings.
42
 *
43
 * @version 5.0
44
 */
45
class Parser
46
{
47
    /** @var SourceHandler */
48
    protected $sourceHandler;
49
50
    /** @var int */
51
    protected $lineNumber;
52
53
    /** @var string */
54
    protected $property;
55
56
    /**
57
     * Reads and parses a string
58
     *
59
     * @param string $string po content
60
     *
61
     * @throws \Exception.
62
     * @return Parser
63
     */
64
    public static function parseString($string)
65
    {
66
        $parser = new Parser(new StringSource($string));
67
        $parser->parse();
68
69
        return $parser;
70
    }
71
72
    /**
73
     * Reads and parses a file
74
     *
75
     * @param string $filePath
76
     *
77
     * @throws \Exception.
78
     * @return Catalog
79
     */
80
    public static function parseFile($filePath)
81
    {
82
        $parser = new Parser(new FileSystem($filePath));
83
84
        return $parser->parse();
85
    }
86
87
    public function __construct(SourceHandler $sourceHandler)
88
    {
89
        $this->sourceHandler = $sourceHandler;
90
    }
91
92
    /**
93
     * Reads and parses strings of a .po file.
94
     *
95
     * @param SourceHandler . Optional
96
     *
97
     * @throws \Exception, \InvalidArgumentException, ParseException
98
     * @return Catalog
99
     */
100
    public function parse()
101
    {
102
        $catalog = new Catalog();
103
        $this->lineNumber = 0;
104
        $entry = array();
105
        $this->mode = null;     // current mode
0 ignored issues
show
Bug introduced by
The property mode does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
106
        $this->property = null; // current property
107
108
        // Flags
109
        $headersFound = false;
110
111
        // A new entry has been just inserted.
112
113
        // Used to remember last key in a multiline previous entry.
114
        $lastPreviousKey = null;
0 ignored issues
show
Unused Code introduced by
$lastPreviousKey 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...
115
        $state = null;
0 ignored issues
show
Unused Code introduced by
$state 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...
116
117
        while (!$this->sourceHandler->ended()) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
118
119
            $line = trim($this->sourceHandler->getNextLine());
120
121
            if ($this->shouldIgnoreLine($line, $entry)) {
122
                $this->lineNumber++;
123
                continue;
124
            }
125
126
            if ($this->shouldCloseEntry($line, $entry)) {
127
                if (!$headersFound && $this->isHeader($entry)) {
128
                    $headersFound = true;
129
                    $catalog->addHeaders(array_filter(explode('\\n', $entry['msgstr'])));
130
                } else {
131
                    $catalog->addEntry(EntryFactory::createFromArray($entry));
132
                }
133
134
                $entry = array();
135
                $this->mode = null;
136
                $this->property = null;
137
138
                if (empty($line)) {
139
                    $this->lineNumber++;
140
                    continue;
141
                }
142
            }
143
144
            $firstChar = strlen($line) > 0 ? $line[0] : '';
145
146
            switch ($firstChar) {
147
                case '#':
148
                    $entry = $this->parseComment($line, $entry);
149
                    break;
150
151
                case 'm':
152
                    $entry = $this->parseProperty($line, $entry);
153
                    break;
154
155
                case '"':
156
                    $entry = $this->parseMultiline($line, $entry);
157
                    break;
158
            }
159
160
            $this->lineNumber++;
161
            continue;
162
        }
163
        $this->sourceHandler->close();
164
165
        // add final entry
166
        if (count($entry)) {
167
            $catalog->addEntry(EntryFactory::createFromArray($entry));
168
        }
169
170
        return $catalog;
171
    }
172
173
    protected function shouldIgnoreLine($line, array $entry)
174
    {
175
        return empty($line) && count($entry) === 0;
176
    }
177
178
179
    protected function shouldCloseEntry($line, array $entry)
180
    {
181
        $lineKey = '';
182
183
        return ($line === '' || ($lineKey === 'msgid' && isset($entry['msgid'])));
184
    }
185
186
    /**
187
     * Checks if entry is a header by
188
     *
189
     * @param array $entry
190
     *
191
     * @return bool
192
     */
193
    protected function isHeader(array $entry)
194
    {
195
        if (empty($entry) || !isset($entry['msgstr'])) {
196
            return false;
197
        }
198
199
        if (!isset($entry['msgid']) || !empty($entry['msgid'])) {
200
            return false;
201
        }
202
203
        $headerKeys = array(
204
            'Project-Id-Version:' => false,
205
            'Report-Msgid-Bugs-To:' => false,
206
            'POT-Creation-Date:' => false,
207
            'PO-Revision-Date:' => false,
208
            'Last-Translator:' => false,
209
            'Language-Team:' => false,
210
            'MIME-Version:' => false,
211
            'Content-Type:' => false,
212
            'Content-Transfer-Encoding:' => false,
213
            'Plural-Forms:' => false,
214
        );
215
        $count = count($headerKeys);
216
        $keys = array_keys($headerKeys);
217
218
        $headerItems = 0;
219
        $lines = explode("\\n", $entry['msgstr']);
220
221
        foreach ($lines as $str) {
222
            $tokens = explode(':', $str);
223
            $tokens[0] = trim($tokens[0], '"').':';
224
225
            if (in_array($tokens[0], $keys, true)) {
226
                $headerItems++;
227
                unset($headerKeys[$tokens[0]]);
228
                $keys = array_keys($headerKeys);
229
            }
230
        }
231
232
        return $headerItems === $count;
233
    }
234
235
    /**
236
     * @param string $line
237
     *
238
     * @return array
239
     */
240
    protected function getProperty($line)
241
    {
242
        $tokens = preg_split('/\s+/ ', $line, 2);
243
244
        return $tokens;
245
    }
246
247
    /**
248
     * @param string $line
249
     * @param array  $entry
250
     *
251
     * @return array
252
     * @throws ParseException
253
     */
254
    private function parseProperty($line, array $entry)
255
    {
256
        list($key, $value) = $this->getProperty($line);
257
258
        if (!isset($entry[$key])) {
259
            $entry[$key] = '';
260
        }
261
262
        switch (true) {
263
            case $key === 'msgctxt':
264
            case $key === 'msgid':
265
            case $key === 'msgid_plural':
266
            case $key === 'msgstr':
267
                $entry[$key] .= trim($value, '"');
268
                $this->property = $key;
269
                break;
270
271
            case strpos($key, 'msgstr[') !== false:
272
                $entry[$key] .= trim($value, '"');
273
                $this->property = $key;
274
                break;
275
276
            default:
277
                throw new ParseException(sprintf('Could not parse %s at line %d', $key, $this->lineNumber));
278
        }
279
280
        return $entry;
281
    }
282
283
    /**
284
     * @param string $line
285
     * @param array  $entry
286
     *
287
     * @return array
288
     * @throws ParseException
289
     */
290
    private function parseMultiline($line, $entry)
291
    {
292
        switch (true) {
293
            case $this->property === 'msgctxt':
294
            case $this->property === 'msgid':
295
            case $this->property === 'msgid_plural':
296
            case $this->property === 'msgstr':
297
            case strpos($this->property, 'msgstr[') !== false:
298
                $entry[$this->property] .= trim($line, '"');
299
                break;
300
301
            default:
302
                throw new ParseException(
303
                    sprintf('Error parsing property %s as multiline.', $this->property)
304
                );
305
        }
306
307
        return $entry;
308
    }
309
310
    /**
311
     * @param string $line
312
     * @param array  $entry
313
     *
314
     * @return array
315
     */
316
    private function parseComment($line, $entry)
0 ignored issues
show
Unused Code introduced by
The parameter $line is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
317
    {
318
        return $entry;
319
    }
320
}
321