5 * @version $Id: po.php 1158 2015-11-20 04:31:23Z dd32 $ |
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 if ( ! defined( 'PO_MAX_LINE_LEN' ) ) { |
12 if ( ! defined( 'PO_MAX_LINE_LEN' ) ) { |
13 define('PO_MAX_LINE_LEN', 79); |
13 define( 'PO_MAX_LINE_LEN', 79 ); |
14 } |
14 } |
15 |
15 |
16 ini_set('auto_detect_line_endings', 1); |
16 ini_set( 'auto_detect_line_endings', 1 ); |
17 |
17 |
18 /** |
18 /** |
19 * Routines for working with PO files |
19 * Routines for working with PO files |
20 */ |
20 */ |
21 if ( ! class_exists( 'PO', false ) ): |
21 if ( ! class_exists( 'PO', false ) ) : |
22 class PO extends Gettext_Translations { |
22 class PO extends Gettext_Translations { |
23 |
23 |
24 var $comments_before_headers = ''; |
24 var $comments_before_headers = ''; |
25 |
25 |
26 /** |
26 /** |
27 * Exports headers to a PO entry |
27 * Exports headers to a PO entry |
28 * |
28 * |
29 * @return string msgid/msgstr PO entry for this PO file headers, doesn't contain newline at the end |
29 * @return string msgid/msgstr PO entry for this PO file headers, doesn't contain newline at the end |
30 */ |
30 */ |
31 function export_headers() { |
31 function export_headers() { |
32 $header_string = ''; |
32 $header_string = ''; |
33 foreach($this->headers as $header => $value) { |
33 foreach ( $this->headers as $header => $value ) { |
34 $header_string.= "$header: $value\n"; |
34 $header_string .= "$header: $value\n"; |
35 } |
35 } |
36 $poified = PO::poify($header_string); |
36 $poified = PO::poify( $header_string ); |
37 if ($this->comments_before_headers) |
37 if ( $this->comments_before_headers ) { |
38 $before_headers = $this->prepend_each_line(rtrim($this->comments_before_headers)."\n", '# '); |
38 $before_headers = $this->prepend_each_line( rtrim( $this->comments_before_headers ) . "\n", '# ' ); |
39 else |
39 } else { |
40 $before_headers = ''; |
40 $before_headers = ''; |
41 return rtrim("{$before_headers}msgid \"\"\nmsgstr $poified"); |
41 } |
42 } |
42 return rtrim( "{$before_headers}msgid \"\"\nmsgstr $poified" ); |
43 |
43 } |
44 /** |
44 |
45 * Exports all entries to PO format |
45 /** |
46 * |
46 * Exports all entries to PO format |
47 * @return string sequence of mgsgid/msgstr PO strings, doesn't containt newline at the end |
47 * |
48 */ |
48 * @return string sequence of mgsgid/msgstr PO strings, doesn't containt newline at the end |
49 function export_entries() { |
49 */ |
50 //TODO sorting |
50 function export_entries() { |
51 return implode("\n\n", array_map(array('PO', 'export_entry'), $this->entries)); |
51 //TODO sorting |
52 } |
52 return implode( "\n\n", array_map( array( 'PO', 'export_entry' ), $this->entries ) ); |
53 |
53 } |
54 /** |
54 |
55 * Exports the whole PO file as a string |
55 /** |
56 * |
56 * Exports the whole PO file as a string |
57 * @param bool $include_headers whether to include the headers in the export |
57 * |
58 * @return string ready for inclusion in PO file string for headers and all the enrtries |
58 * @param bool $include_headers whether to include the headers in the export |
59 */ |
59 * @return string ready for inclusion in PO file string for headers and all the enrtries |
60 function export($include_headers = true) { |
60 */ |
61 $res = ''; |
61 function export( $include_headers = true ) { |
62 if ($include_headers) { |
62 $res = ''; |
63 $res .= $this->export_headers(); |
63 if ( $include_headers ) { |
64 $res .= "\n\n"; |
64 $res .= $this->export_headers(); |
65 } |
65 $res .= "\n\n"; |
66 $res .= $this->export_entries(); |
66 } |
67 return $res; |
67 $res .= $this->export_entries(); |
68 } |
68 return $res; |
69 |
69 } |
70 /** |
70 |
71 * Same as {@link export}, but writes the result to a file |
71 /** |
72 * |
72 * Same as {@link export}, but writes the result to a file |
73 * @param string $filename where to write the PO string |
73 * |
74 * @param bool $include_headers whether to include tje headers in the export |
74 * @param string $filename where to write the PO string |
75 * @return bool true on success, false on error |
75 * @param bool $include_headers whether to include tje headers in the export |
76 */ |
76 * @return bool true on success, false on error |
77 function export_to_file($filename, $include_headers = true) { |
77 */ |
78 $fh = fopen($filename, 'w'); |
78 function export_to_file( $filename, $include_headers = true ) { |
79 if (false === $fh) return false; |
79 $fh = fopen( $filename, 'w' ); |
80 $export = $this->export($include_headers); |
80 if ( false === $fh ) { |
81 $res = fwrite($fh, $export); |
81 return false; |
82 if (false === $res) return false; |
82 } |
83 return fclose($fh); |
83 $export = $this->export( $include_headers ); |
84 } |
84 $res = fwrite( $fh, $export ); |
85 |
85 if ( false === $res ) { |
86 /** |
86 return false; |
87 * Text to include as a comment before the start of the PO contents |
87 } |
88 * |
88 return fclose( $fh ); |
89 * Doesn't need to include # in the beginning of lines, these are added automatically |
89 } |
90 */ |
90 |
91 function set_comment_before_headers( $text ) { |
91 /** |
92 $this->comments_before_headers = $text; |
92 * Text to include as a comment before the start of the PO contents |
93 } |
93 * |
94 |
94 * Doesn't need to include # in the beginning of lines, these are added automatically |
95 /** |
95 */ |
96 * Formats a string in PO-style |
96 function set_comment_before_headers( $text ) { |
97 * |
97 $this->comments_before_headers = $text; |
98 * @static |
98 } |
99 * @param string $string the string to format |
99 |
100 * @return string the poified string |
100 /** |
101 */ |
101 * Formats a string in PO-style |
102 public static function poify($string) { |
102 * |
103 $quote = '"'; |
103 * @param string $string the string to format |
104 $slash = '\\'; |
104 * @return string the poified string |
105 $newline = "\n"; |
105 */ |
106 |
106 public static function poify( $string ) { |
107 $replaces = array( |
107 $quote = '"'; |
108 "$slash" => "$slash$slash", |
108 $slash = '\\'; |
109 "$quote" => "$slash$quote", |
109 $newline = "\n"; |
110 "\t" => '\t', |
110 |
111 ); |
111 $replaces = array( |
112 |
112 "$slash" => "$slash$slash", |
113 $string = str_replace(array_keys($replaces), array_values($replaces), $string); |
113 "$quote" => "$slash$quote", |
114 |
114 "\t" => '\t', |
115 $po = $quote.implode("${slash}n$quote$newline$quote", explode($newline, $string)).$quote; |
115 ); |
116 // add empty string on first line for readbility |
116 |
117 if (false !== strpos($string, $newline) && |
117 $string = str_replace( array_keys( $replaces ), array_values( $replaces ), $string ); |
118 (substr_count($string, $newline) > 1 || !($newline === substr($string, -strlen($newline))))) { |
118 |
119 $po = "$quote$quote$newline$po"; |
119 $po = $quote . implode( "${slash}n$quote$newline$quote", explode( $newline, $string ) ) . $quote; |
120 } |
120 // add empty string on first line for readbility |
121 // remove empty strings |
121 if ( false !== strpos( $string, $newline ) && |
122 $po = str_replace("$newline$quote$quote", '', $po); |
122 ( substr_count( $string, $newline ) > 1 || ! ( $newline === substr( $string, -strlen( $newline ) ) ) ) ) { |
123 return $po; |
123 $po = "$quote$quote$newline$po"; |
124 } |
124 } |
125 |
125 // remove empty strings |
126 /** |
126 $po = str_replace( "$newline$quote$quote", '', $po ); |
127 * Gives back the original string from a PO-formatted string |
127 return $po; |
128 * |
128 } |
129 * @static |
129 |
130 * @param string $string PO-formatted string |
130 /** |
131 * @return string enascaped string |
131 * Gives back the original string from a PO-formatted string |
132 */ |
132 * |
133 public static function unpoify($string) { |
133 * @param string $string PO-formatted string |
134 $escapes = array('t' => "\t", 'n' => "\n", 'r' => "\r", '\\' => '\\'); |
134 * @return string enascaped string |
135 $lines = array_map('trim', explode("\n", $string)); |
135 */ |
136 $lines = array_map(array('PO', 'trim_quotes'), $lines); |
136 public static function unpoify( $string ) { |
137 $unpoified = ''; |
137 $escapes = array( |
138 $previous_is_backslash = false; |
138 't' => "\t", |
139 foreach($lines as $line) { |
139 'n' => "\n", |
140 preg_match_all('/./u', $line, $chars); |
140 'r' => "\r", |
141 $chars = $chars[0]; |
141 '\\' => '\\', |
142 foreach($chars as $char) { |
142 ); |
143 if (!$previous_is_backslash) { |
143 $lines = array_map( 'trim', explode( "\n", $string ) ); |
144 if ('\\' == $char) |
144 $lines = array_map( array( 'PO', 'trim_quotes' ), $lines ); |
145 $previous_is_backslash = true; |
145 $unpoified = ''; |
146 else |
146 $previous_is_backslash = false; |
147 $unpoified .= $char; |
147 foreach ( $lines as $line ) { |
|
148 preg_match_all( '/./u', $line, $chars ); |
|
149 $chars = $chars[0]; |
|
150 foreach ( $chars as $char ) { |
|
151 if ( ! $previous_is_backslash ) { |
|
152 if ( '\\' == $char ) { |
|
153 $previous_is_backslash = true; |
|
154 } else { |
|
155 $unpoified .= $char; |
|
156 } |
|
157 } else { |
|
158 $previous_is_backslash = false; |
|
159 $unpoified .= isset( $escapes[ $char ] ) ? $escapes[ $char ] : $char; |
|
160 } |
|
161 } |
|
162 } |
|
163 |
|
164 // Standardise the line endings on imported content, technically PO files shouldn't contain \r |
|
165 $unpoified = str_replace( array( "\r\n", "\r" ), "\n", $unpoified ); |
|
166 |
|
167 return $unpoified; |
|
168 } |
|
169 |
|
170 /** |
|
171 * Inserts $with in the beginning of every new line of $string and |
|
172 * returns the modified string |
|
173 * |
|
174 * @param string $string prepend lines in this string |
|
175 * @param string $with prepend lines with this string |
|
176 */ |
|
177 public static function prepend_each_line( $string, $with ) { |
|
178 $lines = explode( "\n", $string ); |
|
179 $append = ''; |
|
180 if ( "\n" === substr( $string, -1 ) && '' === end( $lines ) ) { |
|
181 // Last line might be empty because $string was terminated |
|
182 // with a newline, remove it from the $lines array, |
|
183 // we'll restore state by re-terminating the string at the end |
|
184 array_pop( $lines ); |
|
185 $append = "\n"; |
|
186 } |
|
187 foreach ( $lines as &$line ) { |
|
188 $line = $with . $line; |
|
189 } |
|
190 unset( $line ); |
|
191 return implode( "\n", $lines ) . $append; |
|
192 } |
|
193 |
|
194 /** |
|
195 * Prepare a text as a comment -- wraps the lines and prepends # |
|
196 * and a special character to each line |
|
197 * |
|
198 * @access private |
|
199 * @param string $text the comment text |
|
200 * @param string $char character to denote a special PO comment, |
|
201 * like :, default is a space |
|
202 */ |
|
203 public static function comment_block( $text, $char = ' ' ) { |
|
204 $text = wordwrap( $text, PO_MAX_LINE_LEN - 3 ); |
|
205 return PO::prepend_each_line( $text, "#$char " ); |
|
206 } |
|
207 |
|
208 /** |
|
209 * Builds a string from the entry for inclusion in PO file |
|
210 * |
|
211 * @param Translation_Entry $entry the entry to convert to po string (passed by reference). |
|
212 * @return false|string PO-style formatted string for the entry or |
|
213 * false if the entry is empty |
|
214 */ |
|
215 public static function export_entry( &$entry ) { |
|
216 if ( null === $entry->singular || '' === $entry->singular ) { |
|
217 return false; |
|
218 } |
|
219 $po = array(); |
|
220 if ( ! empty( $entry->translator_comments ) ) { |
|
221 $po[] = PO::comment_block( $entry->translator_comments ); |
|
222 } |
|
223 if ( ! empty( $entry->extracted_comments ) ) { |
|
224 $po[] = PO::comment_block( $entry->extracted_comments, '.' ); |
|
225 } |
|
226 if ( ! empty( $entry->references ) ) { |
|
227 $po[] = PO::comment_block( implode( ' ', $entry->references ), ':' ); |
|
228 } |
|
229 if ( ! empty( $entry->flags ) ) { |
|
230 $po[] = PO::comment_block( implode( ', ', $entry->flags ), ',' ); |
|
231 } |
|
232 if ( $entry->context ) { |
|
233 $po[] = 'msgctxt ' . PO::poify( $entry->context ); |
|
234 } |
|
235 $po[] = 'msgid ' . PO::poify( $entry->singular ); |
|
236 if ( ! $entry->is_plural ) { |
|
237 $translation = empty( $entry->translations ) ? '' : $entry->translations[0]; |
|
238 $translation = PO::match_begin_and_end_newlines( $translation, $entry->singular ); |
|
239 $po[] = 'msgstr ' . PO::poify( $translation ); |
|
240 } else { |
|
241 $po[] = 'msgid_plural ' . PO::poify( $entry->plural ); |
|
242 $translations = empty( $entry->translations ) ? array( '', '' ) : $entry->translations; |
|
243 foreach ( $translations as $i => $translation ) { |
|
244 $translation = PO::match_begin_and_end_newlines( $translation, $entry->plural ); |
|
245 $po[] = "msgstr[$i] " . PO::poify( $translation ); |
|
246 } |
|
247 } |
|
248 return implode( "\n", $po ); |
|
249 } |
|
250 |
|
251 public static function match_begin_and_end_newlines( $translation, $original ) { |
|
252 if ( '' === $translation ) { |
|
253 return $translation; |
|
254 } |
|
255 |
|
256 $original_begin = "\n" === substr( $original, 0, 1 ); |
|
257 $original_end = "\n" === substr( $original, -1 ); |
|
258 $translation_begin = "\n" === substr( $translation, 0, 1 ); |
|
259 $translation_end = "\n" === substr( $translation, -1 ); |
|
260 |
|
261 if ( $original_begin ) { |
|
262 if ( ! $translation_begin ) { |
|
263 $translation = "\n" . $translation; |
|
264 } |
|
265 } elseif ( $translation_begin ) { |
|
266 $translation = ltrim( $translation, "\n" ); |
|
267 } |
|
268 |
|
269 if ( $original_end ) { |
|
270 if ( ! $translation_end ) { |
|
271 $translation .= "\n"; |
|
272 } |
|
273 } elseif ( $translation_end ) { |
|
274 $translation = rtrim( $translation, "\n" ); |
|
275 } |
|
276 |
|
277 return $translation; |
|
278 } |
|
279 |
|
280 /** |
|
281 * @param string $filename |
|
282 * @return boolean |
|
283 */ |
|
284 function import_from_file( $filename ) { |
|
285 $f = fopen( $filename, 'r' ); |
|
286 if ( ! $f ) { |
|
287 return false; |
|
288 } |
|
289 $lineno = 0; |
|
290 while ( true ) { |
|
291 $res = $this->read_entry( $f, $lineno ); |
|
292 if ( ! $res ) { |
|
293 break; |
|
294 } |
|
295 if ( $res['entry']->singular == '' ) { |
|
296 $this->set_headers( $this->make_headers( $res['entry']->translations[0] ) ); |
148 } else { |
297 } else { |
149 $previous_is_backslash = false; |
298 $this->add_entry( $res['entry'] ); |
150 $unpoified .= isset($escapes[$char])? $escapes[$char] : $char; |
299 } |
151 } |
300 } |
152 } |
301 PO::read_line( $f, 'clear' ); |
153 } |
302 if ( false === $res ) { |
154 |
303 return false; |
155 // Standardise the line endings on imported content, technically PO files shouldn't contain \r |
304 } |
156 $unpoified = str_replace( array( "\r\n", "\r" ), "\n", $unpoified ); |
305 if ( ! $this->headers && ! $this->entries ) { |
157 |
306 return false; |
158 return $unpoified; |
307 } |
159 } |
308 return true; |
160 |
309 } |
161 /** |
310 |
162 * Inserts $with in the beginning of every new line of $string and |
311 /** |
163 * returns the modified string |
312 * Helper function for read_entry |
164 * |
313 * |
165 * @static |
314 * @param string $context |
166 * @param string $string prepend lines in this string |
315 * @return bool |
167 * @param string $with prepend lines with this string |
316 */ |
168 */ |
317 protected static function is_final( $context ) { |
169 public static function prepend_each_line($string, $with) { |
318 return ( $context === 'msgstr' ) || ( $context === 'msgstr_plural' ); |
170 $lines = explode("\n", $string); |
319 } |
171 $append = ''; |
320 |
172 if ("\n" === substr($string, -1) && '' === end($lines)) { |
321 /** |
173 // Last line might be empty because $string was terminated |
322 * @param resource $f |
174 // with a newline, remove it from the $lines array, |
323 * @param int $lineno |
175 // we'll restore state by re-terminating the string at the end |
324 * @return null|false|array |
176 array_pop($lines); |
325 */ |
177 $append = "\n"; |
326 function read_entry( $f, $lineno = 0 ) { |
178 } |
327 $entry = new Translation_Entry(); |
179 foreach ($lines as &$line) { |
328 // where were we in the last step |
180 $line = $with . $line; |
329 // can be: comment, msgctxt, msgid, msgid_plural, msgstr, msgstr_plural |
181 } |
330 $context = ''; |
182 unset($line); |
331 $msgstr_index = 0; |
183 return implode("\n", $lines) . $append; |
332 while ( true ) { |
184 } |
333 $lineno++; |
185 |
334 $line = PO::read_line( $f ); |
186 /** |
335 if ( ! $line ) { |
187 * Prepare a text as a comment -- wraps the lines and prepends # |
336 if ( feof( $f ) ) { |
188 * and a special character to each line |
337 if ( self::is_final( $context ) ) { |
189 * |
338 break; |
190 * @access private |
339 } elseif ( ! $context ) { // we haven't read a line and eof came |
191 * @param string $text the comment text |
340 return null; |
192 * @param string $char character to denote a special PO comment, |
341 } else { |
193 * like :, default is a space |
342 return false; |
194 */ |
343 } |
195 public static function comment_block($text, $char=' ') { |
344 } else { |
196 $text = wordwrap($text, PO_MAX_LINE_LEN - 3); |
345 return false; |
197 return PO::prepend_each_line($text, "#$char "); |
346 } |
198 } |
347 } |
199 |
348 if ( $line == "\n" ) { |
200 /** |
349 continue; |
201 * Builds a string from the entry for inclusion in PO file |
350 } |
202 * |
351 $line = trim( $line ); |
203 * @static |
352 if ( preg_match( '/^#/', $line, $m ) ) { |
204 * @param Translation_Entry $entry the entry to convert to po string (passed by reference). |
353 // the comment is the start of a new entry |
205 * @return false|string PO-style formatted string for the entry or |
354 if ( self::is_final( $context ) ) { |
206 * false if the entry is empty |
355 PO::read_line( $f, 'put-back' ); |
207 */ |
356 $lineno--; |
208 public static function export_entry(&$entry) { |
|
209 if ( null === $entry->singular || '' === $entry->singular ) return false; |
|
210 $po = array(); |
|
211 if (!empty($entry->translator_comments)) $po[] = PO::comment_block($entry->translator_comments); |
|
212 if (!empty($entry->extracted_comments)) $po[] = PO::comment_block($entry->extracted_comments, '.'); |
|
213 if (!empty($entry->references)) $po[] = PO::comment_block(implode(' ', $entry->references), ':'); |
|
214 if (!empty($entry->flags)) $po[] = PO::comment_block(implode(", ", $entry->flags), ','); |
|
215 if ($entry->context) $po[] = 'msgctxt '.PO::poify($entry->context); |
|
216 $po[] = 'msgid '.PO::poify($entry->singular); |
|
217 if (!$entry->is_plural) { |
|
218 $translation = empty($entry->translations)? '' : $entry->translations[0]; |
|
219 $translation = PO::match_begin_and_end_newlines( $translation, $entry->singular ); |
|
220 $po[] = 'msgstr '.PO::poify($translation); |
|
221 } else { |
|
222 $po[] = 'msgid_plural '.PO::poify($entry->plural); |
|
223 $translations = empty($entry->translations)? array('', '') : $entry->translations; |
|
224 foreach($translations as $i => $translation) { |
|
225 $translation = PO::match_begin_and_end_newlines( $translation, $entry->plural ); |
|
226 $po[] = "msgstr[$i] ".PO::poify($translation); |
|
227 } |
|
228 } |
|
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; |
|
259 } |
|
260 |
|
261 /** |
|
262 * @param string $filename |
|
263 * @return boolean |
|
264 */ |
|
265 function import_from_file($filename) { |
|
266 $f = fopen($filename, 'r'); |
|
267 if (!$f) return false; |
|
268 $lineno = 0; |
|
269 while (true) { |
|
270 $res = $this->read_entry($f, $lineno); |
|
271 if (!$res) break; |
|
272 if ($res['entry']->singular == '') { |
|
273 $this->set_headers($this->make_headers($res['entry']->translations[0])); |
|
274 } else { |
|
275 $this->add_entry($res['entry']); |
|
276 } |
|
277 } |
|
278 PO::read_line($f, 'clear'); |
|
279 if ( false === $res ) { |
|
280 return false; |
|
281 } |
|
282 if ( ! $this->headers && ! $this->entries ) { |
|
283 return false; |
|
284 } |
|
285 return true; |
|
286 } |
|
287 |
|
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 /** |
|
298 * @param resource $f |
|
299 * @param int $lineno |
|
300 * @return null|false|array |
|
301 */ |
|
302 function read_entry($f, $lineno = 0) { |
|
303 $entry = new Translation_Entry(); |
|
304 // where were we in the last step |
|
305 // can be: comment, msgctxt, msgid, msgid_plural, msgstr, msgstr_plural |
|
306 $context = ''; |
|
307 $msgstr_index = 0; |
|
308 while (true) { |
|
309 $lineno++; |
|
310 $line = PO::read_line($f); |
|
311 if (!$line) { |
|
312 if (feof($f)) { |
|
313 if (self::is_final($context)) |
|
314 break; |
357 break; |
315 elseif (!$context) // we haven't read a line and eof came |
358 } |
316 return null; |
359 // comments have to be at the beginning |
317 else |
360 if ( $context && $context != 'comment' ) { |
318 return false; |
361 return false; |
|
362 } |
|
363 // add comment |
|
364 $this->add_comment_to_entry( $entry, $line ); |
|
365 } elseif ( preg_match( '/^msgctxt\s+(".*")/', $line, $m ) ) { |
|
366 if ( self::is_final( $context ) ) { |
|
367 PO::read_line( $f, 'put-back' ); |
|
368 $lineno--; |
|
369 break; |
|
370 } |
|
371 if ( $context && $context != 'comment' ) { |
|
372 return false; |
|
373 } |
|
374 $context = 'msgctxt'; |
|
375 $entry->context .= PO::unpoify( $m[1] ); |
|
376 } elseif ( preg_match( '/^msgid\s+(".*")/', $line, $m ) ) { |
|
377 if ( self::is_final( $context ) ) { |
|
378 PO::read_line( $f, 'put-back' ); |
|
379 $lineno--; |
|
380 break; |
|
381 } |
|
382 if ( $context && $context != 'msgctxt' && $context != 'comment' ) { |
|
383 return false; |
|
384 } |
|
385 $context = 'msgid'; |
|
386 $entry->singular .= PO::unpoify( $m[1] ); |
|
387 } elseif ( preg_match( '/^msgid_plural\s+(".*")/', $line, $m ) ) { |
|
388 if ( $context != 'msgid' ) { |
|
389 return false; |
|
390 } |
|
391 $context = 'msgid_plural'; |
|
392 $entry->is_plural = true; |
|
393 $entry->plural .= PO::unpoify( $m[1] ); |
|
394 } elseif ( preg_match( '/^msgstr\s+(".*")/', $line, $m ) ) { |
|
395 if ( $context != 'msgid' ) { |
|
396 return false; |
|
397 } |
|
398 $context = 'msgstr'; |
|
399 $entry->translations = array( PO::unpoify( $m[1] ) ); |
|
400 } elseif ( preg_match( '/^msgstr\[(\d+)\]\s+(".*")/', $line, $m ) ) { |
|
401 if ( $context != 'msgid_plural' && $context != 'msgstr_plural' ) { |
|
402 return false; |
|
403 } |
|
404 $context = 'msgstr_plural'; |
|
405 $msgstr_index = $m[1]; |
|
406 $entry->translations[ $m[1] ] = PO::unpoify( $m[2] ); |
|
407 } elseif ( preg_match( '/^".*"$/', $line ) ) { |
|
408 $unpoified = PO::unpoify( $line ); |
|
409 switch ( $context ) { |
|
410 case 'msgid': |
|
411 $entry->singular .= $unpoified; |
|
412 break; |
|
413 case 'msgctxt': |
|
414 $entry->context .= $unpoified; |
|
415 break; |
|
416 case 'msgid_plural': |
|
417 $entry->plural .= $unpoified; |
|
418 break; |
|
419 case 'msgstr': |
|
420 $entry->translations[0] .= $unpoified; |
|
421 break; |
|
422 case 'msgstr_plural': |
|
423 $entry->translations[ $msgstr_index ] .= $unpoified; |
|
424 break; |
|
425 default: |
|
426 return false; |
|
427 } |
319 } else { |
428 } else { |
320 return false; |
429 return false; |
321 } |
430 } |
322 } |
431 } |
323 if ($line == "\n") continue; |
432 |
324 $line = trim($line); |
433 $have_translations = false; |
325 if (preg_match('/^#/', $line, $m)) { |
434 foreach ( $entry->translations as $t ) { |
326 // the comment is the start of a new entry |
435 if ( $t || ( '0' === $t ) ) { |
327 if (self::is_final($context)) { |
436 $have_translations = true; |
328 PO::read_line($f, 'put-back'); |
|
329 $lineno--; |
|
330 break; |
437 break; |
331 } |
438 } |
332 // comments have to be at the beginning |
439 } |
333 if ($context && $context != 'comment') { |
440 if ( false === $have_translations ) { |
334 return false; |
441 $entry->translations = array(); |
335 } |
442 } |
336 // add comment |
443 |
337 $this->add_comment_to_entry($entry, $line); |
444 return array( |
338 } elseif (preg_match('/^msgctxt\s+(".*")/', $line, $m)) { |
445 'entry' => $entry, |
339 if (self::is_final($context)) { |
446 'lineno' => $lineno, |
340 PO::read_line($f, 'put-back'); |
447 ); |
341 $lineno--; |
448 } |
342 break; |
449 |
343 } |
450 /** |
344 if ($context && $context != 'comment') { |
451 * @staticvar string $last_line |
345 return false; |
452 * @staticvar boolean $use_last_line |
346 } |
453 * |
347 $context = 'msgctxt'; |
454 * @param resource $f |
348 $entry->context .= PO::unpoify($m[1]); |
455 * @param string $action |
349 } elseif (preg_match('/^msgid\s+(".*")/', $line, $m)) { |
456 * @return boolean |
350 if (self::is_final($context)) { |
457 */ |
351 PO::read_line($f, 'put-back'); |
458 function read_line( $f, $action = 'read' ) { |
352 $lineno--; |
459 static $last_line = ''; |
353 break; |
460 static $use_last_line = false; |
354 } |
461 if ( 'clear' == $action ) { |
355 if ($context && $context != 'msgctxt' && $context != 'comment') { |
462 $last_line = ''; |
356 return false; |
463 return true; |
357 } |
464 } |
358 $context = 'msgid'; |
465 if ( 'put-back' == $action ) { |
359 $entry->singular .= PO::unpoify($m[1]); |
466 $use_last_line = true; |
360 } elseif (preg_match('/^msgid_plural\s+(".*")/', $line, $m)) { |
467 return true; |
361 if ($context != 'msgid') { |
468 } |
362 return false; |
469 $line = $use_last_line ? $last_line : fgets( $f ); |
363 } |
470 $line = ( "\r\n" == substr( $line, -2 ) ) ? rtrim( $line, "\r\n" ) . "\n" : $line; |
364 $context = 'msgid_plural'; |
471 $last_line = $line; |
365 $entry->is_plural = true; |
472 $use_last_line = false; |
366 $entry->plural .= PO::unpoify($m[1]); |
473 return $line; |
367 } elseif (preg_match('/^msgstr\s+(".*")/', $line, $m)) { |
474 } |
368 if ($context != 'msgid') { |
475 |
369 return false; |
476 /** |
370 } |
477 * @param Translation_Entry $entry |
371 $context = 'msgstr'; |
478 * @param string $po_comment_line |
372 $entry->translations = array(PO::unpoify($m[1])); |
479 */ |
373 } elseif (preg_match('/^msgstr\[(\d+)\]\s+(".*")/', $line, $m)) { |
480 function add_comment_to_entry( &$entry, $po_comment_line ) { |
374 if ($context != 'msgid_plural' && $context != 'msgstr_plural') { |
481 $first_two = substr( $po_comment_line, 0, 2 ); |
375 return false; |
482 $comment = trim( substr( $po_comment_line, 2 ) ); |
376 } |
483 if ( '#:' == $first_two ) { |
377 $context = 'msgstr_plural'; |
484 $entry->references = array_merge( $entry->references, preg_split( '/\s+/', $comment ) ); |
378 $msgstr_index = $m[1]; |
485 } elseif ( '#.' == $first_two ) { |
379 $entry->translations[$m[1]] = PO::unpoify($m[2]); |
486 $entry->extracted_comments = trim( $entry->extracted_comments . "\n" . $comment ); |
380 } elseif (preg_match('/^".*"$/', $line)) { |
487 } elseif ( '#,' == $first_two ) { |
381 $unpoified = PO::unpoify($line); |
488 $entry->flags = array_merge( $entry->flags, preg_split( '/,\s*/', $comment ) ); |
382 switch ($context) { |
|
383 case 'msgid': |
|
384 $entry->singular .= $unpoified; break; |
|
385 case 'msgctxt': |
|
386 $entry->context .= $unpoified; break; |
|
387 case 'msgid_plural': |
|
388 $entry->plural .= $unpoified; break; |
|
389 case 'msgstr': |
|
390 $entry->translations[0] .= $unpoified; break; |
|
391 case 'msgstr_plural': |
|
392 $entry->translations[$msgstr_index] .= $unpoified; break; |
|
393 default: |
|
394 return false; |
|
395 } |
|
396 } else { |
489 } else { |
397 return false; |
490 $entry->translator_comments = trim( $entry->translator_comments . "\n" . $comment ); |
398 } |
491 } |
399 } |
492 } |
400 |
493 |
401 $have_translations = false; |
494 /** |
402 foreach ( $entry->translations as $t ) { |
495 * @param string $s |
403 if ( $t || ('0' === $t) ) { |
496 * @return sring |
404 $have_translations = true; |
497 */ |
405 break; |
498 public static function trim_quotes( $s ) { |
406 } |
499 if ( substr( $s, 0, 1 ) == '"' ) { |
407 } |
500 $s = substr( $s, 1 ); |
408 if ( false === $have_translations ) { |
501 } |
409 $entry->translations = array(); |
502 if ( substr( $s, -1, 1 ) == '"' ) { |
410 } |
503 $s = substr( $s, 0, -1 ); |
411 |
504 } |
412 return array('entry' => $entry, 'lineno' => $lineno); |
505 return $s; |
|
506 } |
413 } |
507 } |
414 |
|
415 /** |
|
416 * @staticvar string $last_line |
|
417 * @staticvar boolean $use_last_line |
|
418 * |
|
419 * @param resource $f |
|
420 * @param string $action |
|
421 * @return boolean |
|
422 */ |
|
423 function read_line($f, $action = 'read') { |
|
424 static $last_line = ''; |
|
425 static $use_last_line = false; |
|
426 if ('clear' == $action) { |
|
427 $last_line = ''; |
|
428 return true; |
|
429 } |
|
430 if ('put-back' == $action) { |
|
431 $use_last_line = true; |
|
432 return true; |
|
433 } |
|
434 $line = $use_last_line? $last_line : fgets($f); |
|
435 $line = ( "\r\n" == substr( $line, -2 ) ) ? rtrim( $line, "\r\n" ) . "\n" : $line; |
|
436 $last_line = $line; |
|
437 $use_last_line = false; |
|
438 return $line; |
|
439 } |
|
440 |
|
441 /** |
|
442 * @param Translation_Entry $entry |
|
443 * @param string $po_comment_line |
|
444 */ |
|
445 function add_comment_to_entry(&$entry, $po_comment_line) { |
|
446 $first_two = substr($po_comment_line, 0, 2); |
|
447 $comment = trim(substr($po_comment_line, 2)); |
|
448 if ('#:' == $first_two) { |
|
449 $entry->references = array_merge($entry->references, preg_split('/\s+/', $comment)); |
|
450 } elseif ('#.' == $first_two) { |
|
451 $entry->extracted_comments = trim($entry->extracted_comments . "\n" . $comment); |
|
452 } elseif ('#,' == $first_two) { |
|
453 $entry->flags = array_merge($entry->flags, preg_split('/,\s*/', $comment)); |
|
454 } else { |
|
455 $entry->translator_comments = trim($entry->translator_comments . "\n" . $comment); |
|
456 } |
|
457 } |
|
458 |
|
459 /** |
|
460 * @param string $s |
|
461 * @return sring |
|
462 */ |
|
463 public static function trim_quotes($s) { |
|
464 if ( substr($s, 0, 1) == '"') $s = substr($s, 1); |
|
465 if ( substr($s, -1, 1) == '"') $s = substr($s, 0, -1); |
|
466 return $s; |
|
467 } |
|
468 } |
|
469 endif; |
508 endif; |