]>
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\MessageCatalogue; | |
15 | use Symfony\Component\Translation\Exception\InvalidResourceException; | |
16 | use Symfony\Component\Translation\Exception\NotFoundResourceException; | |
17 | use Symfony\Component\Config\Resource\FileResource; | |
18 | ||
19 | /** | |
20 | * XliffFileLoader loads translations from XLIFF files. | |
21 | * | |
22 | * @author Fabien Potencier <fabien@symfony.com> | |
23 | * | |
24 | * @api | |
25 | */ | |
26 | class XliffFileLoader implements LoaderInterface | |
27 | { | |
28 | /** | |
29 | * {@inheritdoc} | |
30 | * | |
31 | * @api | |
32 | */ | |
33 | public function load($resource, $locale, $domain = 'messages') | |
34 | { | |
35 | if (!stream_is_local($resource)) { | |
36 | throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource)); | |
37 | } | |
38 | ||
39 | if (!file_exists($resource)) { | |
40 | throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource)); | |
41 | } | |
42 | ||
43 | list($xml, $encoding) = $this->parseFile($resource); | |
44 | $xml->registerXPathNamespace('xliff', 'urn:oasis:names:tc:xliff:document:1.2'); | |
45 | ||
46 | $catalogue = new MessageCatalogue($locale); | |
47 | foreach ($xml->xpath('//xliff:trans-unit') as $translation) { | |
48 | $attributes = $translation->attributes(); | |
49 | ||
50 | if (!(isset($attributes['resname']) || isset($translation->source)) || !isset($translation->target)) { | |
51 | continue; | |
52 | } | |
53 | ||
54 | $source = isset($attributes['resname']) && $attributes['resname'] ? $attributes['resname'] : $translation->source; | |
55 | $target = (string) $translation->target; | |
56 | ||
57 | // If the xlf file has another encoding specified, try to convert it because | |
58 | // simple_xml will always return utf-8 encoded values | |
59 | if ('UTF-8' !== $encoding && !empty($encoding)) { | |
60 | if (function_exists('mb_convert_encoding')) { | |
61 | $target = mb_convert_encoding($target, $encoding, 'UTF-8'); | |
62 | } elseif (function_exists('iconv')) { | |
63 | $target = iconv('UTF-8', $encoding, $target); | |
64 | } else { | |
65 | throw new \RuntimeException('No suitable convert encoding function (use UTF-8 as your encoding or install the iconv or mbstring extension).'); | |
66 | } | |
67 | } | |
68 | ||
69 | $catalogue->set((string) $source, $target, $domain); | |
70 | } | |
71 | $catalogue->addResource(new FileResource($resource)); | |
72 | ||
73 | return $catalogue; | |
74 | } | |
75 | ||
76 | /** | |
77 | * Validates and parses the given file into a SimpleXMLElement | |
78 | * | |
79 | * @param string $file | |
80 | * | |
81 | * @throws \RuntimeException | |
82 | * | |
83 | * @return \SimpleXMLElement | |
84 | * | |
85 | * @throws InvalidResourceException | |
86 | */ | |
87 | private function parseFile($file) | |
88 | { | |
89 | $internalErrors = libxml_use_internal_errors(true); | |
90 | $disableEntities = libxml_disable_entity_loader(true); | |
91 | libxml_clear_errors(); | |
92 | ||
93 | $dom = new \DOMDocument(); | |
94 | $dom->validateOnParse = true; | |
95 | if (!@$dom->loadXML(file_get_contents($file), LIBXML_NONET | (defined('LIBXML_COMPACT') ? LIBXML_COMPACT : 0))) { | |
96 | libxml_disable_entity_loader($disableEntities); | |
97 | ||
98 | throw new InvalidResourceException(implode("\n", $this->getXmlErrors($internalErrors))); | |
99 | } | |
100 | ||
101 | libxml_disable_entity_loader($disableEntities); | |
102 | ||
103 | foreach ($dom->childNodes as $child) { | |
104 | if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) { | |
105 | libxml_use_internal_errors($internalErrors); | |
106 | ||
107 | throw new InvalidResourceException('Document types are not allowed.'); | |
108 | } | |
109 | } | |
110 | ||
111 | $location = str_replace('\\', '/', __DIR__).'/schema/dic/xliff-core/xml.xsd'; | |
112 | $parts = explode('/', $location); | |
113 | if (0 === stripos($location, 'phar://')) { | |
114 | $tmpfile = tempnam(sys_get_temp_dir(), 'sf2'); | |
115 | if ($tmpfile) { | |
116 | copy($location, $tmpfile); | |
117 | $parts = explode('/', str_replace('\\', '/', $tmpfile)); | |
118 | } | |
119 | } | |
120 | $drive = '\\' === DIRECTORY_SEPARATOR ? array_shift($parts).'/' : ''; | |
121 | $location = 'file:///'.$drive.implode('/', array_map('rawurlencode', $parts)); | |
122 | ||
123 | $source = file_get_contents(__DIR__.'/schema/dic/xliff-core/xliff-core-1.2-strict.xsd'); | |
124 | $source = str_replace('http://www.w3.org/2001/xml.xsd', $location, $source); | |
125 | ||
126 | if (!@$dom->schemaValidateSource($source)) { | |
127 | throw new InvalidResourceException(implode("\n", $this->getXmlErrors($internalErrors))); | |
128 | } | |
129 | ||
130 | $dom->normalizeDocument(); | |
131 | ||
132 | libxml_use_internal_errors($internalErrors); | |
133 | ||
134 | return array(simplexml_import_dom($dom), strtoupper($dom->encoding)); | |
135 | } | |
136 | ||
137 | /** | |
138 | * Returns the XML errors of the internal XML parser | |
139 | * | |
140 | * @param Boolean $internalErrors | |
141 | * | |
142 | * @return array An array of errors | |
143 | */ | |
144 | private function getXmlErrors($internalErrors) | |
145 | { | |
146 | $errors = array(); | |
147 | foreach (libxml_get_errors() as $error) { | |
148 | $errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)', | |
149 | LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR', | |
150 | $error->code, | |
151 | trim($error->message), | |
152 | $error->file ? $error->file : 'n/a', | |
153 | $error->line, | |
154 | $error->column | |
155 | ); | |
156 | } | |
157 | ||
158 | libxml_clear_errors(); | |
159 | libxml_use_internal_errors($internalErrors); | |
160 | ||
161 | return $errors; | |
162 | } | |
163 | } |