PHP Cross Reference of WordPress Subversion HEAD

[ Index ]     [ Classes ]     [ Functions ]     [ Variables ]     [ Constants ]

title

Body

[close]

/wp-includes/ -> gettext.php (source)

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


Generated Thu Dec 6 06:47:08 2007 for RedAlt XRefs Cross-referenced by PHPXref 0.6 and RedAlt