[ Index ] |
WordPress Cross Reference |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Class for working with PO files 4 * 5 * @version $Id: po.php 718 2012-10-31 00:32:02Z nbachiyski $ 6 * @package pomo 7 * @subpackage po 8 */ 9 10 require_once dirname(__FILE__) . '/translations.php'; 11 12 define('PO_MAX_LINE_LEN', 79); 13 14 ini_set('auto_detect_line_endings', 1); 15 16 /** 17 * Routines for working with PO files 18 */ 19 if ( !class_exists( 'PO' ) ): 20 class PO extends Gettext_Translations { 21 22 var $comments_before_headers = ''; 23 24 /** 25 * Exports headers to a PO entry 26 * 27 * @return string msgid/msgstr PO entry for this PO file headers, doesn't contain newline at the end 28 */ 29 function export_headers() { 30 $header_string = ''; 31 foreach($this->headers as $header => $value) { 32 $header_string.= "$header: $value\n"; 33 } 34 $poified = PO::poify($header_string); 35 if ($this->comments_before_headers) 36 $before_headers = $this->prepend_each_line(rtrim($this->comments_before_headers)."\n", '# '); 37 else 38 $before_headers = ''; 39 return rtrim("{$before_headers}msgid \"\"\nmsgstr $poified"); 40 } 41 42 /** 43 * Exports all entries to PO format 44 * 45 * @return string sequence of mgsgid/msgstr PO strings, doesn't containt newline at the end 46 */ 47 function export_entries() { 48 //TODO sorting 49 return implode("\n\n", array_map(array('PO', 'export_entry'), $this->entries)); 50 } 51 52 /** 53 * Exports the whole PO file as a string 54 * 55 * @param bool $include_headers whether to include the headers in the export 56 * @return string ready for inclusion in PO file string for headers and all the enrtries 57 */ 58 function export($include_headers = true) { 59 $res = ''; 60 if ($include_headers) { 61 $res .= $this->export_headers(); 62 $res .= "\n\n"; 63 } 64 $res .= $this->export_entries(); 65 return $res; 66 } 67 68 /** 69 * Same as {@link export}, but writes the result to a file 70 * 71 * @param string $filename where to write the PO string 72 * @param bool $include_headers whether to include tje headers in the export 73 * @return bool true on success, false on error 74 */ 75 function export_to_file($filename, $include_headers = true) { 76 $fh = fopen($filename, 'w'); 77 if (false === $fh) return false; 78 $export = $this->export($include_headers); 79 $res = fwrite($fh, $export); 80 if (false === $res) return false; 81 return fclose($fh); 82 } 83 84 /** 85 * Text to include as a comment before the start of the PO contents 86 * 87 * Doesn't need to include # in the beginning of lines, these are added automatically 88 */ 89 function set_comment_before_headers( $text ) { 90 $this->comments_before_headers = $text; 91 } 92 93 /** 94 * Formats a string in PO-style 95 * 96 * @static 97 * @param string $string the string to format 98 * @return string the poified string 99 */ 100 function poify($string) { 101 $quote = '"'; 102 $slash = '\\'; 103 $newline = "\n"; 104 105 $replaces = array( 106 "$slash" => "$slash$slash", 107 "$quote" => "$slash$quote", 108 "\t" => '\t', 109 ); 110 111 $string = str_replace(array_keys($replaces), array_values($replaces), $string); 112 113 $po = $quote.implode("$slash}n$quote$newline$quote", explode($newline, $string)).$quote; 114 // add empty string on first line for readbility 115 if (false !== strpos($string, $newline) && 116 (substr_count($string, $newline) > 1 || !($newline === substr($string, -strlen($newline))))) { 117 $po = "$quote$quote$newline$po"; 118 } 119 // remove empty strings 120 $po = str_replace("$newline$quote$quote", '', $po); 121 return $po; 122 } 123 124 /** 125 * Gives back the original string from a PO-formatted string 126 * 127 * @static 128 * @param string $string PO-formatted string 129 * @return string enascaped string 130 */ 131 function unpoify($string) { 132 $escapes = array('t' => "\t", 'n' => "\n", '\\' => '\\'); 133 $lines = array_map('trim', explode("\n", $string)); 134 $lines = array_map(array('PO', 'trim_quotes'), $lines); 135 $unpoified = ''; 136 $previous_is_backslash = false; 137 foreach($lines as $line) { 138 preg_match_all('/./u', $line, $chars); 139 $chars = $chars[0]; 140 foreach($chars as $char) { 141 if (!$previous_is_backslash) { 142 if ('\\' == $char) 143 $previous_is_backslash = true; 144 else 145 $unpoified .= $char; 146 } else { 147 $previous_is_backslash = false; 148 $unpoified .= isset($escapes[$char])? $escapes[$char] : $char; 149 } 150 } 151 } 152 return $unpoified; 153 } 154 155 /** 156 * Inserts $with in the beginning of every new line of $string and 157 * returns the modified string 158 * 159 * @static 160 * @param string $string prepend lines in this string 161 * @param string $with prepend lines with this string 162 */ 163 function prepend_each_line($string, $with) { 164 $php_with = var_export($with, true); 165 $lines = explode("\n", $string); 166 // do not prepend the string on the last empty line, artefact by explode 167 if ("\n" == substr($string, -1)) unset($lines[count($lines) - 1]); 168 $res = implode("\n", array_map(create_function('$x', "return $php_with.\$x;"), $lines)); 169 // give back the empty line, we ignored above 170 if ("\n" == substr($string, -1)) $res .= "\n"; 171 return $res; 172 } 173 174 /** 175 * Prepare a text as a comment -- wraps the lines and prepends # 176 * and a special character to each line 177 * 178 * @access private 179 * @param string $text the comment text 180 * @param string $char character to denote a special PO comment, 181 * like :, default is a space 182 */ 183 function comment_block($text, $char=' ') { 184 $text = wordwrap($text, PO_MAX_LINE_LEN - 3); 185 return PO::prepend_each_line($text, "#$char "); 186 } 187 188 /** 189 * Builds a string from the entry for inclusion in PO file 190 * 191 * @static 192 * @param object &$entry the entry to convert to po string 193 * @return string|bool PO-style formatted string for the entry or 194 * false if the entry is empty 195 */ 196 function export_entry(&$entry) { 197 if (is_null($entry->singular)) return false; 198 $po = array(); 199 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, '.'); 201 if (!empty($entry->references)) $po[] = PO::comment_block(implode(' ', $entry->references), ':'); 202 if (!empty($entry->flags)) $po[] = PO::comment_block(implode(", ", $entry->flags), ','); 203 if (!is_null($entry->context)) $po[] = 'msgctxt '.PO::poify($entry->context); 204 $po[] = 'msgid '.PO::poify($entry->singular); 205 if (!$entry->is_plural) { 206 $translation = empty($entry->translations)? '' : $entry->translations[0]; 207 $po[] = 'msgstr '.PO::poify($translation); 208 } else { 209 $po[] = 'msgid_plural '.PO::poify($entry->plural); 210 $translations = empty($entry->translations)? array('', '') : $entry->translations; 211 foreach($translations as $i => $translation) { 212 $po[] = "msgstr[$i] ".PO::poify($translation); 213 } 214 } 215 return implode("\n", $po); 216 } 217 218 function import_from_file($filename) { 219 $f = fopen($filename, 'r'); 220 if (!$f) return false; 221 $lineno = 0; 222 while (true) { 223 $res = $this->read_entry($f, $lineno); 224 if (!$res) break; 225 if ($res['entry']->singular == '') { 226 $this->set_headers($this->make_headers($res['entry']->translations[0])); 227 } else { 228 $this->add_entry($res['entry']); 229 } 230 } 231 PO::read_line($f, 'clear'); 232 if ( false === $res ) { 233 return false; 234 } 235 if ( ! $this->headers && ! $this->entries ) { 236 return false; 237 } 238 return true; 239 } 240 241 function read_entry($f, $lineno = 0) { 242 $entry = new Translation_Entry(); 243 // where were we in the last step 244 // can be: comment, msgctxt, msgid, msgid_plural, msgstr, msgstr_plural 245 $context = ''; 246 $msgstr_index = 0; 247 $is_final = create_function('$context', 'return $context == "msgstr" || $context == "msgstr_plural";'); 248 while (true) { 249 $lineno++; 250 $line = PO::read_line($f); 251 if (!$line) { 252 if (feof($f)) { 253 if ($is_final($context)) 254 break; 255 elseif (!$context) // we haven't read a line and eof came 256 return null; 257 else 258 return false; 259 } else { 260 return false; 261 } 262 } 263 if ($line == "\n") continue; 264 $line = trim($line); 265 if (preg_match('/^#/', $line, $m)) { 266 // the comment is the start of a new entry 267 if ($is_final($context)) { 268 PO::read_line($f, 'put-back'); 269 $lineno--; 270 break; 271 } 272 // comments have to be at the beginning 273 if ($context && $context != 'comment') { 274 return false; 275 } 276 // add comment 277 $this->add_comment_to_entry($entry, $line); 278 } elseif (preg_match('/^msgctxt\s+(".*")/', $line, $m)) { 279 if ($is_final($context)) { 280 PO::read_line($f, 'put-back'); 281 $lineno--; 282 break; 283 } 284 if ($context && $context != 'comment') { 285 return false; 286 } 287 $context = 'msgctxt'; 288 $entry->context .= PO::unpoify($m[1]); 289 } elseif (preg_match('/^msgid\s+(".*")/', $line, $m)) { 290 if ($is_final($context)) { 291 PO::read_line($f, 'put-back'); 292 $lineno--; 293 break; 294 } 295 if ($context && $context != 'msgctxt' && $context != 'comment') { 296 return false; 297 } 298 $context = 'msgid'; 299 $entry->singular .= PO::unpoify($m[1]); 300 } elseif (preg_match('/^msgid_plural\s+(".*")/', $line, $m)) { 301 if ($context != 'msgid') { 302 return false; 303 } 304 $context = 'msgid_plural'; 305 $entry->is_plural = true; 306 $entry->plural .= PO::unpoify($m[1]); 307 } elseif (preg_match('/^msgstr\s+(".*")/', $line, $m)) { 308 if ($context != 'msgid') { 309 return false; 310 } 311 $context = 'msgstr'; 312 $entry->translations = array(PO::unpoify($m[1])); 313 } elseif (preg_match('/^msgstr\[(\d+)\]\s+(".*")/', $line, $m)) { 314 if ($context != 'msgid_plural' && $context != 'msgstr_plural') { 315 return false; 316 } 317 $context = 'msgstr_plural'; 318 $msgstr_index = $m[1]; 319 $entry->translations[$m[1]] = PO::unpoify($m[2]); 320 } elseif (preg_match('/^".*"$/', $line)) { 321 $unpoified = PO::unpoify($line); 322 switch ($context) { 323 case 'msgid': 324 $entry->singular .= $unpoified; break; 325 case 'msgctxt': 326 $entry->context .= $unpoified; break; 327 case 'msgid_plural': 328 $entry->plural .= $unpoified; break; 329 case 'msgstr': 330 $entry->translations[0] .= $unpoified; break; 331 case 'msgstr_plural': 332 $entry->translations[$msgstr_index] .= $unpoified; break; 333 default: 334 return false; 335 } 336 } else { 337 return false; 338 } 339 } 340 if (array() == array_filter($entry->translations, create_function('$t', 'return $t || "0" === $t;'))) { 341 $entry->translations = array(); 342 } 343 return array('entry' => $entry, 'lineno' => $lineno); 344 } 345 346 function read_line($f, $action = 'read') { 347 static $last_line = ''; 348 static $use_last_line = false; 349 if ('clear' == $action) { 350 $last_line = ''; 351 return true; 352 } 353 if ('put-back' == $action) { 354 $use_last_line = true; 355 return true; 356 } 357 $line = $use_last_line? $last_line : fgets($f); 358 $line = ( "\r\n" == substr( $line, -2 ) ) ? rtrim( $line, "\r\n" ) . "\n" : $line; 359 $last_line = $line; 360 $use_last_line = false; 361 return $line; 362 } 363 364 function add_comment_to_entry(&$entry, $po_comment_line) { 365 $first_two = substr($po_comment_line, 0, 2); 366 $comment = trim(substr($po_comment_line, 2)); 367 if ('#:' == $first_two) { 368 $entry->references = array_merge($entry->references, preg_split('/\s+/', $comment)); 369 } elseif ('#.' == $first_two) { 370 $entry->extracted_comments = trim($entry->extracted_comments . "\n" . $comment); 371 } elseif ('#,' == $first_two) { 372 $entry->flags = array_merge($entry->flags, preg_split('/,\s*/', $comment)); 373 } else { 374 $entry->translator_comments = trim($entry->translator_comments . "\n" . $comment); 375 } 376 } 377 378 function trim_quotes($s) { 379 if ( substr($s, 0, 1) == '"') $s = substr($s, 1); 380 if ( substr($s, -1, 1) == '"') $s = substr($s, 0, -1); 381 return $s; 382 } 383 } 384 endif;
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Tue Mar 25 01:41:18 2014 | WordPress honlapkészítés: online1.hu |