Completed
Push — v4.0-dev ( 627b53...5ca120 )
by Oscar
02:44
created

Po::parseHeaders()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 3 Features 0
Metric Value
c 3
b 3
f 0
dl 0
loc 22
rs 8.9197
cc 4
eloc 14
nc 4
nop 2
1
<?php
2
3
namespace Gettext\Extractors;
4
5
use Gettext\Translations;
6
use Gettext\Translation;
7
8
/**
9
 * Class to get gettext strings from php files returning arrays.
10
 */
11
class Po extends Extractor implements ExtractorInterface
12
{
13
    /**
14
     * Parses a .po file and append the translations found in the Translations instance.
15
     *
16
     * {@inheritdoc}
17
     */
18
    public static function fromString($string, Translations $translations = null, $file = '')
19
    {
20
        if ($translations === null) {
21
            $translations = new Translations();
22
        }
23
24
        $lines = explode("\n", $string);
25
        $i = 0;
26
27
        $translation = new Translation('', '');
28
29
        for ($n = count($lines); $i < $n; ++$i) {
30
            $line = trim($lines[$i]);
31
32
            $line = self::fixMultiLines($line, $lines, $i);
33
34
            if ($line === '') {
35
                if ($translation->is('', '')) {
36
                    self::parseHeaders($translation->getTranslation(), $translations);
37
                } elseif ($translation->hasOriginal()) {
38
                    $translations[] = $translation;
39
                }
40
41
                $translation = new Translation('', '');
42
                continue;
43
            }
44
45
            $splitLine = preg_split('/\s+/', $line, 2);
46
            $key = $splitLine[0];
47
            $data = isset($splitLine[1]) ? $splitLine[1] : '';
48
49
            switch ($key) {
50
                case '#':
51
                    $translation->addComment($data);
52
                    $append = null;
53
                    break;
54
55
                case '#.':
56
                    $translation->addExtractedComment($data);
57
                    $append = null;
58
                    break;
59
60
                case '#,':
61
                    foreach (array_map('trim', explode(',', trim($data))) as $value) {
62
                        $translation->addFlag($value);
63
                    }
64
                    $append = null;
65
                    break;
66
67
                case '#:':
68
                    foreach (preg_split('/\s+/', trim($data)) as $value) {
69
                        if (preg_match('/^(.+)(:(\d*))?$/U', $value, $matches)) {
70
                            $translation->addReference($matches[1], isset($matches[3]) ? $matches[3] : null);
71
                        }
72
                    }
73
                    $append = null;
74
                    break;
75
76
                case 'msgctxt':
77
                    $translation = $translation->getClone(self::convertString($data));
78
                    $append = 'Context';
79
                    break;
80
81
                case 'msgid':
82
                    $translation = $translation->getClone(null, self::convertString($data));
83
                    $append = 'Original';
84
                    break;
85
86
                case 'msgid_plural':
87
                    $translation->setPlural(self::convertString($data));
88
                    $append = 'Plural';
89
                    break;
90
91
                case 'msgstr':
92
                case 'msgstr[0]':
93
                    $translation->setTranslation(self::convertString($data));
94
                    $append = 'Translation';
95
                    break;
96
97
                case 'msgstr[1]':
98
                    $translation->setPluralTranslations([self::convertString($data)]);
99
                    $append = 'PluralTranslation';
100
                    break;
101
102
                default:
103 View Code Duplication
                    if (strpos($key, 'msgstr[') === 0) {
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...
104
                        $p = $translation->getPluralTranslations();
105
                        $p[] = self::convertString($data);
106
107
                        $translation->setPluralTranslations($p);
108
                        $append = 'PluralTranslation';
109
                        break;
110
                    }
111
112
                    if (isset($append)) {
113
                        if ($append === 'Context') {
114
                            $translation = $translation->getClone($translation->getContext()."\n".self::convertString($data));
115
                            break;
116
                        }
117
118
                        if ($append === 'Original') {
119
                            $translation = $translation->getClone(null, $translation->getOriginal()."\n".self::convertString($data));
120
                            break;
121
                        }
122
123 View Code Duplication
                        if ($append === 'PluralTranslation') {
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...
124
                            $p = $translation->getPluralTranslations();
125
                            $p[] = array_pop($p)."\n".self::convertString($data);
126
                            $translations->setPluralTranslations($p);
0 ignored issues
show
Documentation Bug introduced by
The method setPluralTranslations does not exist on object<Gettext\Translations>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
127
                            break;
128
                        }
129
130
                        $getMethod = 'get'.$append;
131
                        $setMethod = 'set'.$append;
132
                        $translation->$setMethod($translation->$getMethod()."\n".self::convertString($data));
133
                    }
134
                    break;
135
            }
136
        }
137
138
        if ($translation->hasOriginal() && !in_array($translation, iterator_to_array($translations))) {
139
            $translations[] = $translation;
140
        }
141
142
        return $translations;
143
    }
144
145
    /**
146
     * Checks if it is a header definition line. Useful for distguishing between header definitions
147
     * and possible continuations of a header entry.
148
     *
149
     * @param string $line Line to parse
150
     *
151
     * @return bool
152
     */
153
    private static function isHeaderDefinition($line)
154
    {
155
        return (bool) preg_match('/^[\w-]+:/', $line);
156
    }
157
158
    /**
159
     * Parse the po headers.
160
     *
161
     * @param string       $headers
162
     * @param Translations $translations
163
     */
164
    private static function parseHeaders($headers, Translations $translations)
165
    {
166
        $headers = explode("\n", $headers);
167
        $currentHeader = null;
168
169
        foreach ($headers as $line) {
170
            $line = self::convertString($line);
171
172
            if ($line === '') {
173
                continue;
174
            }
175
176
            if (self::isHeaderDefinition($line)) {
177
                $header = array_map('trim', explode(':', $line, 2));
178
                $currentHeader = $header[0];
179
                $translations->setHeader($currentHeader, $header[1]);
180
            } else {
181
                $entry = $translations->getHeader($currentHeader);
182
                $translations->setHeader($currentHeader, $entry.$line);
183
            }
184
        }
185
    }
186
187
    /**
188
     * Gets one string from multiline strings.
189
     *
190
     * @param string $line
191
     * @param array  $lines
192
     * @param int    &$i
193
     *
194
     * @return string
195
     */
196
    private static function fixMultiLines($line, array $lines, &$i)
197
    {
198
        for ($j = $i, $t = count($lines); $j < $t; ++$j) {
199
            if (substr($line, -1, 1) == '"'
200
                && isset($lines[$j + 1])
201
                && substr(trim($lines[$j + 1]), 0, 1) == '"'
202
            ) {
203
                $line = substr($line, 0, -1).substr(trim($lines[$j + 1]), 1);
204
            } else {
205
                $i = $j;
206
                break;
207
            }
208
        }
209
210
        return $line;
211
    }
212
213
    /**
214
     * Convert a string from its PO representation.
215
     *
216
     * @param string $value
217
     *
218
     * @return string
219
     */
220
    public static function convertString($value)
221
    {
222
        if (!$value) {
223
            return '';
224
        }
225
226
        if ($value[0] === '"') {
227
            $value = substr($value, 1, -1);
228
        }
229
230
        return strtr(
231
            $value,
232
            array(
233
                '\\\\' => '\\',
234
                '\\a' => "\x07",
235
                '\\b' => "\x08",
236
                '\\t' => "\t",
237
                '\\n' => "\n",
238
                '\\v' => "\x0b",
239
                '\\f' => "\x0c",
240
                '\\r' => "\r",
241
                '\\"' => '"',
242
            )
243
        );
244
    }
245
}
246