1<?php
2
3namespace Gettext\Extractors;
4
5use Gettext\Translations;
6use Gettext\Translation;
7use Gettext\Utils\HeadersExtractorTrait;
8
9/**
10 * Class to get gettext strings from php files returning arrays.
11 */
12class Po extends Extractor implements ExtractorInterface
13{
14    use HeadersExtractorTrait;
15
16    /**
17     * Parses a .po file and append the translations found in the Translations instance.
18     *
19     * {@inheritdoc}
20     */
21    public static function fromString($string, Translations $translations, array $options = [])
22    {
23        $lines = explode("\n", $string);
24        $i = 0;
25
26        $translation = $translations->createNewTranslation('', '');
27
28        for ($n = count($lines); $i < $n; ++$i) {
29            $line = trim($lines[$i]);
30            $line = static::fixMultiLines($line, $lines, $i);
31
32            if ($line === '') {
33                if ($translation->is('', '')) {
34                    static::extractHeaders($translation->getTranslation(), $translations);
35                } elseif ($translation->hasOriginal()) {
36                    $translations[] = $translation;
37                }
38
39                $translation = $translations->createNewTranslation('', '');
40                continue;
41            }
42
43            $splitLine = preg_split('/\s+/', $line, 2);
44            $key = $splitLine[0];
45            $data = isset($splitLine[1]) ? $splitLine[1] : '';
46
47            if ($key === '#~') {
48                $translation->setDisabled(true);
49
50                $splitLine = preg_split('/\s+/', $data, 2);
51                $key = $splitLine[0];
52                $data = isset($splitLine[1]) ? $splitLine[1] : '';
53            }
54
55            switch ($key) {
56                case '#':
57                    $translation->addComment($data);
58                    $append = null;
59                    break;
60
61                case '#.':
62                    $translation->addExtractedComment($data);
63                    $append = null;
64                    break;
65
66                case '#,':
67                    foreach (array_map('trim', explode(',', trim($data))) as $value) {
68                        $translation->addFlag($value);
69                    }
70                    $append = null;
71                    break;
72
73                case '#:':
74                    foreach (preg_split('/\s+/', trim($data)) as $value) {
75                        if (preg_match('/^(.+)(:(\d*))?$/U', $value, $matches)) {
76                            $translation->addReference($matches[1], isset($matches[3]) ? $matches[3] : null);
77                        }
78                    }
79                    $append = null;
80                    break;
81
82                case 'msgctxt':
83                    $translation = $translation->getClone(static::convertString($data));
84                    $append = 'Context';
85                    break;
86
87                case 'msgid':
88                    $translation = $translation->getClone(null, static::convertString($data));
89                    $append = 'Original';
90                    break;
91
92                case 'msgid_plural':
93                    $translation->setPlural(static::convertString($data));
94                    $append = 'Plural';
95                    break;
96
97                case 'msgstr':
98                case 'msgstr[0]':
99                    $translation->setTranslation(static::convertString($data));
100                    $append = 'Translation';
101                    break;
102
103                case 'msgstr[1]':
104                    $translation->setPluralTranslations([static::convertString($data)]);
105                    $append = 'PluralTranslation';
106                    break;
107
108                default:
109                    if (strpos($key, 'msgstr[') === 0) {
110                        $p = $translation->getPluralTranslations();
111                        $p[] = static::convertString($data);
112
113                        $translation->setPluralTranslations($p);
114                        $append = 'PluralTranslation';
115                        break;
116                    }
117
118                    if (isset($append)) {
119                        if ($append === 'Context') {
120                            $translation = $translation->getClone($translation->getContext()
121                                ."\n"
122                                .static::convertString($data));
123                            break;
124                        }
125
126                        if ($append === 'Original') {
127                            $translation = $translation->getClone(null, $translation->getOriginal()
128                                ."\n"
129                                .static::convertString($data));
130                            break;
131                        }
132
133                        if ($append === 'PluralTranslation') {
134                            $p = $translation->getPluralTranslations();
135                            $p[] = array_pop($p)."\n".static::convertString($data);
136                            $translation->setPluralTranslations($p);
137                            break;
138                        }
139
140                        $getMethod = 'get'.$append;
141                        $setMethod = 'set'.$append;
142                        $translation->$setMethod($translation->$getMethod()."\n".static::convertString($data));
143                    }
144                    break;
145            }
146        }
147
148        if ($translation->hasOriginal() && !in_array($translation, iterator_to_array($translations))) {
149            $translations[] = $translation;
150        }
151    }
152
153    /**
154     * Gets one string from multiline strings.
155     *
156     * @param string $line
157     * @param array  $lines
158     * @param int    &$i
159     *
160     * @return string
161     */
162    protected static function fixMultiLines($line, array $lines, &$i)
163    {
164        for ($j = $i, $t = count($lines); $j < $t; ++$j) {
165            if (substr($line, -1, 1) == '"'
166                && isset($lines[$j + 1])
167                && substr(trim($lines[$j + 1]), 0, 1) == '"'
168            ) {
169                $line = substr($line, 0, -1).substr(trim($lines[$j + 1]), 1);
170            } else {
171                $i = $j;
172                break;
173            }
174        }
175
176        return $line;
177    }
178
179    /**
180     * Convert a string from its PO representation.
181     *
182     * @param string $value
183     *
184     * @return string
185     */
186    public static function convertString($value)
187    {
188        if (!$value) {
189            return '';
190        }
191
192        if ($value[0] === '"') {
193            $value = substr($value, 1, -1);
194        }
195
196        return strtr(
197            $value,
198            [
199                '\\\\' => '\\',
200                '\\a' => "\x07",
201                '\\b' => "\x08",
202                '\\t' => "\t",
203                '\\n' => "\n",
204                '\\v' => "\x0b",
205                '\\f' => "\x0c",
206                '\\r' => "\r",
207                '\\"' => '"',
208            ]
209        );
210    }
211}
212