4 * Copyright (c) 2003 Danilo Segan <danilo@kvota.net>.
6 * This file is part of PHP-gettext.
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.
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.
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
22 * @copyright © 2004-2007 The SquirrelMail Project Team
23 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
25 * @package squirrelmail
30 * Class that uses parsed translation input objects
31 * @package squirrelmail
34 class gettext_reader
{
36 * holds error code (0 if no error)
42 * specifies the byte order: 0 low endian, 1 big endian
58 // Reads 4 byte value from $FD and puts it in int
59 // $BYTEORDER specifies the byte order: 0 low endian, 1 big endian
60 for ($i=0; $i<4; $i++
) {
61 $byte[$i]=ord($this->STREAM
->read(1));
63 //print sprintf("pos: %d\n",$this->STREAM->currentpos());
64 if ($this->BYTEORDER
== 0)
65 return (int)(($byte[0]) |
($byte[1]<<8) |
($byte[2]<<16) |
($byte[3]<<24));
67 return (int)(($byte[3]) |
($byte[2]<<8) |
($byte[1]<<16) |
($byte[0]<<24));
71 * constructor that requires StreamReader object
72 * @param object $Reader
73 * @return boolean false, if some error with stream
75 function gettext_reader($Reader) {
76 $MAGIC1 = (int) ((222) |
(18<<8) |
(4<<16) |
(149<<24));
77 $MAGIC2 = (int) ((149) |
(4<<8) |
(18<<16) |
(222<<24));
79 $this->STREAM
= $Reader;
80 if ($this->STREAM
->error
>0) {
84 $magic = $this->readint();
85 if ($magic == $MAGIC1) {
87 } elseif ($magic == $MAGIC2) {
90 $this->error
= 1; // not MO file
94 // FIXME: Do we care about revision? We should.
95 $revision = $this->readint();
97 $total = $this->readint();
98 $originals = $this->readint();
99 $translations = $this->readint();
101 $this->total
= $total;
102 $this->originals
= $originals;
103 $this->translations
= $translations;
105 // Here we store already found translations
106 $this->_HASHED
= array();
110 * @param boolean $translations do translation have to be loaded
112 function load_tables($translations=false) {
113 // if tables are loaded do not load them again
114 if (!isset($this->ORIGINALS
)) {
115 $this->ORIGINALS
= array();
116 $this->STREAM
->seekto($this->originals
);
117 for ($i=0; $i<$this->total
; $i++
) {
118 $len = $this->readint();
119 $ofs = $this->readint();
120 $this->ORIGINALS
[] = array($len,$ofs);
124 // similar for translations
125 if ($translations and !isset($this->TRANSLATIONS
)) {
126 $this->TRANSLATIONS
= array();
127 $this->STREAM
->seekto($this->translations
);
128 for ($i=0; $i<$this->total
; $i++
) {
129 $len = $this->readint();
130 $ofs = $this->readint();
131 $this->TRANSLATIONS
[] = array($len,$ofs);
137 * get a string with particular number
138 * @param integer $num
139 * @return string untranslated string
141 function get_string_number($num) {
142 // TODO: Add simple hashing [check array, add if not already there]
143 $this->load_tables();
144 $meta = $this->ORIGINALS
[$num];
147 $this->STREAM
->seekto($offset);
148 $data = $this->STREAM
->read($length);
149 return (string)$data;
153 * get translated string with particular number
154 * @param integer $num
155 * @return string translated string
157 function get_translation_number($num) {
158 // get a string with particular number
159 // TODO: Add simple hashing [check array, add if not already there]
160 $this->load_tables(true);
161 $meta = $this->TRANSLATIONS
[$num];
164 $this->STREAM
->seekto($offset);
165 $data = $this->STREAM
->read($length);
166 return (string)$data;
170 * binary search for string
171 * @param string $string
172 * @param integer $start
173 * @param integer $end
175 function find_string($string, $start,$end) {
176 //print "start: $start, end: $end\n";
177 // Simple hashing to improve speed
178 if (isset($this->_HASHED
[$string])) return $this->_HASHED
[$string];
180 if (abs($start-$end)<=1) {
181 // we're done, if it's not it, bye bye
182 $txt = $this->get_string_number($start);
183 if ($string == $txt) {
184 $this->_HASHED
[$string] = $start;
188 } elseif ($start>$end) {
189 return $this->find_string($string,$end,$start);
191 $half = (int)(($start+
$end)/2);
192 $tst = $this->get_string_number($half);
193 $cmp = strcmp($string,$tst);
195 $this->_HASHED
[$string] = $half;
198 return $this->find_string($string,$start,$half);
200 return $this->find_string($string,$half,$end);
206 * @param string $string English string
207 * @return string translated string
209 function translate($string) {
210 if ($this->error
> 0) return $string;
211 $num = $this->find_string($string, 0, $this->total
);
215 return $this->get_translation_number($num);
219 * extract plural forms header
220 * @return string plural-forms header string
222 function get_plural_forms() {
223 // lets assume message number 0 is header
224 // this is true, right?
226 // cache header field for plural forms
227 if (isset($this->pluralheader
) && is_string($this->pluralheader
))
228 return $this->pluralheader
;
230 $header = $this->get_translation_number(0);
232 if (eregi("plural-forms: (.*)\n",$header,$regs)) {
235 $expr = "nplurals=2; plural=n == 1 ? 0 : 1;";
237 $this->pluralheader
= $expr;
243 * find out the appropriate form number
244 * @param integer $n count
247 function select_string($n) {
248 $string = $this->get_plural_forms();
249 $string = str_replace('nplurals',"\$total",$string);
250 $string = str_replace("n",$n,$string);
251 $string = str_replace('plural',"\$plural",$string);
257 if ($plural>=$total) $plural = 0;
262 * translate string with singular/plural forms
263 * @param string $single English singural form of translation
264 * @param string $plural English plural form of translation
265 * @param string $number count
268 function ngettext($single, $plural, $number) {
269 if ($this->error
> 0) {
272 // find out the appropriate form
273 $select = $this->select_string($number);
275 // this should contains all strings separated by NULLs
276 $result = $this->find_string($single.chr(0).$plural,0,$this->total
);
279 if ($number != 1) return $plural;
282 $result = $this->get_translation_number($result);
284 // lets try to parse all the NUL staff
285 //$result = "proba0".chr(0)."proba1".chr(0)."proba2";
286 $list = explode (chr(0), $result);
287 return $list[$select];