1 <?php |
1 <?php |
2 /** |
2 /** |
3 * Class for working with PO files |
3 * Class for working with PO files |
4 * |
4 * |
5 * @version $Id: po.php 718 2012-10-31 00:32:02Z nbachiyski $ |
5 * @version $Id: po.php 1158 2015-11-20 04:31:23Z dd32 $ |
6 * @package pomo |
6 * @package pomo |
7 * @subpackage po |
7 * @subpackage po |
8 */ |
8 */ |
9 |
9 |
10 require_once dirname(__FILE__) . '/translations.php'; |
10 require_once dirname(__FILE__) . '/translations.php'; |
11 |
11 |
12 define('PO_MAX_LINE_LEN', 79); |
12 if ( ! defined( 'PO_MAX_LINE_LEN' ) ) { |
|
13 define('PO_MAX_LINE_LEN', 79); |
|
14 } |
13 |
15 |
14 ini_set('auto_detect_line_endings', 1); |
16 ini_set('auto_detect_line_endings', 1); |
15 |
17 |
16 /** |
18 /** |
17 * Routines for working with PO files |
19 * Routines for working with PO files |
18 */ |
20 */ |
19 if ( !class_exists( 'PO' ) ): |
21 if ( ! class_exists( 'PO', false ) ): |
20 class PO extends Gettext_Translations { |
22 class PO extends Gettext_Translations { |
21 |
23 |
22 var $comments_before_headers = ''; |
24 var $comments_before_headers = ''; |
23 |
25 |
24 /** |
26 /** |
126 * |
128 * |
127 * @static |
129 * @static |
128 * @param string $string PO-formatted string |
130 * @param string $string PO-formatted string |
129 * @return string enascaped string |
131 * @return string enascaped string |
130 */ |
132 */ |
131 function unpoify($string) { |
133 public static function unpoify($string) { |
132 $escapes = array('t' => "\t", 'n' => "\n", '\\' => '\\'); |
134 $escapes = array('t' => "\t", 'n' => "\n", 'r' => "\r", '\\' => '\\'); |
133 $lines = array_map('trim', explode("\n", $string)); |
135 $lines = array_map('trim', explode("\n", $string)); |
134 $lines = array_map(array('PO', 'trim_quotes'), $lines); |
136 $lines = array_map(array('PO', 'trim_quotes'), $lines); |
135 $unpoified = ''; |
137 $unpoified = ''; |
136 $previous_is_backslash = false; |
138 $previous_is_backslash = false; |
137 foreach($lines as $line) { |
139 foreach($lines as $line) { |
158 * |
164 * |
159 * @static |
165 * @static |
160 * @param string $string prepend lines in this string |
166 * @param string $string prepend lines in this string |
161 * @param string $with prepend lines with this string |
167 * @param string $with prepend lines with this string |
162 */ |
168 */ |
163 function prepend_each_line($string, $with) { |
169 public static function prepend_each_line($string, $with) { |
164 $php_with = var_export($with, true); |
|
165 $lines = explode("\n", $string); |
170 $lines = explode("\n", $string); |
166 // do not prepend the string on the last empty line, artefact by explode |
171 $append = ''; |
167 if ("\n" == substr($string, -1)) unset($lines[count($lines) - 1]); |
172 if ("\n" === substr($string, -1) && '' === end($lines)) { |
168 $res = implode("\n", array_map(create_function('$x', "return $php_with.\$x;"), $lines)); |
173 // Last line might be empty because $string was terminated |
169 // give back the empty line, we ignored above |
174 // with a newline, remove it from the $lines array, |
170 if ("\n" == substr($string, -1)) $res .= "\n"; |
175 // we'll restore state by re-terminating the string at the end |
171 return $res; |
176 array_pop($lines); |
|
177 $append = "\n"; |
|
178 } |
|
179 foreach ($lines as &$line) { |
|
180 $line = $with . $line; |
|
181 } |
|
182 unset($line); |
|
183 return implode("\n", $lines) . $append; |
172 } |
184 } |
173 |
185 |
174 /** |
186 /** |
175 * Prepare a text as a comment -- wraps the lines and prepends # |
187 * Prepare a text as a comment -- wraps the lines and prepends # |
176 * and a special character to each line |
188 * and a special character to each line |
178 * @access private |
190 * @access private |
179 * @param string $text the comment text |
191 * @param string $text the comment text |
180 * @param string $char character to denote a special PO comment, |
192 * @param string $char character to denote a special PO comment, |
181 * like :, default is a space |
193 * like :, default is a space |
182 */ |
194 */ |
183 function comment_block($text, $char=' ') { |
195 public static function comment_block($text, $char=' ') { |
184 $text = wordwrap($text, PO_MAX_LINE_LEN - 3); |
196 $text = wordwrap($text, PO_MAX_LINE_LEN - 3); |
185 return PO::prepend_each_line($text, "#$char "); |
197 return PO::prepend_each_line($text, "#$char "); |
186 } |
198 } |
187 |
199 |
188 /** |
200 /** |
189 * Builds a string from the entry for inclusion in PO file |
201 * Builds a string from the entry for inclusion in PO file |
190 * |
202 * |
191 * @static |
203 * @static |
192 * @param Translation_Entry &$entry the entry to convert to po string |
204 * @param Translation_Entry $entry the entry to convert to po string (passed by reference). |
193 * @return false|string PO-style formatted string for the entry or |
205 * @return false|string PO-style formatted string for the entry or |
194 * false if the entry is empty |
206 * false if the entry is empty |
195 */ |
207 */ |
196 function export_entry(&$entry) { |
208 public static function export_entry(&$entry) { |
197 if (is_null($entry->singular)) return false; |
209 if ( null === $entry->singular || '' === $entry->singular ) return false; |
198 $po = array(); |
210 $po = array(); |
199 if (!empty($entry->translator_comments)) $po[] = PO::comment_block($entry->translator_comments); |
211 if (!empty($entry->translator_comments)) $po[] = PO::comment_block($entry->translator_comments); |
200 if (!empty($entry->extracted_comments)) $po[] = PO::comment_block($entry->extracted_comments, '.'); |
212 if (!empty($entry->extracted_comments)) $po[] = PO::comment_block($entry->extracted_comments, '.'); |
201 if (!empty($entry->references)) $po[] = PO::comment_block(implode(' ', $entry->references), ':'); |
213 if (!empty($entry->references)) $po[] = PO::comment_block(implode(' ', $entry->references), ':'); |
202 if (!empty($entry->flags)) $po[] = PO::comment_block(implode(", ", $entry->flags), ','); |
214 if (!empty($entry->flags)) $po[] = PO::comment_block(implode(", ", $entry->flags), ','); |
203 if (!is_null($entry->context)) $po[] = 'msgctxt '.PO::poify($entry->context); |
215 if ($entry->context) $po[] = 'msgctxt '.PO::poify($entry->context); |
204 $po[] = 'msgid '.PO::poify($entry->singular); |
216 $po[] = 'msgid '.PO::poify($entry->singular); |
205 if (!$entry->is_plural) { |
217 if (!$entry->is_plural) { |
206 $translation = empty($entry->translations)? '' : $entry->translations[0]; |
218 $translation = empty($entry->translations)? '' : $entry->translations[0]; |
|
219 $translation = PO::match_begin_and_end_newlines( $translation, $entry->singular ); |
207 $po[] = 'msgstr '.PO::poify($translation); |
220 $po[] = 'msgstr '.PO::poify($translation); |
208 } else { |
221 } else { |
209 $po[] = 'msgid_plural '.PO::poify($entry->plural); |
222 $po[] = 'msgid_plural '.PO::poify($entry->plural); |
210 $translations = empty($entry->translations)? array('', '') : $entry->translations; |
223 $translations = empty($entry->translations)? array('', '') : $entry->translations; |
211 foreach($translations as $i => $translation) { |
224 foreach($translations as $i => $translation) { |
|
225 $translation = PO::match_begin_and_end_newlines( $translation, $entry->plural ); |
212 $po[] = "msgstr[$i] ".PO::poify($translation); |
226 $po[] = "msgstr[$i] ".PO::poify($translation); |
213 } |
227 } |
214 } |
228 } |
215 return implode("\n", $po); |
229 return implode("\n", $po); |
|
230 } |
|
231 |
|
232 public static function match_begin_and_end_newlines( $translation, $original ) { |
|
233 if ( '' === $translation ) { |
|
234 return $translation; |
|
235 } |
|
236 |
|
237 $original_begin = "\n" === substr( $original, 0, 1 ); |
|
238 $original_end = "\n" === substr( $original, -1 ); |
|
239 $translation_begin = "\n" === substr( $translation, 0, 1 ); |
|
240 $translation_end = "\n" === substr( $translation, -1 ); |
|
241 |
|
242 if ( $original_begin ) { |
|
243 if ( ! $translation_begin ) { |
|
244 $translation = "\n" . $translation; |
|
245 } |
|
246 } elseif ( $translation_begin ) { |
|
247 $translation = ltrim( $translation, "\n" ); |
|
248 } |
|
249 |
|
250 if ( $original_end ) { |
|
251 if ( ! $translation_end ) { |
|
252 $translation .= "\n"; |
|
253 } |
|
254 } elseif ( $translation_end ) { |
|
255 $translation = rtrim( $translation, "\n" ); |
|
256 } |
|
257 |
|
258 return $translation; |
216 } |
259 } |
217 |
260 |
218 /** |
261 /** |
219 * @param string $filename |
262 * @param string $filename |
220 * @return boolean |
263 * @return boolean |
241 } |
284 } |
242 return true; |
285 return true; |
243 } |
286 } |
244 |
287 |
245 /** |
288 /** |
|
289 * Helper function for read_entry |
|
290 * @param string $context |
|
291 * @return bool |
|
292 */ |
|
293 protected static function is_final($context) { |
|
294 return ($context === 'msgstr') || ($context === 'msgstr_plural'); |
|
295 } |
|
296 |
|
297 /** |
246 * @param resource $f |
298 * @param resource $f |
247 * @param int $lineno |
299 * @param int $lineno |
248 * @return null|false|array |
300 * @return null|false|array |
249 */ |
301 */ |
250 function read_entry($f, $lineno = 0) { |
302 function read_entry($f, $lineno = 0) { |
251 $entry = new Translation_Entry(); |
303 $entry = new Translation_Entry(); |
252 // where were we in the last step |
304 // where were we in the last step |
253 // can be: comment, msgctxt, msgid, msgid_plural, msgstr, msgstr_plural |
305 // can be: comment, msgctxt, msgid, msgid_plural, msgstr, msgstr_plural |
254 $context = ''; |
306 $context = ''; |
255 $msgstr_index = 0; |
307 $msgstr_index = 0; |
256 $is_final = create_function('$context', 'return $context == "msgstr" || $context == "msgstr_plural";'); |
|
257 while (true) { |
308 while (true) { |
258 $lineno++; |
309 $lineno++; |
259 $line = PO::read_line($f); |
310 $line = PO::read_line($f); |
260 if (!$line) { |
311 if (!$line) { |
261 if (feof($f)) { |
312 if (feof($f)) { |
262 if ($is_final($context)) |
313 if (self::is_final($context)) |
263 break; |
314 break; |
264 elseif (!$context) // we haven't read a line and eof came |
315 elseif (!$context) // we haven't read a line and eof came |
265 return null; |
316 return null; |
266 else |
317 else |
267 return false; |
318 return false; |
283 return false; |
334 return false; |
284 } |
335 } |
285 // add comment |
336 // add comment |
286 $this->add_comment_to_entry($entry, $line); |
337 $this->add_comment_to_entry($entry, $line); |
287 } elseif (preg_match('/^msgctxt\s+(".*")/', $line, $m)) { |
338 } elseif (preg_match('/^msgctxt\s+(".*")/', $line, $m)) { |
288 if ($is_final($context)) { |
339 if (self::is_final($context)) { |
289 PO::read_line($f, 'put-back'); |
340 PO::read_line($f, 'put-back'); |
290 $lineno--; |
341 $lineno--; |
291 break; |
342 break; |
292 } |
343 } |
293 if ($context && $context != 'comment') { |
344 if ($context && $context != 'comment') { |
294 return false; |
345 return false; |
295 } |
346 } |
296 $context = 'msgctxt'; |
347 $context = 'msgctxt'; |
297 $entry->context .= PO::unpoify($m[1]); |
348 $entry->context .= PO::unpoify($m[1]); |
298 } elseif (preg_match('/^msgid\s+(".*")/', $line, $m)) { |
349 } elseif (preg_match('/^msgid\s+(".*")/', $line, $m)) { |
299 if ($is_final($context)) { |
350 if (self::is_final($context)) { |
300 PO::read_line($f, 'put-back'); |
351 PO::read_line($f, 'put-back'); |
301 $lineno--; |
352 $lineno--; |
302 break; |
353 break; |
303 } |
354 } |
304 if ($context && $context != 'msgctxt' && $context != 'comment') { |
355 if ($context && $context != 'msgctxt' && $context != 'comment') { |
344 } |
395 } |
345 } else { |
396 } else { |
346 return false; |
397 return false; |
347 } |
398 } |
348 } |
399 } |
349 if (array() == array_filter($entry->translations, create_function('$t', 'return $t || "0" === $t;'))) { |
400 |
|
401 $have_translations = false; |
|
402 foreach ( $entry->translations as $t ) { |
|
403 if ( $t || ('0' === $t) ) { |
|
404 $have_translations = true; |
|
405 break; |
|
406 } |
|
407 } |
|
408 if ( false === $have_translations ) { |
350 $entry->translations = array(); |
409 $entry->translations = array(); |
351 } |
410 } |
|
411 |
352 return array('entry' => $entry, 'lineno' => $lineno); |
412 return array('entry' => $entry, 'lineno' => $lineno); |
353 } |
413 } |
354 |
414 |
355 /** |
415 /** |
356 * @staticvar string $last_line |
416 * @staticvar string $last_line |
357 * @staticvar boolean $use_last_line |
417 * @staticvar boolean $use_last_line |
|
418 * |
358 * @param resource $f |
419 * @param resource $f |
359 * @param string $action |
420 * @param string $action |
360 * @return boolean |
421 * @return boolean |
361 */ |
422 */ |
362 function read_line($f, $action = 'read') { |
423 function read_line($f, $action = 'read') { |