]>
Commit | Line | Data |
---|---|---|
4f5b44bd NL |
1 | <?php |
2 | ||
3 | /* | |
4 | * This file is part of the Symfony package. | |
5 | * | |
6 | * (c) Fabien Potencier <fabien@symfony.com> | |
7 | * | |
8 | * For the full copyright and license information, please view the LICENSE | |
9 | * file that was distributed with this source code. | |
10 | */ | |
11 | ||
12 | namespace Symfony\Component\Translation\Loader; | |
13 | ||
14 | use Symfony\Component\Translation\Exception\InvalidResourceException; | |
15 | use Symfony\Component\Translation\Exception\NotFoundResourceException; | |
16 | use Symfony\Component\Config\Resource\FileResource; | |
17 | ||
18 | /** | |
19 | * @copyright Copyright (c) 2010, Union of RAD http://union-of-rad.org (http://lithify.me/) | |
20 | */ | |
21 | class MoFileLoader extends ArrayLoader implements LoaderInterface | |
22 | { | |
23 | /** | |
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. | |
26 | * | |
27 | * @var float | |
28 | */ | |
29 | const MO_LITTLE_ENDIAN_MAGIC = 0x950412de; | |
30 | ||
31 | /** | |
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. | |
34 | * | |
35 | * @var float | |
36 | */ | |
37 | const MO_BIG_ENDIAN_MAGIC = 0xde120495; | |
38 | ||
39 | /** | |
40 | * The size of the header of a MO file in bytes. | |
41 | * | |
42 | * @var integer Number of bytes. | |
43 | */ | |
44 | const MO_HEADER_SIZE = 28; | |
45 | ||
46 | public function load($resource, $locale, $domain = 'messages') | |
47 | { | |
48 | if (!stream_is_local($resource)) { | |
49 | throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource)); | |
50 | } | |
51 | ||
52 | if (!file_exists($resource)) { | |
53 | throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource)); | |
54 | } | |
55 | ||
56 | $messages = $this->parse($resource); | |
57 | ||
58 | // empty file | |
59 | if (null === $messages) { | |
60 | $messages = array(); | |
61 | } | |
62 | ||
63 | // not an array | |
64 | if (!is_array($messages)) { | |
65 | throw new InvalidResourceException(sprintf('The file "%s" must contain a valid mo file.', $resource)); | |
66 | } | |
67 | ||
68 | $catalogue = parent::load($messages, $locale, $domain); | |
69 | $catalogue->addResource(new FileResource($resource)); | |
70 | ||
71 | return $catalogue; | |
72 | } | |
73 | ||
74 | /** | |
75 | * Parses machine object (MO) format, independent of the machine's endian it | |
76 | * was created on. Both 32bit and 64bit systems are supported. | |
77 | * | |
78 | * @param resource $resource | |
79 | * | |
80 | * @return array | |
81 | * @throws InvalidResourceException If stream content has an invalid format. | |
82 | */ | |
83 | private function parse($resource) | |
84 | { | |
85 | $stream = fopen($resource, 'r'); | |
86 | ||
87 | $stat = fstat($stream); | |
88 | ||
89 | if ($stat['size'] < self::MO_HEADER_SIZE) { | |
90 | throw new InvalidResourceException("MO stream content has an invalid format."); | |
91 | } | |
92 | $magic = unpack('V1', fread($stream, 4)); | |
93 | $magic = hexdec(substr(dechex(current($magic)), -8)); | |
94 | ||
95 | if ($magic == self::MO_LITTLE_ENDIAN_MAGIC) { | |
96 | $isBigEndian = false; | |
97 | } elseif ($magic == self::MO_BIG_ENDIAN_MAGIC) { | |
98 | $isBigEndian = true; | |
99 | } else { | |
100 | throw new InvalidResourceException("MO stream content has an invalid format."); | |
101 | } | |
102 | ||
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); | |
109 | ||
110 | $messages = array(); | |
111 | ||
112 | for ($i = 0; $i < $count; $i++) { | |
113 | $singularId = $pluralId = null; | |
114 | $translated = null; | |
115 | ||
116 | fseek($stream, $offsetId + $i * 8); | |
117 | ||
118 | $length = $this->readLong($stream, $isBigEndian); | |
119 | $offset = $this->readLong($stream, $isBigEndian); | |
120 | ||
121 | if ($length < 1) { | |
122 | continue; | |
123 | } | |
124 | ||
125 | fseek($stream, $offset); | |
126 | $singularId = fread($stream, $length); | |
127 | ||
128 | if (strpos($singularId, "\000") !== false) { | |
129 | list($singularId, $pluralId) = explode("\000", $singularId); | |
130 | } | |
131 | ||
132 | fseek($stream, $offsetTranslated + $i * 8); | |
133 | $length = $this->readLong($stream, $isBigEndian); | |
134 | $offset = $this->readLong($stream, $isBigEndian); | |
135 | ||
136 | fseek($stream, $offset); | |
137 | $translated = fread($stream, $length); | |
138 | ||
139 | if (strpos($translated, "\000") !== false) { | |
140 | $translated = explode("\000", $translated); | |
141 | } | |
142 | ||
143 | $ids = array('singular' => $singularId, 'plural' => $pluralId); | |
144 | $item = compact('ids', 'translated'); | |
145 | ||
146 | if (is_array($item['translated'])) { | |
147 | $messages[$item['ids']['singular']] = stripcslashes($item['translated'][0]); | |
148 | if (isset($item['ids']['plural'])) { | |
149 | $plurals = array(); | |
150 | foreach ($item['translated'] as $plural => $translated) { | |
151 | $plurals[] = sprintf('{%d} %s', $plural, $translated); | |
152 | } | |
153 | $messages[$item['ids']['plural']] = stripcslashes(implode('|', $plurals)); | |
154 | } | |
155 | } elseif (!empty($item['ids']['singular'])) { | |
156 | $messages[$item['ids']['singular']] = stripcslashes($item['translated']); | |
157 | } | |
158 | } | |
159 | ||
160 | fclose($stream); | |
161 | ||
162 | return array_filter($messages); | |
163 | } | |
164 | ||
165 | /** | |
166 | * Reads an unsigned long from stream respecting endianess. | |
167 | * | |
168 | * @param resource $stream | |
169 | * @param boolean $isBigEndian | |
170 | * @return integer | |
171 | */ | |
172 | private function readLong($stream, $isBigEndian) | |
173 | { | |
174 | $result = unpack($isBigEndian ? 'N1' : 'V1', fread($stream, 4)); | |
175 | $result = current($result); | |
176 | ||
177 | return (integer) substr($result, -8); | |
178 | } | |
179 | } |