Issues (10)

src/PoCompiler.php (5 issues)

1
<?php
2
3
namespace Sepia\PoParser;
4
5
use Sepia\PoParser\Catalog\Catalog;
6
use Sepia\PoParser\Catalog\Entry;
7
use Sepia\PoParser\Catalog\Header;
8
9
class PoCompiler
10
{
11
    /** @var string */
12
    const TOKEN_OBSOLETE = '#~ ';
13
14
    /** @var int */
15
    protected $wrappingColumn;
16
17
    /** @var string */
18
    protected $lineEnding;
19
20
    /** @var string */
21
    protected $tokenCarriageReturn;
22
23
    /**
24
     * PoCompiler constructor.
25
     *
26
     * @param int    $wrappingColumn
27
     * @param string $lineEnding
28
     */
29
    public function __construct($wrappingColumn = 80, $lineEnding = "\n")
30
    {
31
        $this->wrappingColumn = $wrappingColumn;
32
        $this->lineEnding = $lineEnding;
33
        $this->tokenCarriageReturn = chr(13);
34
    }
35
36
    /**
37
     * Compiles entries into a string
38
     *
39
     * @param Catalog $catalog
40
     *
41
     * @return string
42
     * @throws \Exception
43
     * @todo Write obsolete messages at the end of the file.
44
     */
45
    public function compile(Catalog $catalog)
46
    {
47
        $output = '';
48
49
        if (\count($catalog->getHeaders()) > 0) {
50
            $output .= 'msgid ""'.$this->eol();
51
            $output .= 'msgstr ""'.$this->eol();
52
            foreach ($catalog->getHeaders() as $header) {
53
                $output .= '"'.$header.'\n"'.$this->eol();
54
            }
55
            $output .= $this->eol();
56
        }
57
58
59
        $entriesCount = \count($catalog->getEntries());
60
        $counter = 0;
61
        foreach ($catalog->getEntries() as $entry) {
62
            if ($entry->isObsolete() === false) {
63
                $output .= $this->buildPreviousEntry($entry);
64
                $output .= $this->buildTranslatorComment($entry);
65
                $output .= $this->buildDeveloperComment($entry);
66
                $output .= $this->buildReference($entry);
67
            }
68
69
            $output .= $this->buildFlags($entry);
70
71
//            if (isset($entry['@'])) {
72
//                $output .= "#@ ".$entry['@'].$this->eol();
73
//            }
74
75
            $output .= $this->buildContext($entry);
76
            $output .= $this->buildMsgId($entry);
77
            $output .= $this->buildMsgIdPlural($entry);
78
            $output .= $this->buildMsgStr($entry, $catalog->getHeader());
79
80
81
            $counter++;
82
            // Avoid inserting an extra newline at end of file
83
            if ($counter < $entriesCount) {
84
                $output .= $this->eol();
85
            }
86
        }
87
88
        return $output;
89
    }
90
91
    /**
92
     * @return string
93
     */
94
    protected function eol()
95
    {
96
        return $this->lineEnding;
97
    }
98
99
    /**
100
     * @param $entry
101
     *
102
     * @return string
103
     */
104
    protected function buildPreviousEntry(Entry $entry)
105
    {
106
        $previous = $entry->getPreviousEntry();
107
        if ($previous === null) {
108
            return '';
109
        }
110
111
        return '#| msgid '.$this->cleanExport($previous->getMsgId()).$this->eol();
112
    }
113
114
    /**
115
     * @param $entry
116
     *
117
     * @return string
118
     */
119
    protected function buildTranslatorComment(Entry $entry)
120
    {
121
        if ($entry->getTranslatorComments() === null) {
0 ignored issues
show
The condition $entry->getTranslatorComments() === null is always false.
Loading history...
122
            return '';
123
        }
124
125
        $output = '';
126
        foreach ($entry->getTranslatorComments() as $comment) {
127
            $output .= '# '.$comment.$this->eol();
128
        }
129
130
        return $output;
131
    }
132
133
    protected function buildDeveloperComment(Entry $entry)
134
    {
135
        if ($entry->getDeveloperComments() === null) {
0 ignored issues
show
The condition $entry->getDeveloperComments() === null is always false.
Loading history...
136
            return '';
137
        }
138
139
        $output = '';
140
        foreach ($entry->getDeveloperComments() as $comment) {
141
            $output .= '#. '.$comment.$this->eol();
142
        }
143
144
        return $output;
145
    }
146
147
    protected function buildReference(Entry $entry)
148
    {
149
        $reference = $entry->getReference();
150
        if ($reference === null || \count($reference) === 0) {
151
            return '';
152
        }
153
154
        $output = '';
155
        foreach ($reference as $ref) {
156
            $output .= '#: '.$ref.$this->eol();
157
        }
158
159
        return $output;
160
    }
161
162
    protected function buildFlags(Entry $entry)
163
    {
164
        $flags = $entry->getFlags();
165
        if ($flags === null || \count($flags) === 0) {
166
            return '';
167
        }
168
169
        return '#, '.\implode(', ', $flags).$this->eol();
170
    }
171
172
    protected function buildContext(Entry $entry)
173
    {
174
        if ($entry->getMsgCtxt() === null) {
175
            return '';
176
        }
177
178
        return
179
            ($entry->isObsolete() ? '#~ ' : '').
180
            'msgctxt '.$this->cleanExport($entry->getMsgCtxt()).$this->eol();
181
    }
182
183
    protected function buildMsgId(Entry $entry)
184
    {
185
        if ($entry->getMsgId() === null) {
0 ignored issues
show
The condition $entry->getMsgId() === null is always false.
Loading history...
186
            return '';
187
        }
188
189
        return $this->buildProperty('msgid', $entry->getMsgId(), $entry->isObsolete());
190
    }
191
192
    protected function buildMsgStr(Entry $entry, Header $headers)
193
    {
194
        $value = $entry->getMsgStr();
195
        $plurals = $entry->getMsgStrPlurals();
196
197
        if ($value === null && $plurals === null) {
0 ignored issues
show
The condition $value === null is always false.
Loading history...
198
            return '';
199
        }
200
201
        if ($entry->isPlural()) {
202
            $output = '';
203
            $nPlurals = $headers->getPluralFormsCount();
204
            $pluralsFound = \count($plurals);
205
            $maxIterations = \max($nPlurals, $pluralsFound);
206
            for ($i = 0; $i < $maxIterations; $i++) {
207
                $value = isset($plurals[$i]) ? $plurals[$i] : '';
208
                $output .= $entry->isObsolete() ? self::TOKEN_OBSOLETE : '';
209
                $output .= 'msgstr['.$i.'] '.$this->cleanExport($value).$this->eol();
210
            }
211
212
            return $output;
213
        }
214
215
        return $this->buildProperty('msgstr', $value, $entry->isObsolete());
216
    }
217
218
    /**
219
     * @param Entry $entry
220
     *
221
     * @return string
222
     */
223
    protected function buildMsgIdPlural(Entry $entry)
224
    {
225
        $value = $entry->getMsgIdPlural();
226
        if ($value === null) {
0 ignored issues
show
The condition $value === null is always false.
Loading history...
227
            return '';
228
        }
229
230
        $output = '';
231
        $output .= $entry->isObsolete() ? self::TOKEN_OBSOLETE : '';
232
        $output .= 'msgid_plural '.$this->cleanExport($value).$this->eol();
233
        return $output;
234
    }
235
236
    protected function buildProperty($property, $value, $obsolete = false)
237
    {
238
        $tokens = $this->wrapString($value);
239
240
        $output = '';
241
        if (\count($tokens) > 1) {
242
            \array_unshift($tokens, '');
243
        }
244
245
        foreach ($tokens as $i => $token) {
246
            $output .= $obsolete ? self::TOKEN_OBSOLETE : '';
247
            $output .= ($i === 0) ? $property.' ' : '';
248
            $output .= $this->cleanExport($token).$this->eol();
249
        }
250
251
        return $output;
252
    }
253
254
    /**
255
     * Prepares a string to be outputed into a file.
256
     *
257
     * @param string $string The string to be converted.
258
     *
259
     * @return string
260
     */
261
    protected function cleanExport($string)
262
    {
263
        // only quotation mark (" or \42) and backslash (\ or \134) chars needs to be escaped
264
        $string = sprintf('"%s"', addcslashes($string, "\42\134"));
265
266
        // Replace newline character with \n after addcslashes to prevent escaping backslash.
267
        return str_replace($this->tokenCarriageReturn, '\n', $string);
268
    }
269
270
    /**
271
     * @param string $value
272
     * @return array
273
     */
274
    private function wrapString($value)
275
    {
276
        // value that are most likely never present in the $value
277
        $fileSeparator = chr(28);
278
279
        /**
280
         * Replace newline character with the same value that is used for wrapping ($fileSeparator)
281
         * and with another character ($this->tokenCarriageReturn) that is later replaced back and thus
282
         * newline won't be escaped by addcslashes function
283
         */
284
        $value = str_replace("\n", $this->tokenCarriageReturn . $fileSeparator, $value);
285
        $wrapped = \wordwrap($value, $this->wrappingColumn, ' ' . $fileSeparator);
286
287
        return \explode(chr(28), $wrapped);
288
    }
289
}
290