root/trunk/wp-includes/gettext.php

Revision 6554, 11.1 kB (checked in by ryan, 7 months ago)

Some file level phpdoc from darkdragon. fixes #5572

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
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  */
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 ?>
Note: See TracBrowser for help on using the browser.