source: trunk/www.guidonia.net/wp/wp-includes/gettext.php@ 44

Last change on this file since 44 was 44, checked in by luciano, 15 years ago
File size: 11.1 KB
Line 
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 */
42class 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?>
Note: See TracBrowser for help on using the repository browser.