MO.php 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. <?php
  2. // +----------------------------------------------------------------------+
  3. // | PEAR :: File :: Gettext :: MO |
  4. // +----------------------------------------------------------------------+
  5. // | This source file is subject to version 3.0 of the PHP license, |
  6. // | that is available at http://www.php.net/license/3_0.txt |
  7. // | If you did not receive a copy of the PHP license and are unable |
  8. // | to obtain it through the world-wide-web, please send a note to |
  9. // | license@php.net so we can mail you a copy immediately. |
  10. // +----------------------------------------------------------------------+
  11. // | Copyright (c) 2004 Michael Wallner <mike@iworks.at> |
  12. // +----------------------------------------------------------------------+
  13. //
  14. // $Id: MO.php 9856 2008-06-25 11:33:49Z fabien $
  15. /**
  16. * File::Gettext::MO
  17. *
  18. * @author Michael Wallner <mike@php.net>
  19. * @license PHP License
  20. */
  21. require_once dirname(__FILE__).'/TGettext.class.php';
  22. /**
  23. * File_Gettext_MO
  24. *
  25. * GNU MO file reader and writer.
  26. *
  27. * @author Michael Wallner <mike@php.net>
  28. * @version $Revision: 9856 $
  29. * @access public
  30. * @package System.I18N.core
  31. */
  32. class TGettext_MO extends TGettext
  33. {
  34. /**
  35. * file handle
  36. *
  37. * @access private
  38. * @var resource
  39. */
  40. protected $_handle = null;
  41. /**
  42. * big endianess
  43. *
  44. * Whether to write with big endian byte order.
  45. *
  46. * @access public
  47. * @var bool
  48. */
  49. protected $writeBigEndian = false;
  50. /**
  51. * Constructor
  52. *
  53. * @access public
  54. * @return object File_Gettext_MO
  55. * @param string $file path to GNU MO file
  56. */
  57. function TGettext_MO($file = '')
  58. {
  59. $this->file = $file;
  60. }
  61. /**
  62. * _read
  63. *
  64. * @access private
  65. * @return mixed
  66. * @param int $bytes
  67. */
  68. function _read($bytes = 1)
  69. {
  70. if (0 < $bytes = abs($bytes)) {
  71. return fread($this->_handle, $bytes);
  72. }
  73. return null;
  74. }
  75. /**
  76. * _readInt
  77. *
  78. * @access private
  79. * @return int
  80. * @param bool $bigendian
  81. */
  82. function _readInt($bigendian = false)
  83. {
  84. //unpack returns a reference????
  85. $unpacked = unpack($bigendian ? 'N' : 'V', $this->_read(4));
  86. return array_shift($unpacked);
  87. }
  88. /**
  89. * _writeInt
  90. *
  91. * @access private
  92. * @return int
  93. * @param int $int
  94. */
  95. function _writeInt($int)
  96. {
  97. return $this->_write(pack($this->writeBigEndian ? 'N' : 'V', (int) $int));
  98. }
  99. /**
  100. * _write
  101. *
  102. * @access private
  103. * @return int
  104. * @param string $data
  105. */
  106. function _write($data)
  107. {
  108. return fwrite($this->_handle, $data);
  109. }
  110. /**
  111. * _writeStr
  112. *
  113. * @access private
  114. * @return int
  115. * @param string $string
  116. */
  117. function _writeStr($string)
  118. {
  119. return $this->_write($string . "\0");
  120. }
  121. /**
  122. * _readStr
  123. *
  124. * @access private
  125. * @return string
  126. * @param array $params associative array with offset and length
  127. * of the string
  128. */
  129. function _readStr($params)
  130. {
  131. fseek($this->_handle, $params['offset']);
  132. return $this->_read($params['length']);
  133. }
  134. /**
  135. * Load MO file
  136. *
  137. * @access public
  138. * @return mixed Returns true on success or PEAR_Error on failure.
  139. * @param string $file
  140. */
  141. function load($file = null)
  142. {
  143. if (!isset($file)) {
  144. $file = $this->file;
  145. }
  146. // open MO file
  147. if (!is_resource($this->_handle = @fopen($file, 'rb'))) {
  148. return false;
  149. }
  150. // lock MO file shared
  151. if (!@flock($this->_handle, LOCK_SH)) {
  152. @fclose($this->_handle);
  153. return false;
  154. }
  155. // read (part of) magic number from MO file header and define endianess
  156. //unpack returns a reference????
  157. $unpacked = unpack('c', $this->_read(4));
  158. switch ($magic = array_shift($unpacked))
  159. {
  160. case -34:
  161. $be = false;
  162. break;
  163. case -107:
  164. $be = true;
  165. break;
  166. default:
  167. return false;
  168. }
  169. // check file format revision - we currently only support 0
  170. if (0 !== ($_rev = $this->_readInt($be))) {
  171. return false;
  172. }
  173. // count of strings in this file
  174. $count = $this->_readInt($be);
  175. // offset of hashing table of the msgids
  176. $offset_original = $this->_readInt($be);
  177. // offset of hashing table of the msgstrs
  178. $offset_translat = $this->_readInt($be);
  179. // move to msgid hash table
  180. fseek($this->_handle, $offset_original);
  181. // read lengths and offsets of msgids
  182. $original = array();
  183. for ($i = 0; $i < $count; $i++) {
  184. $original[$i] = array(
  185. 'length' => $this->_readInt($be),
  186. 'offset' => $this->_readInt($be)
  187. );
  188. }
  189. // move to msgstr hash table
  190. fseek($this->_handle, $offset_translat);
  191. // read lengths and offsets of msgstrs
  192. $translat = array();
  193. for ($i = 0; $i < $count; $i++) {
  194. $translat[$i] = array(
  195. 'length' => $this->_readInt($be),
  196. 'offset' => $this->_readInt($be)
  197. );
  198. }
  199. // read all
  200. for ($i = 0; $i < $count; $i++) {
  201. $this->strings[$this->_readStr($original[$i])] =
  202. $this->_readStr($translat[$i]);
  203. }
  204. // done
  205. @flock($this->_handle, LOCK_UN);
  206. @fclose($this->_handle);
  207. $this->_handle = null;
  208. // check for meta info
  209. if (isset($this->strings[''])) {
  210. $this->meta = parent::meta2array($this->strings['']);
  211. unset($this->strings['']);
  212. }
  213. return true;
  214. }
  215. /**
  216. * Save MO file
  217. *
  218. * @access public
  219. * @return mixed Returns true on success or PEAR_Error on failure.
  220. * @param string $file
  221. */
  222. function save($file = null)
  223. {
  224. if (!isset($file)) {
  225. $file = $this->file;
  226. }
  227. // open MO file
  228. if (!is_resource($this->_handle = @fopen($file, 'wb'))) {
  229. return false;
  230. }
  231. // lock MO file exclusively
  232. if (!@flock($this->_handle, LOCK_EX)) {
  233. @fclose($this->_handle);
  234. return false;
  235. }
  236. // write magic number
  237. if ($this->writeBigEndian) {
  238. $this->_write(pack('c*', 0x95, 0x04, 0x12, 0xde));
  239. } else {
  240. $this->_write(pack('c*', 0xde, 0x12, 0x04, 0x95));
  241. }
  242. // write file format revision
  243. $this->_writeInt(0);
  244. $count = count($this->strings) + ($meta = (count($this->meta) ? 1 : 0));
  245. // write count of strings
  246. $this->_writeInt($count);
  247. $offset = 28;
  248. // write offset of orig. strings hash table
  249. $this->_writeInt($offset);
  250. $offset += ($count * 8);
  251. // write offset transl. strings hash table
  252. $this->_writeInt($offset);
  253. // write size of hash table (we currently ommit the hash table)
  254. $this->_writeInt(0);
  255. $offset += ($count * 8);
  256. // write offset of hash table
  257. $this->_writeInt($offset);
  258. // unshift meta info
  259. if ($meta) {
  260. $meta = '';
  261. foreach ($this->meta as $key => $val) {
  262. $meta .= $key . ': ' . $val . "\n";
  263. }
  264. $strings = array('' => $meta) + $this->strings;
  265. } else {
  266. $strings = $this->strings;
  267. }
  268. // write offsets for original strings
  269. foreach (array_keys($strings) as $o) {
  270. $len = strlen($o);
  271. $this->_writeInt($len);
  272. $this->_writeInt($offset);
  273. $offset += $len + 1;
  274. }
  275. // write offsets for translated strings
  276. foreach ($strings as $t) {
  277. $len = strlen($t);
  278. $this->_writeInt($len);
  279. $this->_writeInt($offset);
  280. $offset += $len + 1;
  281. }
  282. // write original strings
  283. foreach (array_keys($strings) as $o) {
  284. $this->_writeStr($o);
  285. }
  286. // write translated strings
  287. foreach ($strings as $t) {
  288. $this->_writeStr($t);
  289. }
  290. // done
  291. @flock($this->_handle, LOCK_UN);
  292. @fclose($this->_handle);
  293. return true;
  294. }
  295. }