MoFile.php 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. <?php
  2. namespace RocketTheme\Toolbox\File;
  3. /**
  4. * Implements Gettext Mo File reader (readonly).
  5. *
  6. * @package RocketTheme\Toolbox\File
  7. * @author RocketTheme
  8. * @license MIT
  9. */
  10. class MoFile extends File
  11. {
  12. /**
  13. * @var string
  14. */
  15. protected $extension = '.mo';
  16. protected $pos = 0;
  17. protected $str;
  18. protected $len;
  19. protected $endian;
  20. /**
  21. * @var array|File[]
  22. */
  23. static protected $instances = array();
  24. /**
  25. * File can never be written.
  26. *
  27. * @return bool
  28. */
  29. public function writable()
  30. {
  31. return false;
  32. }
  33. /**
  34. * Prevent saving file.
  35. *
  36. * @throws \BadMethodCallException
  37. */
  38. public function save($data = null)
  39. {
  40. throw new \BadMethodCallException('save() not supported for .mo files.');
  41. }
  42. /**
  43. * Prevent deleting file from filesystem.
  44. *
  45. * @return bool
  46. */
  47. public function delete()
  48. {
  49. return false;
  50. }
  51. /**
  52. * @param $var
  53. * @return array
  54. * @throws \RuntimeException
  55. */
  56. public function decode($var)
  57. {
  58. $this->endian = 'V';
  59. $this->str = $var;
  60. $this->len = strlen($var);
  61. $magic = $this->readInt() & 0xffffffff;
  62. if ($magic === 0x950412de) {
  63. // Low endian.
  64. $this->endian = 'V';
  65. } elseif ($magic === 0xde120495) {
  66. // Big endian.
  67. $this->endian = 'N';
  68. } else {
  69. throw new \RuntimeException('Not a Gettext file (.mo).');
  70. }
  71. // Skip revision number.
  72. $rev = $this->readInt();
  73. // Total count.
  74. $total = $this->readInt();
  75. // Offset of original table.
  76. $originals = $this->readInt();
  77. // Offset of translation table.
  78. $translations = $this->readInt();
  79. // Each table consists of string length and offset of the string.
  80. $this->seek($originals);
  81. $table_originals = $this->readIntArray($total * 2);
  82. $this->seek($translations);
  83. $table_translations = $this->readIntArray($total * 2);
  84. $items = [];
  85. for ($i = 0; $i < $total; $i++) {
  86. $this->seek($table_originals[$i * 2 + 2]);
  87. // TODO: Original string can have context concatenated on it. We do not yet support that.
  88. $original = $this->read($table_originals[$i * 2 + 1]);
  89. if ($original) {
  90. $this->seek($table_translations[$i * 2 + 2]);
  91. // TODO: Plural forms are stored by letting the plural of the original string follow the singular of the original string, separated through a NUL byte.
  92. $translated = $this->read($table_translations[$i * 2 + 1]);
  93. $items[$original] = $translated;
  94. }
  95. }
  96. return $items;
  97. }
  98. /**
  99. * @return int
  100. */
  101. protected function readInt()
  102. {
  103. $read = $this->read(4);
  104. if ($read === false) {
  105. return false;
  106. }
  107. $read = unpack($this->endian, $read);
  108. return array_shift($read);
  109. }
  110. /**
  111. * @param $count
  112. * @return array
  113. */
  114. protected function readIntArray($count)
  115. {
  116. return unpack($this->endian . $count, $this->read(4 * $count));
  117. }
  118. /**
  119. * @param $bytes
  120. * @return string
  121. */
  122. private function read($bytes)
  123. {
  124. $data = substr($this->str, $this->pos, $bytes);
  125. $this->seek($this->pos + $bytes);
  126. return $data;
  127. }
  128. /**
  129. * @param $pos
  130. * @return mixed
  131. */
  132. private function seek($pos)
  133. {
  134. $this->pos = $pos < $this->len ? $pos : $this->len;
  135. return $this->pos;
  136. }
  137. }