| [ Index ] |
PHP Cross Reference of Wordpress 2.7.1 |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * PHP-Gettext External Library: gettext_reader class 4 * 5 * @package External 6 * @subpackage PHP-gettext 7 * 8 * @internal 9 Copyright (c) 2003 Danilo Segan <danilo@kvota.net>. 10 Copyright (c) 2005 Nico Kaiser <nico@siriux.net> 11 12 This file is part of PHP-gettext. 13 14 PHP-gettext is free software; you can redistribute it and/or modify 15 it under the terms of the GNU General Public License as published by 16 the Free Software Foundation; either version 2 of the License, or 17 (at your option) any later version. 18 19 PHP-gettext is distributed in the hope that it will be useful, 20 but WITHOUT ANY WARRANTY; without even the implied warranty of 21 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 GNU General Public License for more details. 23 24 You should have received a copy of the GNU General Public License 25 along with PHP-gettext; if not, write to the Free Software 26 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 27 28 */ 29 30 /** 31 * Provides a simple gettext replacement that works independently from 32 * the system's gettext abilities. 33 * It can read MO files and use them for translating strings. 34 * The files are passed to gettext_reader as a Stream (see streams.php) 35 * 36 * This version has the ability to cache all strings and translations to 37 * speed up the string lookup. 38 * While the cache is enabled by default, it can be switched off with the 39 * second parameter in the constructor (e.g. whenusing very large MO files 40 * that you don't want to keep in memory) 41 */ 42 class gettext_reader { 43 //public: 44 var $error = 0; // public variable that holds error code (0 if no error) 45 46 //private: 47 var $BYTEORDER = 0; // 0: low endian, 1: big endian 48 var $STREAM = NULL; 49 var $short_circuit = false; 50 var $enable_cache = false; 51 var $originals = NULL; // offset of original table 52 var $translations = NULL; // offset of translation table 53 var $pluralheader = NULL; // cache header field for plural forms 54 var $select_string_function = NULL; // cache function, which chooses plural forms 55 var $total = 0; // total string count 56 var $table_originals = NULL; // table for original strings (offsets) 57 var $table_translations = NULL; // table for translated strings (offsets) 58 var $cache_translations = NULL; // original -> translation mapping 59 60 61 /* Methods */ 62 63 64 /** 65 * Reads a 32bit Integer from the Stream 66 * 67 * @access private 68 * @return Integer from the Stream 69 */ 70 function readint() { 71 if ($this->BYTEORDER == 0) { 72 // low endian 73 $low_end = unpack('V', $this->STREAM->read(4)); 74 return array_shift($low_end); 75 } else { 76 // big endian 77 $big_end = unpack('N', $this->STREAM->read(4)); 78 return array_shift($big_end); 79 } 80 } 81 82 /** 83 * Reads an array of Integers from the Stream 84 * 85 * @param int count How many elements should be read 86 * @return Array of Integers 87 */ 88 function readintarray($count) { 89 if ($this->BYTEORDER == 0) { 90 // low endian 91 return unpack('V'.$count, $this->STREAM->read(4 * $count)); 92 } else { 93 // big endian 94 return unpack('N'.$count, $this->STREAM->read(4 * $count)); 95 } 96 } 97 98 /** 99 * Constructor 100 * 101 * @param object Reader the StreamReader object 102 * @param boolean enable_cache Enable or disable caching of strings (default on) 103 */ 104 function gettext_reader($Reader, $enable_cache = true) { 105 // If there isn't a StreamReader, turn on short circuit mode. 106 if (! $Reader || isset($Reader->error) ) { 107 $this->short_circuit = true; 108 return; 109 } 110 111 // Caching can be turned off 112 $this->enable_cache = $enable_cache; 113 114 // $MAGIC1 = (int)0x950412de; //bug in PHP 5.0.2, see https://savannah.nongnu.org/bugs/?func=detailitem&item_id=10565 115 $MAGIC1 = (int) - 1794895138; 116 // $MAGIC2 = (int)0xde120495; //bug 117 $MAGIC2 = (int) - 569244523; 118 // 64-bit fix 119 $MAGIC3 = (int) 2500072158; 120 121 $this->STREAM = $Reader; 122 $magic = $this->readint(); 123 if ($magic == $MAGIC1 || $magic == $MAGIC3) { // to make sure it works for 64-bit platforms 124 $this->BYTEORDER = 0; 125 } elseif ($magic == ($MAGIC2 & 0xFFFFFFFF)) { 126 $this->BYTEORDER = 1; 127 } else { 128 $this->error = 1; // not MO file 129 return false; 130 } 131 132 // FIXME: Do we care about revision? We should. 133 $revision = $this->readint(); 134 135 $this->total = $this->readint(); 136 $this->originals = $this->readint(); 137 $this->translations = $this->readint(); 138 } 139 140 /** 141 * Loads the translation tables from the MO file into the cache 142 * If caching is enabled, also loads all strings into a cache 143 * to speed up translation lookups 144 * 145 * @access private 146 */ 147 function load_tables() { 148 if (is_array($this->cache_translations) && 149 is_array($this->table_originals) && 150 is_array($this->table_translations)) 151 return; 152 153 /* get original and translations tables */ 154 $this->STREAM->seekto($this->originals); 155 $this->table_originals = $this->readintarray($this->total * 2); 156 $this->STREAM->seekto($this->translations); 157 $this->table_translations = $this->readintarray($this->total * 2); 158 159 if ($this->enable_cache) { 160 $this->cache_translations = array (); 161 /* read all strings in the cache */ 162 for ($i = 0; $i < $this->total; $i++) { 163 $this->STREAM->seekto($this->table_originals[$i * 2 + 2]); 164 $original = $this->STREAM->read($this->table_originals[$i * 2 + 1]); 165 $this->STREAM->seekto($this->table_translations[$i * 2 + 2]); 166 $translation = $this->STREAM->read($this->table_translations[$i * 2 + 1]); 167 $this->cache_translations[$original] = $translation; 168 } 169 } 170 } 171 172 /** 173 * Returns a string from the "originals" table 174 * 175 * @access private 176 * @param int num Offset number of original string 177 * @return string Requested string if found, otherwise '' 178 */ 179 function get_original_string($num) { 180 $length = $this->table_originals[$num * 2 + 1]; 181 $offset = $this->table_originals[$num * 2 + 2]; 182 if (! $length) 183 return ''; 184 $this->STREAM->seekto($offset); 185 $data = $this->STREAM->read($length); 186 return (string)$data; 187 } 188 189 /** 190 * Returns a string from the "translations" table 191 * 192 * @access private 193 * @param int num Offset number of original string 194 * @return string Requested string if found, otherwise '' 195 */ 196 function get_translation_string($num) { 197 $length = $this->table_translations[$num * 2 + 1]; 198 $offset = $this->table_translations[$num * 2 + 2]; 199 if (! $length) 200 return ''; 201 $this->STREAM->seekto($offset); 202 $data = $this->STREAM->read($length); 203 return (string)$data; 204 } 205 206 /** 207 * Binary search for string 208 * 209 * @access private 210 * @param string string 211 * @param int start (internally used in recursive function) 212 * @param int end (internally used in recursive function) 213 * @return int string number (offset in originals table) 214 */ 215 function find_string($string, $start = -1, $end = -1) { 216 if (($start == -1) or ($end == -1)) { 217 // find_string is called with only one parameter, set start end end 218 $start = 0; 219 $end = $this->total; 220 } 221 if (abs($start - $end) <= 1) { 222 // We're done, now we either found the string, or it doesn't exist 223 $txt = $this->get_original_string($start); 224 if ($string == $txt) 225 return $start; 226 else 227 return -1; 228 } else if ($start > $end) { 229 // start > end -> turn around and start over 230 return $this->find_string($string, $end, $start); 231 } else { 232 // Divide table in two parts 233 $half = (int)(($start + $end) / 2); 234 $cmp = strcmp($string, $this->get_original_string($half)); 235 if ($cmp == 0) 236 // string is exactly in the middle => return it 237 return $half; 238 else if ($cmp < 0) 239 // The string is in the upper half 240 return $this->find_string($string, $start, $half); 241 else 242 // The string is in the lower half 243 return $this->find_string($string, $half, $end); 244 } 245 } 246 247 /** 248 * Translates a string 249 * 250 * @access public 251 * @param string string to be translated 252 * @return string translated string (or original, if not found) 253 */ 254 function translate($string) { 255 if ($this->short_circuit) 256 return $string; 257 $this->load_tables(); 258 259 if ($this->enable_cache) { 260 // Caching enabled, get translated string from cache 261 if (array_key_exists($string, $this->cache_translations)) 262 return $this->cache_translations[$string]; 263 else 264 return $string; 265 } else { 266 // Caching not enabled, try to find string 267 $num = $this->find_string($string); 268 if ($num == -1) 269 return $string; 270 else 271 return $this->get_translation_string($num); 272 } 273 } 274 275 /** 276 * Get possible plural forms from MO header 277 * 278 * @access private 279 * @return string plural form header 280 */ 281 function get_plural_forms() { 282 // lets assume message number 0 is header 283 // this is true, right? 284 $this->load_tables(); 285 286 // cache header field for plural forms 287 if (! is_string($this->pluralheader)) { 288 if ($this->enable_cache) { 289 $header = $this->cache_translations[""]; 290 } else { 291 $header = $this->get_translation_string(0); 292 } 293 $header .= "\n"; //make sure our regex matches 294 if (eregi("plural-forms: ([^\n]*)\n", $header, $regs)) 295 $expr = $regs[1]; 296 else 297 $expr = "nplurals=2; plural=n == 1 ? 0 : 1;"; 298 299 // add parentheses 300 // important since PHP's ternary evaluates from left to right 301 $expr.= ';'; 302 $res= ''; 303 $p= 0; 304 for ($i= 0; $i < strlen($expr); $i++) { 305 $ch= $expr[$i]; 306 switch ($ch) { 307 case '?': 308 $res.= ' ? ('; 309 $p++; 310 break; 311 case ':': 312 $res.= ') : ('; 313 break; 314 case ';': 315 $res.= str_repeat( ')', $p) . ';'; 316 $p= 0; 317 break; 318 default: 319 $res.= $ch; 320 } 321 } 322 $this->pluralheader = $res; 323 } 324 325 return $this->pluralheader; 326 } 327 328 /** 329 * Detects which plural form to take 330 * 331 * @access private 332 * @param n count 333 * @return int array index of the right plural form 334 */ 335 function select_string($n) { 336 if (is_null($this->select_string_function)) { 337 $string = $this->get_plural_forms(); 338 if (preg_match("/nplurals\s*=\s*(\d+)\s*\;\s*plural\s*=\s*(.*?)\;+/", $string, $matches)) { 339 $nplurals = $matches[1]; 340 $expression = $matches[2]; 341 $expression = str_replace("n", '$n', $expression); 342 } else { 343 $nplurals = 2; 344 $expression = ' $n == 1 ? 0 : 1 '; 345 } 346 $func_body = " 347 \$plural = ($expression); 348 return (\$plural <= $nplurals)? \$plural : \$plural - 1;"; 349 $this->select_string_function = create_function('$n', $func_body); 350 } 351 return call_user_func($this->select_string_function, $n); 352 } 353 354 /** 355 * Plural version of gettext 356 * 357 * @access public 358 * @param string single 359 * @param string plural 360 * @param string number 361 * @return translated plural form 362 */ 363 function ngettext($single, $plural, $number) { 364 if ($this->short_circuit) { 365 if ($number != 1) 366 return $plural; 367 else 368 return $single; 369 } 370 371 // find out the appropriate form 372 $select = $this->select_string($number); 373 374 // this should contains all strings separated by NULLs 375 $key = $single.chr(0).$plural; 376 377 378 if ($this->enable_cache) { 379 if (! array_key_exists($key, $this->cache_translations)) { 380 return ($number != 1) ? $plural : $single; 381 } else { 382 $result = $this->cache_translations[$key]; 383 $list = explode(chr(0), $result); 384 return $list[$select]; 385 } 386 } else { 387 $num = $this->find_string($key); 388 if ($num == -1) { 389 return ($number != 1) ? $plural : $single; 390 } else { 391 $result = $this->get_translation_string($num); 392 $list = explode(chr(0), $result); 393 return $list[$select]; 394 } 395 } 396 } 397 398 } 399 400 ?>
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Mon Mar 23 16:23:02 2009 | Cross-referenced by PHPXref 0.7 |