Hoa central
Gettext.php
Go to the documentation of this file.
1 <?php
2 
37 namespace Hoa\Translate;
38 
39 use Hoa\Stream;
40 
49 class Gettext extends Translate
50 {
56  protected $_bigEndian = false;
57 
63  protected $_revision = 0;
64 
70  protected $_headers = [];
71 
77  protected $_plural = null;
78 
84  protected static $_pluralFunctions = [];
85 
86 
87 
94  public function __construct(Stream\IStream\In $stream)
95  {
96  parent::__construct($stream);
97 
98  if (!($stream instanceof Stream\IStream\Pointable)) {
99  throw new Exception(
100  'Stream %s must also be pointable.',
101  0,
102  $stream->getStreamName()
103  );
104  }
105 
106  $stream->seek(0);
107  $this->setEndianness();
108  $this->unpack();
109 
110  return;
111  }
112 
118  protected function setEndianness()
119  {
120  $magicNumber = unpack('V1', $this->getStream()->read(4));
121 
122  switch (dechex($magicNumber[1])) {
123  case '950412de':
124  $this->_bigEndian = false;
125 
126  break;
127 
128  case 'de120495':
129  $this->_bigEndian = true;
130 
131  break;
132 
133  default:
134  throw new Exception(
135  '%s is not a GNU MO file.',
136  1,
137  $this->getStream()->getStreamName()
138  );
139  }
140 
141  return;
142  }
143 
149  protected function unpack()
150  {
203  $stream = $this->getStream();
204  $out = &$this->_data;
205  $this->setRevision($this->_read(1));
206  $notsh = [
207  'N' => $this->_read(1),
208  'O' => $this->_read(1),
209  'T' => $this->_read(1),
210  'S' => $this->_read(1),
211  'H' => $this->_read(1)
212  ];
213 
214  $stream->seek($notsh['O']);
215  $originalStringOffset = $this->_read(2 * $notsh['N'], null);
216 
217  $stream->seek($notsh['T']);
218  $translateStringOffset = $this->_read(2 * $notsh['N'], null);
219 
220  for ($e = 0, $max = $notsh['N']; $e < $max; ++$e) {
221  if (0 === $originalStringOffset[$e * 2 + 1]) {
222  $_headers = $this->getHeaders();
223 
224  if (!empty($_headers)) {
225  continue;
226  }
227 
228  $stream->seek($translateStringOffset[$e * 2 + 2]);
229  $this->setHeaders(
230  $this->unpackHeaders(
231  $stream->read($translateStringOffset[$e * 2 + 1])
232  )
233  );
234 
235  continue;
236  }
237 
238  $stream->seek($originalStringOffset[$e * 2 + 2]);
239  $key = $stream->read($originalStringOffset[$e * 2 + 1]);
240 
241  $stream->seek($translateStringOffset[$e * 2 + 2]);
242  $out[$key] = $stream->read($translateStringOffset[$e * 2 + 1]);
243  }
244 
245  return;
246  }
247 
255  protected function _read($bytes, $pointer = 1)
256  {
257  $buffer = $this->getStream()->read(4 * $bytes);
258 
259  if (false === $this->isBigEndian()) {
260  $out = unpack('V' . $bytes, $buffer);
261  } else {
262  $out = unpack('N' . $bytes, $buffer);
263  }
264 
265  if (isset($out[$pointer])) {
266  return $out[$pointer];
267  }
268 
269  return $out;
270  }
271 
279  public function _($message)
280  {
281  if (!isset($this->_data[$message])) {
282  return $message;
283  }
284 
285  $parameters = func_get_args();
286  array_shift($parameters);
287 
288  if (false === $out = @vsprintf($this->_data[$message], $parameters)) {
289  return $message;
290  }
291 
292  return $out;
293  }
294 
306  public function _n($message, $n)
307  {
308  if (false === $this->_plural) {
309  return $message;
310  }
311 
312  $n = max(1, $n);
313  $parameters = array_slice(func_get_args(), 2);
314  $message = preg_replace('#(?<!\\\)(\\\0|\^@)#', "\0", $message);
315  $message = preg_replace('#\\\(\\\0|\^@)#', '\1', $message);
316 
317  if (!isset($this->_data[$message])) {
318  return $message;
319  }
320 
321  $plurals = explode("\0", $this->_data[$message]);
322 
323  if (!isset($this->_headers['Plural-Forms'])) {
324  $this->_plural = false;
325 
326  return $message;
327  }
328 
329  if (null === $this->_plural) {
330  $pluralForms = $this->_headers['Plural-Forms'];
331 
332  if (false === preg_match('#^nplurals=(\d+);\s*plural=(.+?);?$#s', $pluralForms, $matches)) {
333  return $plurals[0];
334  }
335 
336  list(, $nplurals, $plural) = $matches;
337  $_plural = preg_replace('#n#', '$n', $plural);
338  $_plural = preg_replace('#\s+#', '', $_plural);
339  $id = '__hoa_translate_gettext_' . md5($_plural);
340 
341  if (!isset(static::$_pluralFunctions[$id])) {
342  $handle = @create_function(
343  '$n',
344  'return (int) (' . $_plural . ');'
345  );
346 
347  if (false === $handle) {
348  throw new Exception(
349  'Something is wrong with your plurial form %s.',
350  2,
351  $plural
352  );
353  }
354 
355  static::$_pluralFunctions[$id] = $handle;
356  }
357 
358  $this->_plural = $id;
359  }
360 
361  $pluralFunction = static::$_pluralFunctions[$this->_plural];
362  $i = $pluralFunction($n);
363 
364  if (!isset($plurals[$i])) {
365  return $plurals[0];
366  }
367 
368  if (false === $out = @vsprintf($plurals[$i], $parameters)) {
369  return $plurals[$i];
370  }
371 
372  return $out;
373  }
374 
380  public function isBigEndian()
381  {
382  return $this->_bigEndian;
383  }
384 
391  protected function setRevision($revision)
392  {
393  $old = $this->_revision;
394  $this->_revision = $revision;
395 
396  return $old;
397  }
398 
404  public function getRevision()
405  {
406  return $this->_revision;
407  }
408 
415  public static function unpackHeaders($headers)
416  {
417  $out = [];
418 
419  foreach (explode("\n", $headers) as $line) {
420  if (empty($line)) {
421  continue;
422  }
423 
424  list($type, $value) = explode(':', $line, 2);
425  $out[trim($type)] = trim($value);
426  }
427 
428  return $out;
429  }
430 
437  protected function setHeaders(Array $headers)
438  {
439  $old = $this->_headers;
440  $this->_headers = $headers;
441 
442  return $old;
443  }
444 
450  public function getHeaders()
451  {
452  return $this->_headers;
453  }
454 }
_read($bytes, $pointer=1)
Definition: Gettext.php:255
_n($message, $n)
Definition: Gettext.php:306
__construct(Stream\IStream\In $stream)
Definition: Gettext.php:94
setHeaders(Array $headers)
Definition: Gettext.php:437
setRevision($revision)
Definition: Gettext.php:391
static unpackHeaders($headers)
Definition: Gettext.php:415
static $_pluralFunctions
Definition: Gettext.php:84