1 <?php |
1 <?php |
2 /** |
2 /** |
3 * Class for working with MO files |
3 * Class for working with MO files |
4 * |
4 * |
5 * @version $Id: mo.php 106 2009-04-23 19:48:22Z nbachiyski $ |
5 * @version $Id: mo.php 293 2009-11-12 15:43:50Z nbachiyski $ |
6 * @package pomo |
6 * @package pomo |
7 * @subpackage mo |
7 * @subpackage mo |
8 */ |
8 */ |
9 |
9 |
10 require_once dirname(__FILE__) . '/translations.php'; |
10 require_once dirname(__FILE__) . '/translations.php'; |
11 require_once dirname(__FILE__) . '/streams.php'; |
11 require_once dirname(__FILE__) . '/streams.php'; |
12 |
12 |
|
13 if ( !class_exists( 'MO' ) ): |
13 class MO extends Gettext_Translations { |
14 class MO extends Gettext_Translations { |
14 |
15 |
15 var $_nplurals = 2; |
16 var $_nplurals = 2; |
16 |
17 |
17 /** |
18 /** |
18 * Fills up with the entries from MO file $filename |
19 * Fills up with the entries from MO file $filename |
19 * |
20 * |
20 * @param string $filename MO file to load |
21 * @param string $filename MO file to load |
21 */ |
22 */ |
22 function import_from_file($filename) { |
23 function import_from_file($filename) { |
23 $reader = new POMO_CachedIntFileReader($filename); |
24 $reader = new POMO_FileReader($filename); |
24 if (isset($reader->error)) { |
25 if (!$reader->is_resource()) |
25 return false; |
26 return false; |
26 } |
|
27 return $this->import_from_reader($reader); |
27 return $this->import_from_reader($reader); |
28 } |
28 } |
29 |
29 |
30 function export_to_file($filename) { |
30 function export_to_file($filename) { |
31 $fh = fopen($filename, 'wb'); |
31 $fh = fopen($filename, 'wb'); |
93 } |
93 } |
94 return $exported; |
94 return $exported; |
95 } |
95 } |
96 |
96 |
97 function get_byteorder($magic) { |
97 function get_byteorder($magic) { |
98 |
|
99 // The magic is 0x950412de |
98 // The magic is 0x950412de |
100 |
99 |
101 // bug in PHP 5.0.2, see https://savannah.nongnu.org/bugs/?func=detailitem&item_id=10565 |
100 // bug in PHP 5.0.2, see https://savannah.nongnu.org/bugs/?func=detailitem&item_id=10565 |
102 $magic_little = (int) - 1794895138; |
101 $magic_little = (int) - 1794895138; |
103 $magic_little_64 = (int) 2500072158; |
102 $magic_little_64 = (int) 2500072158; |
104 // 0xde120495 |
103 // 0xde120495 |
105 $magic_big = ((int) - 569244523) && 0xFFFFFFFF; |
104 $magic_big = ((int) - 569244523) & 0xFFFFFFFF; |
106 |
|
107 if ($magic_little == $magic || $magic_little_64 == $magic) { |
105 if ($magic_little == $magic || $magic_little_64 == $magic) { |
108 return 'little'; |
106 return 'little'; |
109 } else if ($magic_big == $magic) { |
107 } else if ($magic_big == $magic) { |
110 return 'big'; |
108 return 'big'; |
111 } else { |
109 } else { |
112 return false; |
110 return false; |
113 } |
111 } |
114 } |
112 } |
115 |
113 |
116 function import_from_reader($reader) { |
114 function import_from_reader($reader) { |
117 $reader->setEndian('little'); |
115 $endian_string = MO::get_byteorder($reader->readint32()); |
118 $endian = MO::get_byteorder($reader->readint32()); |
116 if (false === $endian_string) { |
119 if (false === $endian) { |
117 return false; |
120 return false; |
118 } |
121 } |
119 $reader->setEndian($endian_string); |
122 $reader->setEndian($endian); |
120 |
123 |
121 $endian = ('big' == $endian_string)? 'N' : 'V'; |
124 $revision = $reader->readint32(); |
122 |
125 $total = $reader->readint32(); |
123 $header = $reader->read(24); |
126 // get addresses of array of lenghts and offsets for original string and translations |
124 if ($reader->strlen($header) != 24) |
127 $originals_lenghts_addr = $reader->readint32(); |
125 return false; |
128 $translations_lenghts_addr = $reader->readint32(); |
126 |
129 |
127 // parse header |
|
128 $header = unpack("{$endian}revision/{$endian}total/{$endian}originals_lenghts_addr/{$endian}translations_lenghts_addr/{$endian}hash_length/{$endian}hash_addr", $header); |
|
129 if (!is_array($header)) |
|
130 return false; |
|
131 |
|
132 extract( $header ); |
|
133 |
|
134 // support revision 0 of MO format specs, only |
|
135 if ($revision != 0) |
|
136 return false; |
|
137 |
|
138 // seek to data blocks |
130 $reader->seekto($originals_lenghts_addr); |
139 $reader->seekto($originals_lenghts_addr); |
131 $originals_lenghts = $reader->readint32array($total * 2); // each of |
140 |
132 $reader->seekto($translations_lenghts_addr); |
141 // read originals' indices |
133 $translations_lenghts = $reader->readint32array($total * 2); |
142 $originals_lengths_length = $translations_lenghts_addr - $originals_lenghts_addr; |
134 |
143 if ( $originals_lengths_length != $total * 8 ) |
135 $length = create_function('$i', 'return $i * 2 + 1;'); |
144 return false; |
136 $offset = create_function('$i', 'return $i * 2 + 2;'); |
145 |
137 |
146 $originals = $reader->read($originals_lengths_length); |
138 for ($i = 0; $i < $total; ++$i) { |
147 if ( $reader->strlen( $originals ) != $originals_lengths_length ) |
139 $reader->seekto($originals_lenghts[$offset($i)]); |
148 return false; |
140 $original = $reader->read($originals_lenghts[$length($i)]); |
149 |
141 $reader->seekto($translations_lenghts[$offset($i)]); |
150 // read translations' indices |
142 $translation = $reader->read($translations_lenghts[$length($i)]); |
151 $translations_lenghts_length = $hash_addr - $translations_lenghts_addr; |
143 if ('' == $original) { |
152 if ( $translations_lenghts_length != $total * 8 ) |
|
153 return false; |
|
154 |
|
155 $translations = $reader->read($translations_lenghts_length); |
|
156 if ( $reader->strlen( $translations ) != $translations_lenghts_length ) |
|
157 return false; |
|
158 |
|
159 // transform raw data into set of indices |
|
160 $originals = $reader->str_split( $originals, 8 ); |
|
161 $translations = $reader->str_split( $translations, 8 ); |
|
162 |
|
163 // skip hash table |
|
164 $strings_addr = $hash_addr + $hash_length * 4; |
|
165 |
|
166 $reader->seekto($strings_addr); |
|
167 |
|
168 $strings = $reader->read_all(); |
|
169 $reader->close(); |
|
170 |
|
171 for ( $i = 0; $i < $total; $i++ ) { |
|
172 $o = unpack( "{$endian}length/{$endian}pos", $originals[$i] ); |
|
173 $t = unpack( "{$endian}length/{$endian}pos", $translations[$i] ); |
|
174 if ( !$o || !$t ) return false; |
|
175 |
|
176 // adjust offset due to reading strings to separate space before |
|
177 $o['pos'] -= $strings_addr; |
|
178 $t['pos'] -= $strings_addr; |
|
179 |
|
180 $original = $reader->substr( $strings, $o['pos'], $o['length'] ); |
|
181 $translation = $reader->substr( $strings, $t['pos'], $t['length'] ); |
|
182 |
|
183 if ('' === $original) { |
144 $this->set_headers($this->make_headers($translation)); |
184 $this->set_headers($this->make_headers($translation)); |
145 } else { |
185 } else { |
146 $this->add_entry($this->make_entry($original, $translation)); |
186 $entry = &$this->make_entry($original, $translation); |
|
187 $this->entries[$entry->key()] = &$entry; |
147 } |
188 } |
148 } |
189 } |
149 return true; |
190 return true; |
150 } |
191 } |
151 |
192 |
152 /** |
193 /** |
|
194 * Build a Translation_Entry from original string and translation strings, |
|
195 * found in a MO file |
|
196 * |
153 * @static |
197 * @static |
|
198 * @param string $original original string to translate from MO file. Might contain |
|
199 * 0x04 as context separator or 0x00 as singular/plural separator |
|
200 * @param string $translation translation string from MO file. Might contain |
|
201 * 0x00 as a plural translations separator |
154 */ |
202 */ |
155 function &make_entry($original, $translation) { |
203 function &make_entry($original, $translation) { |
156 $args = array(); |
204 $entry = & new Translation_Entry(); |
157 // look for context |
205 // look for context |
158 $parts = explode(chr(4), $original); |
206 $parts = explode(chr(4), $original); |
159 if (isset($parts[1])) { |
207 if (isset($parts[1])) { |
160 $original = $parts[1]; |
208 $original = $parts[1]; |
161 $args['context'] = $parts[0]; |
209 $entry->context = $parts[0]; |
162 } |
210 } |
163 // look for plural original |
211 // look for plural original |
164 $parts = explode(chr(0), $original); |
212 $parts = explode(chr(0), $original); |
165 $args['singular'] = $parts[0]; |
213 $entry->singular = $parts[0]; |
166 if (isset($parts[1])) { |
214 if (isset($parts[1])) { |
167 $args['plural'] = $parts[1]; |
215 $entry->is_plural = true; |
|
216 $entry->plural = $parts[1]; |
168 } |
217 } |
169 // plural translations are also separated by \0 |
218 // plural translations are also separated by \0 |
170 $args['translations'] = explode(chr(0), $translation); |
219 $entry->translations = explode(chr(0), $translation); |
171 $entry = & new Translation_Entry($args); |
|
172 return $entry; |
220 return $entry; |
173 } |
221 } |
174 |
222 |
175 function select_plural_form($count) { |
223 function select_plural_form($count) { |
176 return $this->gettext_select_plural_form($count); |
224 return $this->gettext_select_plural_form($count); |
177 } |
225 } |
178 |
226 |
179 function get_plural_forms_count() { |
227 function get_plural_forms_count() { |
180 return $this->_nplurals; |
228 return $this->_nplurals; |
181 } |
229 } |
182 |
|
183 |
|
184 } |
230 } |
185 ?> |
231 endif; |