3 * Class for working with MO files
5 * @version $Id: mo.php 106 2009-04-23 19:48:22Z nbachiyski $
10 require_once dirname(__FILE__) . '/translations.php';
11 require_once dirname(__FILE__) . '/streams.php';
13 class MO extends Gettext_Translations {
18 * Fills up with the entries from MO file $filename
20 * @param string $filename MO file to load
22 function import_from_file($filename) {
23 $reader = new POMO_CachedIntFileReader($filename);
24 if (isset($reader->error)) {
27 return $this->import_from_reader($reader);
30 function export_to_file($filename) {
31 $fh = fopen($filename, 'wb');
32 if ( !$fh ) return false;
33 $entries = array_filter($this->entries, create_function('$e', 'return !empty($e->translations);'));
37 $total = count($entries) + 1; // all the headers are one entry
38 $originals_lenghts_addr = 28;
39 $translations_lenghts_addr = $originals_lenghts_addr + 8 * $total;
41 $hash_addr = $translations_lenghts_addr + 8 * $total;
42 $current_addr = $hash_addr;
43 fwrite($fh, pack('V*', $magic, $revision, $total, $originals_lenghts_addr,
44 $translations_lenghts_addr, $size_of_hash, $hash_addr));
45 fseek($fh, $originals_lenghts_addr);
47 // headers' msgid is an empty string
48 fwrite($fh, pack('VV', 0, $current_addr));
50 $originals_table = chr(0);
52 foreach($entries as $entry) {
53 $originals_table .= $this->export_original($entry) . chr(0);
54 $length = strlen($this->export_original($entry));
55 fwrite($fh, pack('VV', $length, $current_addr));
56 $current_addr += $length + 1; // account for the NULL byte after
59 $exported_headers = $this->export_headers();
60 fwrite($fh, pack('VV', strlen($exported_headers), $current_addr));
61 $current_addr += strlen($exported_headers) + 1;
62 $translations_table = $exported_headers . chr(0);
64 foreach($entries as $entry) {
65 $translations_table .= $this->export_translations($entry) . chr(0);
66 $length = strlen($this->export_translations($entry));
67 fwrite($fh, pack('VV', $length, $current_addr));
68 $current_addr += $length + 1;
71 fwrite($fh, $originals_table);
72 fwrite($fh, $translations_table);
76 function export_original($entry) {
77 //TODO: warnings for control characters
78 $exported = $entry->singular;
79 if ($entry->is_plural) $exported .= chr(0).$entry->plural;
80 if (!is_null($entry->context)) $exported = $entry->context . chr(4) . $exported;
84 function export_translations($entry) {
85 //TODO: warnings for control characters
86 return implode(chr(0), $entry->translations);
89 function export_headers() {
91 foreach($this->headers as $header => $value) {
92 $exported.= "$header: $value\n";
97 function get_byteorder($magic) {
99 // The magic is 0x950412de
101 // bug in PHP 5.0.2, see https://savannah.nongnu.org/bugs/?func=detailitem&item_id=10565
102 $magic_little = (int) - 1794895138;
103 $magic_little_64 = (int) 2500072158;
105 $magic_big = ((int) - 569244523) && 0xFFFFFFFF;
107 if ($magic_little == $magic || $magic_little_64 == $magic) {
109 } else if ($magic_big == $magic) {
116 function import_from_reader($reader) {
117 $reader->setEndian('little');
118 $endian = MO::get_byteorder($reader->readint32());
119 if (false === $endian) {
122 $reader->setEndian($endian);
124 $revision = $reader->readint32();
125 $total = $reader->readint32();
126 // get addresses of array of lenghts and offsets for original string and translations
127 $originals_lenghts_addr = $reader->readint32();
128 $translations_lenghts_addr = $reader->readint32();
130 $reader->seekto($originals_lenghts_addr);
131 $originals_lenghts = $reader->readint32array($total * 2); // each of
132 $reader->seekto($translations_lenghts_addr);
133 $translations_lenghts = $reader->readint32array($total * 2);
135 $length = create_function('$i', 'return $i * 2 + 1;');
136 $offset = create_function('$i', 'return $i * 2 + 2;');
138 for ($i = 0; $i < $total; ++$i) {
139 $reader->seekto($originals_lenghts[$offset($i)]);
140 $original = $reader->read($originals_lenghts[$length($i)]);
141 $reader->seekto($translations_lenghts[$offset($i)]);
142 $translation = $reader->read($translations_lenghts[$length($i)]);
143 if ('' == $original) {
144 $this->set_headers($this->make_headers($translation));
146 $this->add_entry($this->make_entry($original, $translation));
155 function &make_entry($original, $translation) {
158 $parts = explode(chr(4), $original);
159 if (isset($parts[1])) {
160 $original = $parts[1];
161 $args['context'] = $parts[0];
163 // look for plural original
164 $parts = explode(chr(0), $original);
165 $args['singular'] = $parts[0];
166 if (isset($parts[1])) {
167 $args['plural'] = $parts[1];
169 // plural translations are also separated by \0
170 $args['translations'] = explode(chr(0), $translation);
171 $entry = & new Translation_Entry($args);
175 function select_plural_form($count) {
176 return $this->gettext_select_plural_form($count);
179 function get_plural_forms_count() {
180 return $this->_nplurals;