SymfonyComponentTranslationLoaderXliffFileLoader

XliffFileLoader loads translations from XLIFF files.

Defined (1)

The class is defined in the following location(s).

/vendor/symfony/translation/Loader/XliffFileLoader.php  
  1. class XliffFileLoader implements LoaderInterface 
  2. /** 
  3. * {@inheritdoc} 
  4. */ 
  5. public function load($resource, $locale, $domain = 'messages') 
  6. if (!stream_is_local($resource)) { 
  7. throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource)); 
  8.  
  9. if (!file_exists($resource)) { 
  10. throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource)); 
  11.  
  12. $catalogue = new MessageCatalogue($locale); 
  13. $this->extract($resource, $catalogue, $domain); 
  14.  
  15. if (class_exists('Symfony\Component\Config\Resource\FileResource')) { 
  16. $catalogue->addResource(new FileResource($resource)); 
  17.  
  18. return $catalogue; 
  19.  
  20. private function extract($resource, MessageCatalogue $catalogue, $domain) 
  21. try { 
  22. $dom = XmlUtils::loadFile($resource); 
  23. } catch (\InvalidArgumentException $e) { 
  24. throw new InvalidResourceException(sprintf('Unable to load "%s": %s', $resource, $e->getMessage()), $e->getCode(), $e); 
  25.  
  26. $xliffVersion = $this->getVersionNumber($dom); 
  27. $this->validateSchema($xliffVersion, $dom, $this->getSchema($xliffVersion)); 
  28.  
  29. if ('1.2' === $xliffVersion) { 
  30. $this->extractXliff1($dom, $catalogue, $domain); 
  31.  
  32. if ('2.0' === $xliffVersion) { 
  33. $this->extractXliff2($dom, $catalogue, $domain); 
  34.  
  35. /** 
  36. * Extract messages and metadata from DOMDocument into a MessageCatalogue. 
  37. * @param \DOMDocument $dom Source to extract messages and metadata 
  38. * @param MessageCatalogue $catalogue Catalogue where we'll collect messages and metadata 
  39. * @param string $domain The domain 
  40. */ 
  41. private function extractXliff1(\DOMDocument $dom, MessageCatalogue $catalogue, $domain) 
  42. $xml = simplexml_import_dom($dom); 
  43. $encoding = strtoupper($dom->encoding); 
  44.  
  45. $xml->registerXPathNamespace('xliff', 'urn:oasis:names:tc:xliff:document:1.2'); 
  46. foreach ($xml->xpath('//xliff:trans-unit') as $translation) { 
  47. $attributes = $translation->attributes(); 
  48.  
  49. if (!(isset($attributes['resname']) || isset($translation->source))) { 
  50. continue; 
  51.  
  52. $source = isset($attributes['resname']) && $attributes['resname'] ? $attributes['resname'] : $translation->source; 
  53. // If the xlf file has another encoding specified, try to convert it because 
  54. // simple_xml will always return utf-8 encoded values 
  55. $target = $this->utf8ToCharset((string) (isset($translation->target) ? $translation->target : $source), $encoding); 
  56.  
  57. $catalogue->set((string) $source, $target, $domain); 
  58.  
  59. $metadata = array(); 
  60. if ($notes = $this->parseNotesMetadata($translation->note, $encoding)) { 
  61. $metadata['notes'] = $notes; 
  62.  
  63. if (isset($translation->target) && $translation->target->attributes()) { 
  64. $metadata['target-attributes'] = array(); 
  65. foreach ($translation->target->attributes() as $key => $value) { 
  66. $metadata['target-attributes'][$key] = (string) $value; 
  67.  
  68. if (isset($attributes['id'])) { 
  69. $metadata['id'] = (string) $attributes['id']; 
  70.  
  71. $catalogue->setMetadata((string) $source, $metadata, $domain); 
  72.  
  73. /** 
  74. * @param \DOMDocument $dom 
  75. * @param MessageCatalogue $catalogue 
  76. * @param string $domain 
  77. */ 
  78. private function extractXliff2(\DOMDocument $dom, MessageCatalogue $catalogue, $domain) 
  79. $xml = simplexml_import_dom($dom); 
  80. $encoding = strtoupper($dom->encoding); 
  81.  
  82. $xml->registerXPathNamespace('xliff', 'urn:oasis:names:tc:xliff:document:2.0'); 
  83.  
  84. foreach ($xml->xpath('//xliff:unit/xliff:segment') as $segment) { 
  85. $source = $segment->source; 
  86.  
  87. // If the xlf file has another encoding specified, try to convert it because 
  88. // simple_xml will always return utf-8 encoded values 
  89. $target = $this->utf8ToCharset((string) (isset($segment->target) ? $segment->target : $source), $encoding); 
  90.  
  91. $catalogue->set((string) $source, $target, $domain); 
  92.  
  93. $metadata = array(); 
  94. if (isset($segment->target) && $segment->target->attributes()) { 
  95. $metadata['target-attributes'] = array(); 
  96. foreach ($segment->target->attributes() as $key => $value) { 
  97. $metadata['target-attributes'][$key] = (string) $value; 
  98.  
  99. $catalogue->setMetadata((string) $source, $metadata, $domain); 
  100.  
  101. /** 
  102. * Convert a UTF8 string to the specified encoding. 
  103. * @param string $content String to decode 
  104. * @param string $encoding Target encoding 
  105. * @return string 
  106. */ 
  107. private function utf8ToCharset($content, $encoding = null) 
  108. if ('UTF-8' !== $encoding && !empty($encoding)) { 
  109. return mb_convert_encoding($content, $encoding, 'UTF-8'); 
  110.  
  111. return $content; 
  112.  
  113. /** 
  114. * Validates and parses the given file into a DOMDocument. 
  115. * @param string $file 
  116. * @param \DOMDocument $dom 
  117. * @param string $schema source of the schema 
  118. * @throws InvalidResourceException 
  119. */ 
  120. private function validateSchema($file, \DOMDocument $dom, $schema) 
  121. $internalErrors = libxml_use_internal_errors(true); 
  122.  
  123. $disableEntities = libxml_disable_entity_loader(false); 
  124.  
  125. if (!@$dom->schemaValidateSource($schema)) { 
  126. libxml_disable_entity_loader($disableEntities); 
  127.  
  128. throw new InvalidResourceException(sprintf('Invalid resource provided: "%s"; Errors: %s', $file, implode("\n", $this->getXmlErrors($internalErrors)))); 
  129.  
  130. libxml_disable_entity_loader($disableEntities); 
  131.  
  132. $dom->normalizeDocument(); 
  133.  
  134. libxml_clear_errors(); 
  135. libxml_use_internal_errors($internalErrors); 
  136.  
  137. private function getSchema($xliffVersion) 
  138. if ('1.2' === $xliffVersion) { 
  139. $schemaSource = file_get_contents(__DIR__.'/schema/dic/xliff-core/xliff-core-1.2-strict.xsd'); 
  140. $xmlUri = 'http://www.w3.org/2001/xml.xsd'; 
  141. } elseif ('2.0' === $xliffVersion) { 
  142. $schemaSource = file_get_contents(__DIR__.'/schema/dic/xliff-core/xliff-core-2.0.xsd'); 
  143. $xmlUri = 'informativeCopiesOf3rdPartySchemas/w3c/xml.xsd'; 
  144. } else { 
  145. throw new \InvalidArgumentException(sprintf('No support implemented for loading XLIFF version "%s".', $xliffVersion)); 
  146.  
  147. return $this->fixXmlLocation($schemaSource, $xmlUri); 
  148.  
  149. /** 
  150. * Internally changes the URI of a dependent xsd to be loaded locally. 
  151. * @param string $schemaSource Current content of schema file 
  152. * @param string $xmlUri External URI of XML to convert to local 
  153. * @return string 
  154. */ 
  155. private function fixXmlLocation($schemaSource, $xmlUri) 
  156. $newPath = str_replace('\\', '/', __DIR__).'/schema/dic/xliff-core/xml.xsd'; 
  157. $parts = explode('/', $newPath); 
  158. if (0 === stripos($newPath, 'phar://')) { 
  159. $tmpfile = tempnam(sys_get_temp_dir(), 'sf2'); 
  160. if ($tmpfile) { 
  161. copy($newPath, $tmpfile); 
  162. $parts = explode('/', str_replace('\\', '/', $tmpfile)); 
  163.  
  164. $drive = '\\' === DIRECTORY_SEPARATOR ? array_shift($parts).'/' : ''; 
  165. $newPath = 'file:///'.$drive.implode('/', array_map('rawurlencode', $parts)); 
  166.  
  167. return str_replace($xmlUri, $newPath, $schemaSource); 
  168.  
  169. /** 
  170. * Returns the XML errors of the internal XML parser. 
  171. * @param bool $internalErrors 
  172. * @return array An array of errors 
  173. */ 
  174. private function getXmlErrors($internalErrors) 
  175. $errors = array(); 
  176. foreach (libxml_get_errors() as $error) { 
  177. $errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)',  
  178. LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR',  
  179. $error->code,  
  180. trim($error->message),  
  181. $error->file ?: 'n/a',  
  182. $error->line,  
  183. $error->column 
  184. ); 
  185.  
  186. libxml_clear_errors(); 
  187. libxml_use_internal_errors($internalErrors); 
  188.  
  189. return $errors; 
  190.  
  191. /** 
  192. * Gets xliff file version based on the root "version" attribute. 
  193. * Defaults to 1.2 for backwards compatibility. 
  194. * @param \DOMDocument $dom 
  195. * @throws \InvalidArgumentException 
  196. * @return string 
  197. */ 
  198. private function getVersionNumber(\DOMDocument $dom) 
  199. /** @var \DOMNode $xliff */ 
  200. foreach ($dom->getElementsByTagName('xliff') as $xliff) { 
  201. $version = $xliff->attributes->getNamedItem('version'); 
  202. if ($version) { 
  203. return $version->nodeValue; 
  204.  
  205. $namespace = $xliff->attributes->getNamedItem('xmlns'); 
  206. if ($namespace) { 
  207. if (substr_compare('urn:oasis:names:tc:xliff:document:', $namespace->nodeValue, 0, 34) !== 0) { 
  208. throw new \InvalidArgumentException(sprintf('Not a valid XLIFF namespace "%s"', $namespace)); 
  209.  
  210. return substr($namespace, 34); 
  211.  
  212. // Falls back to v1.2 
  213. return '1.2'; 
  214.  
  215. /** 
  216. * @param \SimpleXMLElement|null $noteElement 
  217. * @param string|null $encoding 
  218. * @return array 
  219. */ 
  220. private function parseNotesMetadata(\SimpleXMLElement $noteElement = null, $encoding = null) 
  221. $notes = array(); 
  222.  
  223. if (null === $noteElement) { 
  224. return $notes; 
  225.  
  226. /** @var \SimpleXMLElement $xmlNote */ 
  227. foreach ($noteElement as $xmlNote) { 
  228. $noteAttributes = $xmlNote->attributes(); 
  229. $note = array('content' => $this->utf8ToCharset((string) $xmlNote, $encoding)); 
  230. if (isset($noteAttributes['priority'])) { 
  231. $note['priority'] = (int) $noteAttributes['priority']; 
  232.  
  233. if (isset($noteAttributes['from'])) { 
  234. $note['from'] = (string) $noteAttributes['from']; 
  235.  
  236. $notes[] = $note; 
  237.  
  238. return $notes;