Mercurial > hg > isophonics-drupal-site
comparison vendor/symfony/translation/Loader/XliffFileLoader.php @ 0:4c8ae668cc8c
Initial import (non-working)
author | Chris Cannam |
---|---|
date | Wed, 29 Nov 2017 16:09:58 +0000 |
parents | |
children | 1fec387a4317 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4c8ae668cc8c |
---|---|
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\Config\Util\XmlUtils; | |
15 use Symfony\Component\Translation\MessageCatalogue; | |
16 use Symfony\Component\Translation\Exception\InvalidResourceException; | |
17 use Symfony\Component\Translation\Exception\NotFoundResourceException; | |
18 use Symfony\Component\Translation\Exception\InvalidArgumentException; | |
19 use Symfony\Component\Config\Resource\FileResource; | |
20 | |
21 /** | |
22 * XliffFileLoader loads translations from XLIFF files. | |
23 * | |
24 * @author Fabien Potencier <fabien@symfony.com> | |
25 */ | |
26 class XliffFileLoader implements LoaderInterface | |
27 { | |
28 /** | |
29 * {@inheritdoc} | |
30 */ | |
31 public function load($resource, $locale, $domain = 'messages') | |
32 { | |
33 if (!stream_is_local($resource)) { | |
34 throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource)); | |
35 } | |
36 | |
37 if (!file_exists($resource)) { | |
38 throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource)); | |
39 } | |
40 | |
41 $catalogue = new MessageCatalogue($locale); | |
42 $this->extract($resource, $catalogue, $domain); | |
43 | |
44 if (class_exists('Symfony\Component\Config\Resource\FileResource')) { | |
45 $catalogue->addResource(new FileResource($resource)); | |
46 } | |
47 | |
48 return $catalogue; | |
49 } | |
50 | |
51 private function extract($resource, MessageCatalogue $catalogue, $domain) | |
52 { | |
53 try { | |
54 $dom = XmlUtils::loadFile($resource); | |
55 } catch (\InvalidArgumentException $e) { | |
56 throw new InvalidResourceException(sprintf('Unable to load "%s": %s', $resource, $e->getMessage()), $e->getCode(), $e); | |
57 } | |
58 | |
59 $xliffVersion = $this->getVersionNumber($dom); | |
60 $this->validateSchema($xliffVersion, $dom, $this->getSchema($xliffVersion)); | |
61 | |
62 if ('1.2' === $xliffVersion) { | |
63 $this->extractXliff1($dom, $catalogue, $domain); | |
64 } | |
65 | |
66 if ('2.0' === $xliffVersion) { | |
67 $this->extractXliff2($dom, $catalogue, $domain); | |
68 } | |
69 } | |
70 | |
71 /** | |
72 * Extract messages and metadata from DOMDocument into a MessageCatalogue. | |
73 * | |
74 * @param \DOMDocument $dom Source to extract messages and metadata | |
75 * @param MessageCatalogue $catalogue Catalogue where we'll collect messages and metadata | |
76 * @param string $domain The domain | |
77 */ | |
78 private function extractXliff1(\DOMDocument $dom, MessageCatalogue $catalogue, $domain) | |
79 { | |
80 $xml = simplexml_import_dom($dom); | |
81 $encoding = strtoupper($dom->encoding); | |
82 | |
83 $xml->registerXPathNamespace('xliff', 'urn:oasis:names:tc:xliff:document:1.2'); | |
84 foreach ($xml->xpath('//xliff:trans-unit') as $translation) { | |
85 $attributes = $translation->attributes(); | |
86 | |
87 if (!(isset($attributes['resname']) || isset($translation->source))) { | |
88 continue; | |
89 } | |
90 | |
91 $source = isset($attributes['resname']) && $attributes['resname'] ? $attributes['resname'] : $translation->source; | |
92 // If the xlf file has another encoding specified, try to convert it because | |
93 // simple_xml will always return utf-8 encoded values | |
94 $target = $this->utf8ToCharset((string) (isset($translation->target) ? $translation->target : $source), $encoding); | |
95 | |
96 $catalogue->set((string) $source, $target, $domain); | |
97 | |
98 $metadata = array(); | |
99 if ($notes = $this->parseNotesMetadata($translation->note, $encoding)) { | |
100 $metadata['notes'] = $notes; | |
101 } | |
102 | |
103 if (isset($translation->target) && $translation->target->attributes()) { | |
104 $metadata['target-attributes'] = array(); | |
105 foreach ($translation->target->attributes() as $key => $value) { | |
106 $metadata['target-attributes'][$key] = (string) $value; | |
107 } | |
108 } | |
109 | |
110 if (isset($attributes['id'])) { | |
111 $metadata['id'] = (string) $attributes['id']; | |
112 } | |
113 | |
114 $catalogue->setMetadata((string) $source, $metadata, $domain); | |
115 } | |
116 } | |
117 | |
118 /** | |
119 * @param \DOMDocument $dom | |
120 * @param MessageCatalogue $catalogue | |
121 * @param string $domain | |
122 */ | |
123 private function extractXliff2(\DOMDocument $dom, MessageCatalogue $catalogue, $domain) | |
124 { | |
125 $xml = simplexml_import_dom($dom); | |
126 $encoding = strtoupper($dom->encoding); | |
127 | |
128 $xml->registerXPathNamespace('xliff', 'urn:oasis:names:tc:xliff:document:2.0'); | |
129 | |
130 foreach ($xml->xpath('//xliff:unit/xliff:segment') as $segment) { | |
131 $source = $segment->source; | |
132 | |
133 // If the xlf file has another encoding specified, try to convert it because | |
134 // simple_xml will always return utf-8 encoded values | |
135 $target = $this->utf8ToCharset((string) (isset($segment->target) ? $segment->target : $source), $encoding); | |
136 | |
137 $catalogue->set((string) $source, $target, $domain); | |
138 | |
139 $metadata = array(); | |
140 if (isset($segment->target) && $segment->target->attributes()) { | |
141 $metadata['target-attributes'] = array(); | |
142 foreach ($segment->target->attributes() as $key => $value) { | |
143 $metadata['target-attributes'][$key] = (string) $value; | |
144 } | |
145 } | |
146 | |
147 $catalogue->setMetadata((string) $source, $metadata, $domain); | |
148 } | |
149 } | |
150 | |
151 /** | |
152 * Convert a UTF8 string to the specified encoding. | |
153 * | |
154 * @param string $content String to decode | |
155 * @param string $encoding Target encoding | |
156 * | |
157 * @return string | |
158 */ | |
159 private function utf8ToCharset($content, $encoding = null) | |
160 { | |
161 if ('UTF-8' !== $encoding && !empty($encoding)) { | |
162 return mb_convert_encoding($content, $encoding, 'UTF-8'); | |
163 } | |
164 | |
165 return $content; | |
166 } | |
167 | |
168 /** | |
169 * Validates and parses the given file into a DOMDocument. | |
170 * | |
171 * @param string $file | |
172 * @param \DOMDocument $dom | |
173 * @param string $schema source of the schema | |
174 * | |
175 * @throws InvalidResourceException | |
176 */ | |
177 private function validateSchema($file, \DOMDocument $dom, $schema) | |
178 { | |
179 $internalErrors = libxml_use_internal_errors(true); | |
180 | |
181 $disableEntities = libxml_disable_entity_loader(false); | |
182 | |
183 if (!@$dom->schemaValidateSource($schema)) { | |
184 libxml_disable_entity_loader($disableEntities); | |
185 | |
186 throw new InvalidResourceException(sprintf('Invalid resource provided: "%s"; Errors: %s', $file, implode("\n", $this->getXmlErrors($internalErrors)))); | |
187 } | |
188 | |
189 libxml_disable_entity_loader($disableEntities); | |
190 | |
191 $dom->normalizeDocument(); | |
192 | |
193 libxml_clear_errors(); | |
194 libxml_use_internal_errors($internalErrors); | |
195 } | |
196 | |
197 private function getSchema($xliffVersion) | |
198 { | |
199 if ('1.2' === $xliffVersion) { | |
200 $schemaSource = file_get_contents(__DIR__.'/schema/dic/xliff-core/xliff-core-1.2-strict.xsd'); | |
201 $xmlUri = 'http://www.w3.org/2001/xml.xsd'; | |
202 } elseif ('2.0' === $xliffVersion) { | |
203 $schemaSource = file_get_contents(__DIR__.'/schema/dic/xliff-core/xliff-core-2.0.xsd'); | |
204 $xmlUri = 'informativeCopiesOf3rdPartySchemas/w3c/xml.xsd'; | |
205 } else { | |
206 throw new InvalidArgumentException(sprintf('No support implemented for loading XLIFF version "%s".', $xliffVersion)); | |
207 } | |
208 | |
209 return $this->fixXmlLocation($schemaSource, $xmlUri); | |
210 } | |
211 | |
212 /** | |
213 * Internally changes the URI of a dependent xsd to be loaded locally. | |
214 * | |
215 * @param string $schemaSource Current content of schema file | |
216 * @param string $xmlUri External URI of XML to convert to local | |
217 * | |
218 * @return string | |
219 */ | |
220 private function fixXmlLocation($schemaSource, $xmlUri) | |
221 { | |
222 $newPath = str_replace('\\', '/', __DIR__).'/schema/dic/xliff-core/xml.xsd'; | |
223 $parts = explode('/', $newPath); | |
224 if (0 === stripos($newPath, 'phar://')) { | |
225 $tmpfile = tempnam(sys_get_temp_dir(), 'sf2'); | |
226 if ($tmpfile) { | |
227 copy($newPath, $tmpfile); | |
228 $parts = explode('/', str_replace('\\', '/', $tmpfile)); | |
229 } | |
230 } | |
231 | |
232 $drive = '\\' === DIRECTORY_SEPARATOR ? array_shift($parts).'/' : ''; | |
233 $newPath = 'file:///'.$drive.implode('/', array_map('rawurlencode', $parts)); | |
234 | |
235 return str_replace($xmlUri, $newPath, $schemaSource); | |
236 } | |
237 | |
238 /** | |
239 * Returns the XML errors of the internal XML parser. | |
240 * | |
241 * @param bool $internalErrors | |
242 * | |
243 * @return array An array of errors | |
244 */ | |
245 private function getXmlErrors($internalErrors) | |
246 { | |
247 $errors = array(); | |
248 foreach (libxml_get_errors() as $error) { | |
249 $errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)', | |
250 LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR', | |
251 $error->code, | |
252 trim($error->message), | |
253 $error->file ?: 'n/a', | |
254 $error->line, | |
255 $error->column | |
256 ); | |
257 } | |
258 | |
259 libxml_clear_errors(); | |
260 libxml_use_internal_errors($internalErrors); | |
261 | |
262 return $errors; | |
263 } | |
264 | |
265 /** | |
266 * Gets xliff file version based on the root "version" attribute. | |
267 * Defaults to 1.2 for backwards compatibility. | |
268 * | |
269 * @param \DOMDocument $dom | |
270 * | |
271 * @throws InvalidArgumentException | |
272 * | |
273 * @return string | |
274 */ | |
275 private function getVersionNumber(\DOMDocument $dom) | |
276 { | |
277 /** @var \DOMNode $xliff */ | |
278 foreach ($dom->getElementsByTagName('xliff') as $xliff) { | |
279 $version = $xliff->attributes->getNamedItem('version'); | |
280 if ($version) { | |
281 return $version->nodeValue; | |
282 } | |
283 | |
284 $namespace = $xliff->attributes->getNamedItem('xmlns'); | |
285 if ($namespace) { | |
286 if (substr_compare('urn:oasis:names:tc:xliff:document:', $namespace->nodeValue, 0, 34) !== 0) { | |
287 throw new InvalidArgumentException(sprintf('Not a valid XLIFF namespace "%s"', $namespace)); | |
288 } | |
289 | |
290 return substr($namespace, 34); | |
291 } | |
292 } | |
293 | |
294 // Falls back to v1.2 | |
295 return '1.2'; | |
296 } | |
297 | |
298 /** | |
299 * @param \SimpleXMLElement|null $noteElement | |
300 * @param string|null $encoding | |
301 * | |
302 * @return array | |
303 */ | |
304 private function parseNotesMetadata(\SimpleXMLElement $noteElement = null, $encoding = null) | |
305 { | |
306 $notes = array(); | |
307 | |
308 if (null === $noteElement) { | |
309 return $notes; | |
310 } | |
311 | |
312 /** @var \SimpleXMLElement $xmlNote */ | |
313 foreach ($noteElement as $xmlNote) { | |
314 $noteAttributes = $xmlNote->attributes(); | |
315 $note = array('content' => $this->utf8ToCharset((string) $xmlNote, $encoding)); | |
316 if (isset($noteAttributes['priority'])) { | |
317 $note['priority'] = (int) $noteAttributes['priority']; | |
318 } | |
319 | |
320 if (isset($noteAttributes['from'])) { | |
321 $note['from'] = (string) $noteAttributes['from']; | |
322 } | |
323 | |
324 $notes[] = $note; | |
325 } | |
326 | |
327 return $notes; | |
328 } | |
329 } |