Completed
Pull Request — master (#69)
by Raúl
02:46 queued 01:22
created

Parser::parseProperty()   C

Complexity

Conditions 7
Paths 12

Size

Total Lines 28
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

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