]> git.immae.eu Git - github/wallabag/wallabag.git/blame - vendor/symfony/translation/Symfony/Component/Translation/Loader/MoFileLoader.php
gitignore vendor
[github/wallabag/wallabag.git] / vendor / symfony / translation / Symfony / Component / Translation / Loader / MoFileLoader.php
CommitLineData
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
12namespace Symfony\Component\Translation\Loader;
13
14use Symfony\Component\Translation\Exception\InvalidResourceException;
15use Symfony\Component\Translation\Exception\NotFoundResourceException;
16use Symfony\Component\Config\Resource\FileResource;
17
18/**
19 * @copyright Copyright (c) 2010, Union of RAD http://union-of-rad.org (http://lithify.me/)
20 */
21class 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}