4 * This file is part of the Symfony package.
6 * (c) Fabien Potencier <fabien@symfony.com>
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
12 namespace Symfony\Component\Translation\Loader
;
14 use Symfony\Component\Translation\Exception\InvalidResourceException
;
15 use Symfony\Component\Translation\Exception\NotFoundResourceException
;
16 use Symfony\Component\Config\
Resource\FileResource
;
19 * @copyright Copyright (c) 2010, Union of RAD http://union-of-rad.org (http://lithify.me/)
21 class MoFileLoader
extends ArrayLoader
implements LoaderInterface
24 * Magic used for validating the format of a MO file as well as
25 * detecting if the machine used to create that file was little endian.
29 const MO_LITTLE_ENDIAN_MAGIC
= 0x950412de;
32 * Magic used for validating the format of a MO file as well as
33 * detecting if the machine used to create that file was big endian.
37 const MO_BIG_ENDIAN_MAGIC
= 0xde120495;
40 * The size of the header of a MO file in bytes.
42 * @var integer Number of bytes.
44 const MO_HEADER_SIZE
= 28;
46 public function load($resource, $locale, $domain = 'messages')
48 if (!stream_is_local($resource)) {
49 throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource));
52 if (!file_exists($resource)) {
53 throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource));
56 $messages = $this->parse($resource);
59 if (null === $messages) {
64 if (!is_array($messages)) {
65 throw new InvalidResourceException(sprintf('The file "%s" must contain a valid mo file.', $resource));
68 $catalogue = parent
::load($messages, $locale, $domain);
69 $catalogue->addResource(new FileResource($resource));
75 * Parses machine object (MO) format, independent of the machine's endian it
76 * was created on. Both 32bit and 64bit systems are supported.
78 * @param resource $resource
81 * @throws InvalidResourceException If stream content has an invalid format.
83 private function parse($resource)
85 $stream = fopen($resource, 'r');
87 $stat = fstat($stream);
89 if ($stat['size'] < self
::MO_HEADER_SIZE
) {
90 throw new InvalidResourceException("MO stream content has an invalid format.");
92 $magic = unpack('V1', fread($stream, 4));
93 $magic = hexdec(substr(dechex(current($magic)), -8));
95 if ($magic == self
::MO_LITTLE_ENDIAN_MAGIC
) {
97 } elseif ($magic == self
::MO_BIG_ENDIAN_MAGIC
) {
100 throw new InvalidResourceException("MO stream content has an invalid format.");
103 $formatRevision = $this->readLong($stream, $isBigEndian);
104 $count = $this->readLong($stream, $isBigEndian);
105 $offsetId = $this->readLong($stream, $isBigEndian);
106 $offsetTranslated = $this->readLong($stream, $isBigEndian);
107 $sizeHashes = $this->readLong($stream, $isBigEndian);
108 $offsetHashes = $this->readLong($stream, $isBigEndian);
112 for ($i = 0; $i < $count; $i++
) {
113 $singularId = $pluralId = null;
116 fseek($stream, $offsetId +
$i * 8);
118 $length = $this->readLong($stream, $isBigEndian);
119 $offset = $this->readLong($stream, $isBigEndian);
125 fseek($stream, $offset);
126 $singularId = fread($stream, $length);
128 if (strpos($singularId, "\000") !== false) {
129 list($singularId, $pluralId) = explode("\000", $singularId);
132 fseek($stream, $offsetTranslated +
$i * 8);
133 $length = $this->readLong($stream, $isBigEndian);
134 $offset = $this->readLong($stream, $isBigEndian);
136 fseek($stream, $offset);
137 $translated = fread($stream, $length);
139 if (strpos($translated, "\000") !== false) {
140 $translated = explode("\000", $translated);
143 $ids = array('singular' => $singularId, 'plural' => $pluralId);
144 $item = compact('ids', 'translated');
146 if (is_array($item['translated'])) {
147 $messages[$item['ids']['singular']] = stripcslashes($item['translated'][0]);
148 if (isset($item['ids']['plural'])) {
150 foreach ($item['translated'] as $plural => $translated) {
151 $plurals[] = sprintf('{%d} %s', $plural, $translated);
153 $messages[$item['ids']['plural']] = stripcslashes(implode('|', $plurals));
155 } elseif (!empty($item['ids']['singular'])) {
156 $messages[$item['ids']['singular']] = stripcslashes($item['translated']);
162 return array_filter($messages);
166 * Reads an unsigned long from stream respecting endianess.
168 * @param resource $stream
169 * @param boolean $isBigEndian
172 private function readLong($stream, $isBigEndian)
174 $result = unpack($isBigEndian ? 'N1' : 'V1', fread($stream, 4));
175 $result = current($result);
177 return (integer) substr($result, -8);