161 lines
3.6 KiB
PHP
161 lines
3.6 KiB
PHP
<?php
|
|
namespace RocketTheme\Toolbox\File;
|
|
|
|
/**
|
|
* Implements Gettext Mo File reader (readonly).
|
|
*
|
|
* @package RocketTheme\Toolbox\File
|
|
* @author RocketTheme
|
|
* @license MIT
|
|
*/
|
|
class MoFile extends File
|
|
{
|
|
/**
|
|
* @var string
|
|
*/
|
|
protected $extension = '.mo';
|
|
|
|
protected $pos = 0;
|
|
protected $str;
|
|
protected $len;
|
|
protected $endian;
|
|
|
|
/**
|
|
* @var array|File[]
|
|
*/
|
|
static protected $instances = [];
|
|
|
|
/**
|
|
* File can never be written.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function writable()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Prevent saving file.
|
|
*
|
|
* @throws \BadMethodCallException
|
|
*/
|
|
public function save($data = null)
|
|
{
|
|
throw new \BadMethodCallException('save() not supported for .mo files.');
|
|
}
|
|
|
|
/**
|
|
* Prevent deleting file from filesystem.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function delete()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @param $var
|
|
* @return array
|
|
* @throws \RuntimeException
|
|
*/
|
|
public function decode($var)
|
|
{
|
|
$this->endian = 'V';
|
|
$this->str = $var;
|
|
$this->len = strlen($var);
|
|
|
|
$magic = $this->readInt() & 0xffffffff;
|
|
|
|
if ($magic === 0x950412de) {
|
|
// Low endian.
|
|
$this->endian = 'V';
|
|
} elseif ($magic === 0xde120495) {
|
|
// Big endian.
|
|
$this->endian = 'N';
|
|
} else {
|
|
throw new \RuntimeException('Not a Gettext file (.mo).');
|
|
}
|
|
|
|
// Skip revision number.
|
|
$rev = $this->readInt();
|
|
// Total count.
|
|
$total = $this->readInt();
|
|
// Offset of original table.
|
|
$originals = $this->readInt();
|
|
// Offset of translation table.
|
|
$translations = $this->readInt();
|
|
|
|
// Each table consists of string length and offset of the string.
|
|
$this->seek($originals);
|
|
$table_originals = $this->readIntArray($total * 2);
|
|
$this->seek($translations);
|
|
$table_translations = $this->readIntArray($total * 2);
|
|
|
|
$items = [];
|
|
for ($i = 0; $i < $total; $i++) {
|
|
$this->seek($table_originals[$i * 2 + 2]);
|
|
|
|
// TODO: Original string can have context concatenated on it. We do not yet support that.
|
|
$original = $this->read($table_originals[$i * 2 + 1]);
|
|
|
|
if ($original) {
|
|
$this->seek($table_translations[$i * 2 + 2]);
|
|
|
|
// 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.
|
|
$translated = $this->read($table_translations[$i * 2 + 1]);
|
|
$items[$original] = $translated;
|
|
}
|
|
}
|
|
|
|
return $items;
|
|
}
|
|
|
|
/**
|
|
* @return int
|
|
*/
|
|
protected function readInt()
|
|
{
|
|
$read = $this->read(4);
|
|
|
|
if ($read === false) {
|
|
return false;
|
|
}
|
|
|
|
$read = unpack($this->endian, $read);
|
|
|
|
return array_shift($read);
|
|
}
|
|
|
|
/**
|
|
* @param $count
|
|
* @return array
|
|
*/
|
|
protected function readIntArray($count)
|
|
{
|
|
return unpack($this->endian . $count, $this->read(4 * $count));
|
|
}
|
|
|
|
/**
|
|
* @param $bytes
|
|
* @return string
|
|
*/
|
|
private function read($bytes)
|
|
{
|
|
$data = substr($this->str, $this->pos, $bytes);
|
|
$this->seek($this->pos + $bytes);
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* @param $pos
|
|
* @return mixed
|
|
*/
|
|
private function seek($pos)
|
|
{
|
|
$this->pos = $pos < $this->len ? $pos : $this->len;
|
|
return $this->pos;
|
|
}
|
|
}
|