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['@'])) { |
||
0 ignored issues
–
show
|
|||
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) { |
||
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) { |
||
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) { |
||
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) { |
||
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) { |
||
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 |
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.