XOOPS RMCommon Utilities  2.1.8.91RC
 All Classes Namespaces Files Functions Variables
gettext.php
Go to the documentation of this file.
1 <?php
2 /*
3  Copyright (c) 2003, 2009 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 
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 $total = 0; // total string count
49  var $table_originals = NULL; // table for original strings (offsets)
50  var $table_translations = NULL; // table for translated strings (offsets)
51  var $cache_translations = NULL; // original -> translation mapping
52 
53 
54  /* Methods */
55 
56 
63  function readint() {
64  if ($this->BYTEORDER == 0) {
65  // low endian
66  $input=unpack('V', $this->STREAM->read(4));
67  return array_shift($input);
68  } else {
69  // big endian
70  $input=unpack('N', $this->STREAM->read(4));
71  return array_shift($input);
72  }
73  }
74 
75  function read($bytes) {
76  return $this->STREAM->read($bytes);
77  }
78 
85  function readintarray($count) {
86  if ($this->BYTEORDER == 0) {
87  // low endian
88  return unpack('V'.$count, $this->STREAM->read(4 * $count));
89  } else {
90  // big endian
91  return unpack('N'.$count, $this->STREAM->read(4 * $count));
92  }
93  }
94 
101  function gettext_reader($Reader, $enable_cache = true) {
102  // If there isn't a StreamReader, turn on short circuit mode.
103  if (! $Reader || isset($Reader->error) ) {
104  $this->short_circuit = true;
105  return;
106  }
107 
108  // Caching can be turned off
109  $this->enable_cache = $enable_cache;
110 
111  $MAGIC1 = "\x95\x04\x12\xde";
112  $MAGIC2 = "\xde\x12\x04\x95";
113 
114  $this->STREAM = $Reader;
115  $magic = $this->read(4);
116  if ($magic == $MAGIC1) {
117  $this->BYTEORDER = 1;
118  } elseif ($magic == $MAGIC2) {
119  $this->BYTEORDER = 0;
120  } else {
121  $this->error = 1; // not MO file
122  return false;
123  }
124 
125  // FIXME: Do we care about revision? We should.
126  $revision = $this->readint();
127 
128  $this->total = $this->readint();
129  $this->originals = $this->readint();
130  $this->translations = $this->readint();
131  }
132 
140  function load_tables() {
141  if (is_array($this->cache_translations) &&
142  is_array($this->table_originals) &&
143  is_array($this->table_translations))
144  return;
145 
146  /* get original and translations tables */
147  $this->STREAM->seekto($this->originals);
148  $this->table_originals = $this->readintarray($this->total * 2);
149  $this->STREAM->seekto($this->translations);
150  $this->table_translations = $this->readintarray($this->total * 2);
151 
152  if ($this->enable_cache) {
153  $this->cache_translations = array ();
154  /* read all strings in the cache */
155  for ($i = 0; $i < $this->total; $i++) {
156  $this->STREAM->seekto($this->table_originals[$i * 2 + 2]);
157  $original = $this->STREAM->read($this->table_originals[$i * 2 + 1]);
158  $this->STREAM->seekto($this->table_translations[$i * 2 + 2]);
159  $translation = $this->STREAM->read($this->table_translations[$i * 2 + 1]);
160  $this->cache_translations[$original] = $translation;
161  }
162  }
163  }
164 
172  function get_original_string($num) {
173  $length = $this->table_originals[$num * 2 + 1];
174  $offset = $this->table_originals[$num * 2 + 2];
175  if (! $length)
176  return '';
177  $this->STREAM->seekto($offset);
178  $data = $this->STREAM->read($length);
179  return (string)$data;
180  }
181 
189  function get_translation_string($num) {
190  $length = $this->table_translations[$num * 2 + 1];
191  $offset = $this->table_translations[$num * 2 + 2];
192  if (! $length)
193  return '';
194  $this->STREAM->seekto($offset);
195  $data = $this->STREAM->read($length);
196  return (string)$data;
197  }
198 
208  function find_string($string, $start = -1, $end = -1) {
209  if (($start == -1) or ($end == -1)) {
210  // find_string is called with only one parameter, set start end end
211  $start = 0;
212  $end = $this->total;
213  }
214  if (abs($start - $end) <= 1) {
215  // We're done, now we either found the string, or it doesn't exist
216  $txt = $this->get_original_string($start);
217  if ($string == $txt)
218  return $start;
219  else
220  return -1;
221  } else if ($start > $end) {
222  // start > end -> turn around and start over
223  return $this->find_string($string, $end, $start);
224  } else {
225  // Divide table in two parts
226  $half = (int)(($start + $end) / 2);
227  $cmp = strcmp($string, $this->get_original_string($half));
228  if ($cmp == 0)
229  // string is exactly in the middle => return it
230  return $half;
231  else if ($cmp < 0)
232  // The string is in the upper half
233  return $this->find_string($string, $start, $half);
234  else
235  // The string is in the lower half
236  return $this->find_string($string, $half, $end);
237  }
238  }
239 
247  function translate($string) {
248  if ($this->short_circuit)
249  return $string;
250  $this->load_tables();
251 
252  if ($this->enable_cache) {
253  // Caching enabled, get translated string from cache
254  if (array_key_exists($string, $this->cache_translations))
255  return $this->cache_translations[$string];
256  else
257  return $string;
258  } else {
259  // Caching not enabled, try to find string
260  $num = $this->find_string($string);
261  if ($num == -1)
262  return $string;
263  else
264  return $this->get_translation_string($num);
265  }
266  }
267 
274  function sanitize_plural_expression($expr) {
275  // Get rid of disallowed characters.
276  $expr = preg_replace('@[^a-zA-Z0-9_:;\(\)\?\|\&=!<>+*/\%-]@', '', $expr);
277 
278  // Add parenthesis for tertiary '?' operator.
279  $expr .= ';';
280  $res = '';
281  $p = 0;
282  for ($i = 0; $i < strlen($expr); $i++) {
283  $ch = $expr[$i];
284  switch ($ch) {
285  case '?':
286  $res .= ' ? (';
287  $p++;
288  break;
289  case ':':
290  $res .= ') : (';
291  break;
292  case ';':
293  $res .= str_repeat( ')', $p) . ';';
294  $p = 0;
295  break;
296  default:
297  $res .= $ch;
298  }
299  }
300  return $res;
301  }
302 
309  function get_plural_forms() {
310  // lets assume message number 0 is header
311  // this is true, right?
312  $this->load_tables();
313 
314  // cache header field for plural forms
315  if (! is_string($this->pluralheader)) {
316  if ($this->enable_cache) {
317  $header = $this->cache_translations[""];
318  } else {
319  $header = $this->get_translation_string(0);
320  }
321  if (eregi("plural-forms: ([^\n]*)\n", $header, $regs))
322  $expr = $regs[1];
323  else
324  $expr = "nplurals=2; plural=n == 1 ? 0 : 1;";
325 
326  $this->pluralheader = $this->sanitize_plural_expression($expr);
327  }
328  return $this->pluralheader;
329  }
330 
338  function select_string($n) {
339  $string = $this->get_plural_forms();
340  $string = str_replace('nplurals',"\$total",$string);
341  $string = str_replace("n",$n,$string);
342  $string = str_replace('plural',"\$plural",$string);
343 
344  $total = 0;
345  $plural = 0;
346 
347  eval("$string");
348  if ($plural >= $total) $plural = $total - 1;
349  return $plural;
350  }
351 
361  function ngettext($single, $plural, $number) {
362  if ($this->short_circuit) {
363  if ($number != 1)
364  return $plural;
365  else
366  return $single;
367  }
368 
369  // find out the appropriate form
370  $select = $this->select_string($number);
371 
372  // this should contains all strings separated by NULLs
373  $key = $single.chr(0).$plural;
374 
375 
376  if ($this->enable_cache) {
377  if (! array_key_exists($key, $this->cache_translations)) {
378  return ($number != 1) ? $plural : $single;
379  } else {
380  $result = $this->cache_translations[$key];
381  $list = explode(chr(0), $result);
382  return $list[$select];
383  }
384  } else {
385  $num = $this->find_string($key);
386  if ($num == -1) {
387  return ($number != 1) ? $plural : $single;
388  } else {
389  $result = $this->get_translation_string($num);
390  $list = explode(chr(0), $result);
391  return $list[$select];
392  }
393  }
394  }
395 
396 }
397 
398 ?>