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