3 * Copyright (c) 2003 Danilo Segan <danilo@kvota.net>.
5 * This file is part of PHP-gettext.
7 * PHP-gettext is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * PHP-gettext is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with PHP-gettext; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 * @package squirrelmail
26 * Class that uses parsed translation input objects
27 * @package squirrelmail
30 class gettext_reader
{
32 * holds error code (0 if no error)
38 * specifies the byte order: 0 low endian, 1 big endian
54 // Reads 4 byte value from $FD and puts it in int
55 // $BYTEORDER specifies the byte order: 0 low endian, 1 big endian
56 for ($i=0; $i<4; $i++
) {
57 $byte[$i]=ord($this->STREAM
->read(1));
59 //print sprintf("pos: %d\n",$this->STREAM->currentpos());
60 if ($this->BYTEORDER
== 0)
61 return (int)(($byte[0]) |
($byte[1]<<8) |
($byte[2]<<16) |
($byte[3]<<24));
63 return (int)(($byte[3]) |
($byte[2]<<8) |
($byte[1]<<16) |
($byte[0]<<24));
67 * constructor that requires StreamReader object
68 * @param object $Reader
69 * @return boolean false, if some error with stream
71 function gettext_reader($Reader) {
72 $MAGIC1 = (int) ((222) |
(18<<8) |
(4<<16) |
(149<<24));
73 $MAGIC2 = (int) ((149) |
(4<<8) |
(18<<16) |
(222<<24));
75 $this->STREAM
= $Reader;
76 if ($this->STREAM
->error
>0) {
80 $magic = $this->readint();
81 if ($magic == $MAGIC1) {
83 } elseif ($magic == $MAGIC2) {
86 $this->error
= 1; // not MO file
90 // FIXME: Do we care about revision? We should.
91 $revision = $this->readint();
93 $total = $this->readint();
94 $originals = $this->readint();
95 $translations = $this->readint();
97 $this->total
= $total;
98 $this->originals
= $originals;
99 $this->translations
= $translations;
101 // Here we store already found translations
102 $this->_HASHED
= array();
106 * @param boolean $translations do translation have to be loaded
108 function load_tables($translations=false) {
109 // if tables are loaded do not load them again
110 if (!isset($this->ORIGINALS
)) {
111 $this->ORIGINALS
= array();
112 $this->STREAM
->seekto($this->originals
);
113 for ($i=0; $i<$this->total
; $i++
) {
114 $len = $this->readint();
115 $ofs = $this->readint();
116 $this->ORIGINALS
[] = array($len,$ofs);
120 // similar for translations
121 if ($translations and !isset($this->TRANSLATIONS
)) {
122 $this->TRANSLATIONS
= array();
123 $this->STREAM
->seekto($this->translations
);
124 for ($i=0; $i<$this->total
; $i++
) {
125 $len = $this->readint();
126 $ofs = $this->readint();
127 $this->TRANSLATIONS
[] = array($len,$ofs);
133 * get a string with particular number
134 * @param integer $num
135 * @return string untranslated string
137 function get_string_number($num) {
138 // TODO: Add simple hashing [check array, add if not already there]
139 $this->load_tables();
140 $meta = $this->ORIGINALS
[$num];
143 $this->STREAM
->seekto($offset);
144 $data = $this->STREAM
->read($length);
145 return (string)$data;
149 * get translated string with particular number
150 * @param integer $num
151 * @return string translated string
153 function get_translation_number($num) {
154 // get a string with particular number
155 // TODO: Add simple hashing [check array, add if not already there]
156 $this->load_tables(true);
157 $meta = $this->TRANSLATIONS
[$num];
160 $this->STREAM
->seekto($offset);
161 $data = $this->STREAM
->read($length);
162 return (string)$data;
166 * binary search for string
167 * @param string $string
168 * @param integer $start
169 * @param integer $end
171 function find_string($string, $start,$end) {
172 //print "start: $start, end: $end\n";
173 // Simple hashing to improve speed
174 if (isset($this->_HASHED
[$string])) return $this->_HASHED
[$string];
176 if (abs($start-$end)<=1) {
177 // we're done, if it's not it, bye bye
178 $txt = $this->get_string_number($start);
179 if ($string == $txt) {
180 $this->_HASHED
[$string] = $start;
184 } elseif ($start>$end) {
185 return $this->find_string($string,$end,$start);
187 $half = (int)(($start+
$end)/2);
188 $tst = $this->get_string_number($half);
189 $cmp = strcmp($string,$tst);
191 $this->_HASHED
[$string] = $half;
194 return $this->find_string($string,$start,$half);
196 return $this->find_string($string,$half,$end);
202 * @param string $string English string
203 * @return string translated string
205 function translate($string) {
206 if ($this->error
> 0) return $string;
207 $num = $this->find_string($string, 0, $this->total
);
211 return $this->get_translation_number($num);
215 * extract plural forms header
216 * @return string plural-forms header string
218 function get_plural_forms() {
219 // lets assume message number 0 is header
220 // this is true, right?
222 // cache header field for plural forms
223 if (isset($this->pluralheader
) && is_string($this->pluralheader
))
224 return $this->pluralheader
;
226 $header = $this->get_translation_number(0);
228 if (eregi("plural-forms: (.*)\n",$header,$regs)) {
231 $expr = "nplurals=2; plural=n == 1 ? 0 : 1;";
233 $this->pluralheader
= $expr;
239 * find out the appropriate form number
240 * @param integer $n count
243 function select_string($n) {
244 $string = $this->get_plural_forms();
245 $string = str_replace('nplurals',"\$total",$string);
246 $string = str_replace("n",$n,$string);
247 $string = str_replace('plural',"\$plural",$string);
253 if ($plural>=$total) $plural = 0;
258 * translate string with singular/plural forms
259 * @param string $single English singural form of translation
260 * @param string $plural English plural form of translation
261 * @param string $number count
264 function ngettext($single, $plural, $number) {
265 if ($this->error
> 0) {
268 // find out the appropriate form
269 $select = $this->select_string($number);
271 // this should contains all strings separated by NULLs
272 $result = $this->find_string($single.chr(0).$plural,0,$this->total
);
275 if ($number != 1) return $plural;
278 $result = $this->get_translation_number($result);
280 // lets try to parse all the NUL staff
281 //$result = "proba0".chr(0)."proba1".chr(0)."proba2";
282 $list = explode (chr(0), $result);
283 return $list[$select];