Completed
Pull Request — 5.0 (#66)
by
unknown
14:59 queued 13:33
created

Parser   D

Complexity

Total Complexity 125

Size/Duplication

Total Lines 765
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 87.47%

Importance

Changes 0
Metric Value
wmc 125
lcom 1
cbo 2
dl 0
loc 765
ccs 335
cts 383
cp 0.8747
rs 4.4444
c 0
b 0
f 0

20 Methods

Rating   Name   Duplication   Size   Complexity  
A parseString() 0 7 1
A parseFile() 0 7 1
A __construct() 0 5 1
A setOptions() 0 13 1
A getOptions() 0 4 1
A getSourceHandle() 0 4 1
A setSourceHandle() 0 6 1
A getHeaders() 0 4 1
A setHeaders() 0 6 1
A getEntries() 0 4 1
B setEntry() 0 20 5
A setEntryPlural() 0 8 2
A setEntryContext() 0 8 2
A save() 0 7 1
F compile() 0 135 40
A cleanExport() 0 19 1
A getEntryId() 0 10 2
B clean() 0 22 5
B isHeader() 0 33 6
F parse() 0 252 51

How to fix   Complexity   

Complex Class

Complex classes like Parser often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Parser, and based on these observations, apply Extract Interface, too.

1
<?php namespace Sepia\PoParser;
2
3
use Sepia\PoParser\Handler\FileHandler;
4
use Sepia\PoParser\Handler\HandlerInterface;
5
use Sepia\PoParser\Handler\StringHandler;
6
7
/**
8
 *    Copyright (c) 2012 Raúl Ferràs [email protected]
9
 *    All rights reserved.
10
 *
11
 *    Redistribution and use in source and binary forms, with or without
12
 *    modification, are permitted provided that the following conditions
13
 *    are met:
14
 *    1. Redistributions of source code must retain the above copyright
15
 *       notice, this list of conditions and the following disclaimer.
16
 *    2. Redistributions in binary form must reproduce the above copyright
17
 *       notice, this list of conditions and the following disclaimer in the
18
 *       documentation and/or other materials provided with the distribution.
19
 *    3. Neither the name of copyright holders nor the names of its
20
 *       contributors may be used to endorse or promote products derived
21
 *       from this software without specific prior written permission.
22
 *
23
 *    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24
 *    ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
25
 *    TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
26
 *    PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL COPYRIGHT HOLDERS OR CONTRIBUTORS
27
 *    BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
28
 *    CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
29
 *    SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
30
 *    INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
31
 *    CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32
 *    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33
 *    POSSIBILITY OF SUCH DAMAGE.
34
 *
35
 * https://github.com/raulferras/PHP-po-parser
36
 *
37
 * Class to parse .po file and extract its strings.
38
 *
39
 * @method array headers() deprecated
40
 * @method null update_entry($original, $translation = null, $tcomment = array(), $ccomment = array()) deprecated
41
 * @method array read($filePath) deprecated
42
 * @version 5.0.0
43
 */
44
class Parser
45
{
46
    const OPTION_EOL_KEY = 'multiline-glue';
47
    const OPTION_EOC_KEY = 'context-glue';
48
49
    const OPTION_EOL_VALUE = '<##EOL##>';     // End of Line token.
50
    const OPTION_EOC_VALUE = '<##EOC##>';     // End of Context token.
51
52
    /**
53
     * @var array
54
     */
55
    protected $entries = array();
56
57
    /**
58
     * @var string[]
59
     */
60
    protected $headers = array();
61
62
    /**
63
     * @var null|HandlerInterface
64
     */
65
    protected $sourceHandle = null;
66
67
    /**
68
     * @var array
69
     */
70
    protected $options = array();
71
72
    /**
73
     * Reads and parses a string
74
     *
75
     * @param string $string po content
76
     * @param array $options
77
     *
78
     * @throws \Exception.
79
     * @return $this
80
     */
81
    public static function parseString($string, $options = array())
82
    {
83
        $parser = new Parser(new StringHandler($string), $options);
84
        $parser->parse();
85
86
        return $parser;
87
    }
88
89
90
91
    /**
92
     * Reads and parses a file
93
     *
94
     * @param string $filepath
95
     * @param array $options
96
     *
97
     * @return $this
98
     * @throws \Exception.
99
     */
100 39
    public static function parseFile($filepath, $options = array())
101
    {
102 39
        $parser = new Parser(new FileHandler($filepath), $options);
103 39
        $parser->parse();
104
105 39
        return $parser;
106
    }
107
108
109 48
    public function __construct(HandlerInterface $handler, $options = array())
110
    {
111 48
        $this->setSourceHandle($handler);
112 48
        $this->setOptions($options);
113 48
    }
114
115
    /**
116
     * Sets options.
117
     * Those options not set will the default value.
118
     *
119
     * @param $options
120
     *
121
     * @return $this
122
     */
123 48
    public function setOptions($options)
124
    {
125
        $defaultOptions = array(
126
            // Token used to separate lines in msgid
127 48
            self::OPTION_EOL_KEY => self::OPTION_EOL_VALUE,
128
129
            // Token used to separate ctxt from msgid
130 48
            self::OPTION_EOC_KEY => self::OPTION_EOC_VALUE
131 16
        );
132 48
        $this->options = array_merge($defaultOptions, $options);
133
134 48
        return $this;
135
    }
136
137
    /**
138
     * Get parser options.
139
     *
140
     * @return array
141
     */
142 9
    public function getOptions()
143
    {
144 9
        return $this->options;
145
    }
146
147
    /**
148
     * Gets source Handler.
149
     *
150
     * @return null|HandlerInterface
151
     */
152 3
    public function getSourceHandle()
153
    {
154 3
        return $this->sourceHandle;
155
    }
156
157
    /**
158
     * @param null|HandlerInterface $sourceHandle
159
     *
160
     * @return $this
161
     */
162 48
    public function setSourceHandle(HandlerInterface $sourceHandle)
163
    {
164 48
        $this->sourceHandle = $sourceHandle;
165
166 48
        return $this;
167
    }
168
169
    /**
170
     * Get headers from .po file
171
     *
172
     * @return string[]
173
     */
174 12
    public function getHeaders()
175
    {
176 12
        return $this->headers;
177
    }
178
179
    /**
180
     * Set new headers.
181
     *
182
     * @param array $newHeaders
183
     *
184
     * @return $this
185
     */
186 3
    public function setHeaders(array $newHeaders)
187
    {
188 3
        $this->headers = $newHeaders;
189
190 3
        return $this;
191
    }
192
193
    /**
194
     * Gets entries.
195
     *
196
     * @return array
197
     */
198 30
    public function getEntries()
199
    {
200 30
        return $this->entries;
201
    }
202
203
    /**
204
     * Reads and parses strings of a .po file.
205
     *
206
     * @return arrays List of entries found in .po file.
207
     * @throws \Exception, \InvalidArgumentException
208
     */
209 39
    public function parse()
210
    {
211 39
        $handle = $this->sourceHandle;
212
213 39
        $headers         = array();
214 39
        $hash            = array();
215 39
        $entry           = array();
216 39
        $justNewEntry    = false; // A new entry has been just inserted.
217 39
        $firstLine       = true;
218 39
        $lastPreviousKey = null; // Used to remember last key in a multiline previous entry.
219 39
        $state           = null;
220 39
        $lineNumber      = 0;
221
222 39
        while (!$handle->ended()) {
223 39
            $line  = trim($handle->getNextLine());
224 39
            $split = preg_split('/\s+/ ', $line, 2);
225 39
            $key   = $split[0];
226
227
            // If a blank line is found, or a new msgid when already got one
228 39
            if ($line === '' || ($key=='msgid' && isset($entry['msgid']))) {
229
                // Two consecutive blank lines
230 39
                if ($justNewEntry) {
231 9
                    $lineNumber++;
232 9
                    continue;
233
                }
234
235 39
                if ($firstLine) {
236 39
                    $firstLine = false;
237 39
                    if (self::isHeader($entry)) {
238 36
                        array_shift($entry['msgstr']);
239 36
                        $headers = $entry['msgstr'];
240 12
                    } else {
241 29
                        $hash[] = $entry;
242
                    }
243 13
                } else {
244
                    // A new entry is found!
245 36
                    $hash[] = $entry;
246
                }
247
248 39
                $entry           = array();
249 39
                $state           = null;
250 39
                $justNewEntry    = true;
251 39
                $lastPreviousKey = null;
252 39
                if ($line==='') {
253 36
                    $lineNumber++;
254 36
                    continue;
255
                }
256 1
            }
257
258 39
            $justNewEntry = false;
259 39
            $data         = isset($split[1]) ? $split[1] : null;
260
261
            switch ($key) {
262
                // Flagged translation
263 39
                case '#,':
264 18
                    $entry['flags'] = preg_split('/,\s*/', $data);
265 18
                    break;
266
267
                // # Translator comments
268 39
                case '#':
269 18
                    $entry['tcomment'] = !isset($entry['tcomment']) ? array() : $entry['tcomment'];
270 18
                    $entry['tcomment'][] = $data;
271 18
                    break;
272
273
                // #. Comments extracted from source code
274 39
                case '#.':
275 3
                    $entry['ccomment'] = !isset($entry['ccomment']) ? array() : $entry['ccomment'];
276 3
                    $entry['ccomment'][] = $data;
277 3
                    break;
278
279
                // Reference
280 39
                case '#:':
281 33
                    $entry['reference'][] = addslashes($data);
282 33
                    break;
283
284
                
285 39
                case '#|':      // #| Previous untranslated string
286 39
                case '#~':      // #~ Old entry
287 39
                case '#~|':     // #~| Previous-Old untranslated string. Reported by @Cellard
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
288
289
                    switch ($key) {
290 9
                        case '#|':
291 6
                            $key = 'previous';
292 6
                            break;
293
294 6
                        case '#~':
295 6
                            $key = 'obsolete';
296 6
                            break;
297
298
                        case '#~|':
299
                            $key = 'previous-obsolete';
300
                            break;
301
                    }
302
303 9
                    $tmpParts = explode(' ', $data);
304 9
                    $tmpKey   = $tmpParts[0];
305
306 9
                    if (!in_array($tmpKey, array('msgid','msgid_plural','msgstr','msgctxt'))) {
307
                        // If there is a multiline previous string we must remember what key was first line.
308 6
                        $tmpKey = $lastPreviousKey;
309 6
                        $str = $data;
310 2
                    } else {
311 9
                        $str = implode(' ', array_slice($tmpParts, 1));
312
                    }
313
314 9
                    $entry[$key] = isset($entry[$key])? $entry[$key]:array('msgid'=>array(),'msgstr'=>array());
315
316 9
                    if (strpos($key, 'obsolete')!==false) {
317 6
                        $entry['obsolete'] = true;
318
                        switch ($tmpKey) {
319 6
                            case 'msgid':
320 6
                                $entry['msgid'][] = $str;
321 6
                                $lastPreviousKey = $tmpKey;
322 6
                                break;
323
324 6
                            case 'msgstr':
325 6
                                if ($str == "\"\"") {
326 6
                                    $entry['msgstr'][] = trim($str, '"');
327 2
                                } else {
328 6
                                    $entry['msgstr'][] = $str;
329
                                }
330 6
                                $lastPreviousKey = $tmpKey;
331 6
                                break;
332
333
                            default:
334
                                break;
335
                        }
336 2
                    }
337
338 9
                    if ($key!=='obsolete') {
339
                        switch ($tmpKey) {
340 6
                            case 'msgid':
341 6
                            case 'msgid_plural':
342 6
                            case 'msgstr':
343 6
                                $entry[$key][$tmpKey][] = $str;
344 6
                                $lastPreviousKey = $tmpKey;
345 6
                                break;
346
347
                            default:
348
                                $entry[$key][$tmpKey] = $str;
349
                                break;
350
                        }
351 2
                    }
352 9
                    break;
353
354
355
                // context
356
                // Allows disambiguations of different messages that have same msgid.
357
                // Example:
358
                //
359
                // #: tools/observinglist.cpp:700
360
                // msgctxt "First letter in 'Scope'"
361
                // msgid "S"
362
                // msgstr ""
363
                //
364
                // #: skycomponents/horizoncomponent.cpp:429
365
                // msgctxt "South"
366
                // msgid "S"
367
                // msgstr ""
368 39
                case 'msgctxt':
369
                    // untranslated-string
370 39
                case 'msgid':
371
                    // untranslated-string-plural
372 39
                case 'msgid_plural':
373 39
                    $state = $key;
374 39
                    $entry[$state][] = $data;
375 39
                    break;
376
                // translated-string
377 39
                case 'msgstr':
378 39
                    $state = 'msgstr';
379 39
                    $entry[$state][] = $data;
380 39
                    break;
381
382 12
                default:
383 36
                    if (strpos($key, 'msgstr[') !== false) {
384
                        // translated-string-case-n
385 9
                        $state = $key;
386 9
                        $entry[$state][] = $data;
387 3
                    } else {
388
                        // "multiline" lines
389
                        switch ($state) {
390 36
                            case 'msgctxt':
391 36
                            case 'msgid':
392 36
                            case 'msgid_plural':
393 36
                            case (strpos($state, 'msgstr[') !== false):
394 9
                                if (is_string($entry[$state])) {
395
                                    // Convert it to array
396
                                    $entry[$state] = array($entry[$state]);
397
                                }
398 9
                                $entry[$state][] = $line;
399 9
                                break;
400
401 36
                            case 'msgstr':
402
                                // Special fix where msgid is ""
403 36
                                if ($entry['msgid'] == "\"\"") {
404
                                    $entry['msgstr'][] = trim($line, '"');
405
                                } else {
406 36
                                    $entry['msgstr'][] = $line;
407
                                }
408 36
                                break;
409
410
                            default:
411
                                throw new \Exception(
412
                                    'PoParser: Parse error! Unknown key "' . $key . '" on line ' . ($lineNumber+1)
413
                                );
414
                        }
415
                    }
416 36
                    break;
417 12
            }
418
419 39
            $lineNumber++;
420 13
        }
421 39
        $handle->close();
422
423
        // add final entry
424 39
        if ($state == 'msgstr') {
425 3
            $hash[] = $entry;
426 1
        }
427
428
        // - Cleanup header data
429 39
        $this->headers = array();
430 39
        foreach ($headers as $header) {
431 36
            $header = $this->clean($header);
432 36
            $this->headers[] = "\"" . preg_replace("/\\n/", '\n', $header) . "\"";
433 13
        }
434
435
        // - Cleanup data,
436
        // - merge multiline entries
437
        // - Reindex hash for ksort
438 39
        $temp = $hash;
439 39
        $this->entries = array();
440 39
        foreach ($temp as $entry) {
441 39
            foreach ($entry as &$v) {
442 39
                $or = $v;
443 39
                $v = $this->clean($v);
444 39
                if ($v === false) {
445
                    // parse error
446
                    throw new \Exception(
447 26
                        'PoParser: Parse error! poparser::clean returned false on "' . htmlspecialchars($or) . '"'
448
                    );
449
                }
450 13
            }
451
452
            // check if msgid and a key starting with msgstr exists
453 39
            if (isset($entry['msgid']) && count(preg_grep('/^msgstr/', array_keys($entry)))) {
454 39
                $id = $this->getEntryId($entry);
455 39
                $this->entries[$id] = $entry;
456 13
            }
457 13
        }
458
459 39
        return $this->entries;
460
    }
461
462
    /**
463
     * Updates an entry.
464
     * If entry not found returns false. If $createNew is true, a new entry will be created.
465
     * $entry is an array that can contain following indexes:
466
     *  - msgid: string[]. Required.
467
     *  - msgstr: string[]. Required.
468
     *  - reference: string[].
469
     *  - msgctxt: string. Disambiguating context.
470
     *  - tcomment: string[]. Translator comments.
471
     *  - ccomment: string[]. Source comments.
472
     *  - msgid_plural: string[].
473
     *  - flags: array. List of entry flags. Example: ['fuzzy', 'php-format']
474
     *  - previous: array. Contains previous untranslated strings in a sub array with msgid and msgstr.
475
     *
476
     * @param string $msgid     Id of entry. Be aware that some entries have a multiline msgid.
477
     *                          In that case \n must be replaced by the value of 'multiline-glue'
478
     *                          option (by default "<##EOL##>").
479
     * @param array  $entry     Array with all entry data. Fields not setted will be removed.
480
     * @param bool   $createNew If msgid not found, it will create a new entry. By default true.
481
     *                          You want to set this to false if need to change the msgid of an entry.
482
     */
483 12
    public function setEntry($msgid, $entry, $createNew = true)
484
    {
485
        // In case of new entry
486 12
        if (!isset($this->entries[$msgid])) {
487
            if ($createNew===false) {
488
                return;
489
            }
490
491
            $this->entries[$msgid] = $entry;
492
        } else {
493
            // Be able to change msgid.
494 12
            if ($msgid!==$entry['msgid']) {
495 9
                unset($this->entries[$msgid]);
496 9
                $new_msgid = is_array($entry['msgid'])? implode($this->options[self::OPTION_EOL_KEY], $entry['msgid']):$entry['msgid'];
497 9
                $this->entries[$new_msgid] = $entry;
498 3
            } else {
499 3
                $this->entries[$msgid] = $entry;
500
            }
501
        }
502 12
    }
503
504
    /**
505
     * @param string $msgid Message Id.
506
     * @param bool   $plural
507
     */
508
    public function setEntryPlural($msgid, $plural = false)
509
    {
510
        if ($plural) {
511
            $this->entries[$msgid]['msgid_plural'] = $plural;
512
        } else {
513
            unset($this->entries[$msgid]['msgid_plural']);
514
        }
515
    }
516
517
    /**
518
     * @param string $msgid Message Id.
519
     * @param bool   $context
520
     */
521
    public function setEntryContext($msgid, $context = false)
522
    {
523
        if ($context) {
524
            $this->entries[$msgid]['msgctxt'][0] = $context;
525
        } else {
526
            unset($this->entries[$msgid]['msgctxt']);
527
        }
528
    }
529
530
    /**
531
     * Saves current translation back into source.
532
     *
533
     * @param mixed $params Parameters to pass to the source handler.
534
     *
535
     * @return $this
536
     * @throws \Exception
537
     */
538 15
    public function save($params)
539
    {
540 15
        $compiled = $this->compile();
541 15
        call_user_func(array($this->sourceHandle, 'save'), $compiled, $params);
542
543 15
        return $this;
544
    }
545
546
547
548
549
    /**
550
     * Compiles entries into a string
551
     *
552
     * @return string
553
     * @throws \Exception
554
     */
555 15
    public function compile()
556
    {
557 15
        $output = '';
558
559 15
        if (count($this->headers) > 0) {
560 15
            $output.= "msgid \"\"\n";
561 15
            $output.= "msgstr \"\"\n";
562 15
            foreach ($this->headers as $header) {
563 15
                $output.= $header . "\n";
564 5
            }
565 15
            $output.= "\n";
566 5
        }
567
568
569 15
        $entriesCount = count($this->entries);
570 15
        $counter = 0;
571 15
        foreach ($this->entries as $entry) {
572 15
            $isObsolete = isset($entry['obsolete']) && $entry['obsolete'];
573 15
            $isPlural = isset($entry['msgid_plural']);
574
575 15
            if (isset($entry['previous'])) {
576 3
                foreach ($entry['previous'] as $key => $data) {
577 3
                    if (is_string($data)) {
578
                        $output.= "#| " . $key . " " . $this->cleanExport($data) . "\n";
579 3
                    } elseif (is_array($data) && count($data)>0) {
580 3
                        foreach ($data as $line) {
581 3
                            $output.= "#| " . $key . " " . $this->cleanExport($line) . "\n";
582 1
                        }
583 1
                    }
584 1
                }
585 1
            }
586
587 15
            if (isset($entry['tcomment'])) {
588 6
                $output.= '# ' . implode("\n".'# ', $entry['tcomment']) . "\n";
589 2
            }
590
591 15
            if (isset($entry['ccomment'])) {
592 3
                $output.= '#. ' . implode("\n#. ", $entry['ccomment']) . "\n";
593 1
            }
594
595 15
            if (isset($entry['reference'])) {
596 15
                foreach ($entry['reference'] as $ref) {
597 15
                    $output.= '#: ' . $ref . "\n";
598 5
                }
599 5
            }
600
601 15
            if (isset($entry['flags']) && !empty($entry['flags'])) {
602 12
                $output.= "#, " . implode(', ', $entry['flags']) . "\n";
603 4
            }
604
605 15
            if (isset($entry['@'])) {
606
                $output.= "#@ " . $entry['@'] . "\n";
607
            }
608
609 15
            if (isset($entry['msgctxt'])) {
610 12
                $output.= 'msgctxt ' . $this->cleanExport($entry['msgctxt'][0]) . "\n";
611 4
            }
612
613
614 15
            if ($isObsolete) {
615 3
                $output.= "#~ ";
616 1
            }
617
618 15
            if (isset($entry['msgid'])) {
619
                // Special clean for msgid
620 15
                if (is_string($entry['msgid'])) {
621 3
                    $msgid = explode("\n", $entry['msgid']);
622 15
                } elseif (is_array($entry['msgid'])) {
623 15
                    $msgid = $entry['msgid'];
624 5
                } else {
625
                    throw new \Exception('msgid not string or array');
626
                }
627
628 15
                $output.= 'msgid ';
629 15
                foreach ($msgid as $i => $id) {
630 15
                    if ($i > 0 && $isObsolete) {
631 3
                        $output.= "#~ ";
632 1
                    }
633 15
                    $output.= $this->cleanExport($id) . "\n";
634 5
                }
635 5
            }
636
637 15
            if (isset($entry['msgid_plural'])) {
638
                // Special clean for msgid_plural
639 3
                if (is_string($entry['msgid_plural'])) {
640
                    $msgidPlural = explode("\n", $entry['msgid_plural']);
641 3
                } elseif (is_array($entry['msgid_plural'])) {
642 3
                    $msgidPlural = $entry['msgid_plural'];
643 1
                } else {
644
                    throw new \Exception('msgid_plural not string or array');
645
                }
646
647 3
                $output.= 'msgid_plural ';
648 3
                foreach ($msgidPlural as $plural) {
649 3
                    $output.= $this->cleanExport($plural) . "\n";
650 1
                }
651 1
            }
652
653 15
            if (count(preg_grep('/^msgstr/', array_keys($entry)))) { // checks if there is a key starting with msgstr
654 15
                if ($isPlural) {
655 3
                    foreach ($entry as $key => $value) {
656 3
                        if (strpos($key, 'msgstr[') === false) continue;
657 3
                        $output.= $key." ";
658 3
                        foreach ($value as $i => $t) {
659 3
                            $output.= $this->cleanExport($t) . "\n";
660 1
                        }
661 1
                    }
662 1
                } else {
663 15
                    foreach ((array)$entry['msgstr'] as $i => $t) {
664 15
                        if ($i == 0) {
665 15
                            if ($isObsolete) {
666 3
                                $output.= "#~ ";
667 1
                            }
668
669 15
                            $output.= 'msgstr ' . $this->cleanExport($t) . "\n";
670 5
                        } else {
671 6
                            if ($isObsolete) {
672 3
                                $output.= "#~ ";
673 1
                            }
674
675 12
                            $output.= $this->cleanExport($t) . "\n";
676
                        }
677 5
                    }
678
                }
679 5
            }
680
681 15
            $counter++;
682
            // Avoid inserting an extra newline at end of file
683 15
            if ($counter < $entriesCount) {
684 14
                $output.= "\n";
685 4
            }
686 5
        }
687
688 15
        return $output;
689
    }
690
691
692
    /**
693
     * Prepares a string to be output into a file.
694
     *
695
     * @param string $string The string to be converted.
696
     * @return string
697
     */
698 15
    protected function cleanExport($string)
699
    {
700 15
        $quote = '"';
701 15
        $slash = '\\';
702 15
        $newline = "\n";
703
704
        $replaces = array(
705 15
            "$slash" => "$slash$slash",
706 15
            "$quote" => "$slash$quote",
707 15
            "\t" => '\t',
708 5
        );
709
710 15
        $string = str_replace(array_keys($replaces), array_values($replaces), $string);
711
712 15
        $po = $quote . implode("${slash}n$quote$newline$quote", explode($newline, $string)) . $quote;
713
714
        // remove empty strings
715
        return str_replace("$newline$quote$quote", '', $po);
716
    }
717
718
719
    /**
720
     * Generates the internal key for a msgid.
721
     *
722
     * @param array $entry
723
     *
724
     * @return string
725
     */
726
    protected function getEntryId(array $entry)
727
    {
728 39
        if (isset($entry['msgctxt'])) {
729 15
            $id = implode($this->options[self::OPTION_EOL_KEY], (array)$entry['msgctxt']) . $this->options[self::OPTION_EOC_KEY] . implode($this->options[self::OPTION_EOL_KEY], (array)$entry['msgid']);
730 5
        } else {
731 36
            $id = implode($this->options[self::OPTION_EOL_KEY], (array)$entry['msgid']);
732
        }
733
734 39
        return $id;
735
    }
736
737
738
    /**
739
     * Undo `cleanExport` actions on a string.
740
     *
741
     * @param string|array $x
742
     *
743
     * @return string|array
744
     */
745
    protected function clean($x)
746
    {
747 39
        if (is_array($x)) {
748 39
            foreach ($x as $k => $v) {
749 39
                $x[$k] = $this->clean($v);
750 13
            }
751 13
        } else {
752
            // Remove double quotes from start and end of string
753 39
            if ($x == '') {
754 6
                return '';
755
            }
756
757 39
            if ($x[0] == '"') {
758 39
                $x = substr($x, 1, -1);
759 13
            }
760
761
            // Escapes C-style escape sequences (\a,\b,\f,\n,\r,\t,\v) and converts them to their actual meaning
762 39
            $x = stripcslashes($x);
763
        }
764
765 39
        return $x;
766
    }
767
768
769
    /**
770
     * Checks if entry is a header by
771
     *
772
     * @param array $entry
773
     * @return bool
774
     */
775
    protected static function isHeader(array $entry)
776
    {
777 39
        if (empty($entry) || !isset($entry['msgstr'])) {
778
            return false;
779
        }
780
781
        $headerKeys = array(
782 39
            'Project-Id-Version:' => false,
783 13
            'Report-Msgid-Bugs-To:' => false,
784 13
            'POT-Creation-Date:' => false,
785 13
            'PO-Revision-Date:' => false,
786 13
            'Last-Translator:' => false,
787 13
            'Language-Team:' => false,
788 13
            'MIME-Version:' => false,
789 13
            'Content-Type:' => false,
790 13
            'Content-Transfer-Encoding:' => false,
791
            'Plural-Forms:' => false
792 13
        );
793 39
        $keys = array_keys($headerKeys);
794
795 39
        $headerItems = 0;
796 39
        foreach ($entry['msgstr'] as $str) {
797 39
            $tokens = explode(':', $str);
798 39
            $tokens[0] = trim($tokens[0], "\"") . ':';
799
800 39
            if (in_array($tokens[0], $keys)) {
801 36
                $headerItems++;
802 36
                unset($headerKeys[$tokens[0]]);
803 38
                $keys = array_keys($headerKeys);
804 12
            }
805 13
        }
806 39
        return ($headerItems>0) ? true : false;
807
    }
808
}
809