@@ -41,10 +41,49 @@ public function load($resource, $locale, $domain = 'messages')
41
41
throw new NotFoundResourceException (sprintf ('File "%s" not found. ' , $ resource ));
42
42
}
43
43
44
- list ($ xml , $ encoding ) = $ this ->parseFile ($ resource );
45
- $ xml ->registerXPathNamespace ('xliff ' , 'urn:oasis:names:tc:xliff:document:1.2 ' );
46
-
47
44
$ catalogue = new MessageCatalogue ($ locale );
45
+ $ this ->extract ($ resource , $ catalogue , $ domain );
46
+
47
+ if (class_exists ('Symfony\Component\Config\Resource\FileResource ' )) {
48
+ $ catalogue ->addResource (new FileResource ($ resource ));
49
+ }
50
+
51
+ return $ catalogue ;
52
+ }
53
+
54
+ private function extract ($ resource , MessageCatalogue $ catalogue , $ domain )
55
+ {
56
+ try {
57
+ $ dom = XmlUtils::loadFile ($ resource );
58
+ } catch (\InvalidArgumentException $ e ) {
59
+ throw new InvalidResourceException (sprintf ('Unable to load "%s": %s ' , $ resource , $ e ->getMessage ()), $ e ->getCode (), $ e );
60
+ }
61
+
62
+ $ xliffVersion = $ this ->getVersionNumber ($ dom );
63
+ $ this ->validateSchema ($ xliffVersion , $ dom , $ this ->getSchema ($ xliffVersion , $ dom ));
64
+
65
+ if ('1.2 ' === $ xliffVersion ) {
66
+ $ this ->extractXliff1 ($ dom , $ catalogue , $ domain );
67
+ }
68
+
69
+ if ('2.0 ' === $ xliffVersion ) {
70
+ $ this ->extractXliff2 ($ dom , $ catalogue , $ domain );
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Extract messages and metadata from DOMDocument into a MessageCatalogue.
76
+ *
77
+ * @param \DOMDocument $dom Source to extract messages and metadata
78
+ * @param MessageCatalogue $catalogue Catalogue where we'll collect messages and metadata
79
+ * @param string $domain The domain
80
+ */
81
+ private function extractXliff1 (\DOMDocument $ dom , MessageCatalogue $ catalogue , $ domain )
82
+ {
83
+ $ xml = simplexml_import_dom ($ dom );
84
+ $ encoding = strtoupper ($ dom ->encoding );
85
+
86
+ $ xml ->registerXPathNamespace ('xliff ' , 'urn:oasis:names:tc:xliff:document:1.2 ' );
48
87
foreach ($ xml ->xpath ('//xliff:trans-unit ' ) as $ translation ) {
49
88
$ attributes = $ translation ->attributes ();
50
89
@@ -69,12 +108,29 @@ public function load($resource, $locale, $domain = 'messages')
69
108
70
109
$ catalogue ->setMetadata ((string ) $ source , $ metadata , $ domain );
71
110
}
111
+ }
72
112
73
- if (class_exists ('Symfony\Component\Config\Resource\FileResource ' )) {
74
- $ catalogue ->addResource (new FileResource ($ resource ));
75
- }
113
+ /**
114
+ * @param \DOMDocument $dom
115
+ * @param MessageCatalogue $catalogue
116
+ * @param string $domain
117
+ */
118
+ private function extractXliff2 (\DOMDocument $ dom , MessageCatalogue $ catalogue , $ domain )
119
+ {
120
+ $ xml = simplexml_import_dom ($ dom );
121
+ $ encoding = strtoupper ($ dom ->encoding );
76
122
77
- return $ catalogue ;
123
+ $ xml ->registerXPathNamespace ('xliff ' , 'urn:oasis:names:tc:xliff:document:2.0 ' );
124
+
125
+ foreach ($ xml ->xpath ('//xliff:unit/xliff:segment ' ) as $ segment ) {
126
+ $ source = $ segment ->source ;
127
+
128
+ // If the xlf file has another encoding specified, try to convert it because
129
+ // simple_xml will always return utf-8 encoded values
130
+ $ target = $ this ->utf8ToCharset ((string ) (isset ($ translation ->target ) ? $ translation ->target : $ source ), $ encoding );
131
+
132
+ $ catalogue ->set ((string ) $ source , $ target , $ domain );
133
+ }
78
134
}
79
135
80
136
/**
@@ -103,51 +159,66 @@ private function utf8ToCharset($content, $encoding = null)
103
159
}
104
160
105
161
/**
106
- * Validates and parses the given file into a SimpleXMLElement.
107
- *
108
- * @param string $file
109
- *
110
- * @throws \RuntimeException
111
- *
112
- * @return \SimpleXMLElement
162
+ * @param \DOMDocument $dom
163
+ * @param string $schema source of the schema
113
164
*
114
165
* @throws InvalidResourceException
115
166
*/
116
- private function parseFile ($ file )
167
+ private function validateSchema ($ file, \ DOMDocument $ dom , $ schema )
117
168
{
118
- try {
119
- $ dom = XmlUtils::loadFile ($ file );
120
- } catch (\InvalidArgumentException $ e ) {
121
- throw new InvalidResourceException (sprintf ('Unable to load "%s": %s ' , $ file , $ e ->getMessage ()), $ e ->getCode (), $ e );
122
- }
123
-
124
169
$ internalErrors = libxml_use_internal_errors (true );
125
170
126
- $ location = str_replace ('\\' , '/ ' , __DIR__ ).'/schema/dic/xliff-core/xml.xsd ' ;
127
- $ parts = explode ('/ ' , $ location );
128
- if (0 === stripos ($ location , 'phar:// ' )) {
129
- $ tmpfile = tempnam (sys_get_temp_dir (), 'sf2 ' );
130
- if ($ tmpfile ) {
131
- copy ($ location , $ tmpfile );
132
- $ parts = explode ('/ ' , str_replace ('\\' , '/ ' , $ tmpfile ));
133
- }
134
- }
135
- $ drive = '\\' === DIRECTORY_SEPARATOR ? array_shift ($ parts ).'/ ' : '' ;
136
- $ location = 'file:/// ' .$ drive .implode ('/ ' , array_map ('rawurlencode ' , $ parts ));
137
-
138
- $ source = file_get_contents (__DIR__ .'/schema/dic/xliff-core/xliff-core-1.2-strict.xsd ' );
139
- $ source = str_replace ('http://www.w3.org/2001/xml.xsd ' , $ location , $ source );
140
-
141
- if (!@$ dom ->schemaValidateSource ($ source )) {
171
+ if (!@$ dom ->schemaValidateSource ($ schema )) {
142
172
throw new InvalidResourceException (sprintf ('Invalid resource provided: "%s"; Errors: %s ' , $ file , implode ("\n" , $ this ->getXmlErrors ($ internalErrors ))));
143
173
}
144
174
145
175
$ dom ->normalizeDocument ();
146
176
147
177
libxml_clear_errors ();
148
178
libxml_use_internal_errors ($ internalErrors );
179
+ }
149
180
150
- return array (simplexml_import_dom ($ dom ), strtoupper ($ dom ->encoding ));
181
+ /**
182
+ * @return string
183
+ */
184
+ private function getSchema ($ xliffVersion , $ dom )
185
+ {
186
+ if ('1.2 ' === $ xliffVersion ) {
187
+ $ schemaSource = file_get_contents (__DIR__ .'/schema/dic/xliff-core/xliff-core-1.2-strict.xsd ' );
188
+ $ xmlUri = 'http://www.w3.org/2001/xml.xsd ' ;
189
+ } elseif ('2.0 ' === $ xliffVersion ) {
190
+ $ schemaSource = file_get_contents (__DIR__ .'/schema/dic/xliff-core/xliff-core-2.0.xsd ' );
191
+ $ xmlUri = 'informativeCopiesOf3rdPartySchemas/w3c/xml.xsd ' ;
192
+ } else {
193
+ throw new \InvalidArgumentException (sprintf ('No support implemented for loading XLIFF version "%s". ' , $ xliffVersion ));
194
+ }
195
+
196
+ return $ this ->fixXmlLocation ($ schemaSource , $ xmlUri );
197
+ }
198
+
199
+ /**
200
+ * Internally changes the URI of a dependent xsd to be loaded locally.
201
+ *
202
+ * @param string $schemaSource Current content of schema file
203
+ * @param string $xmlUri External URI of XML to convert to local
204
+ *
205
+ * @return string
206
+ */
207
+ private function fixXmlLocation ($ schemaSource , $ xmlUri )
208
+ {
209
+ $ newPath = str_replace ('\\' , '/ ' , __DIR__ ).'/schema/dic/xliff-core/xml.xsd ' ;
210
+ $ parts = explode ('/ ' , $ newPath );
211
+ if (0 === stripos ($ newPath , 'phar:// ' )) {
212
+ $ tmpfile = tempnam (sys_get_temp_dir (), 'sf2 ' );
213
+ if ($ tmpfile ) {
214
+ copy ($ newPath , $ tmpfile );
215
+ $ parts = explode ('/ ' , str_replace ('\\' , '/ ' , $ tmpfile ));
216
+ }
217
+ }
218
+ $ drive = '\\' === DIRECTORY_SEPARATOR ? array_shift ($ parts ).'/ ' : '' ;
219
+ $ newPath = 'file:/// ' .$ drive .implode ('/ ' , array_map ('rawurlencode ' , $ parts ));
220
+
221
+ return str_replace ($ xmlUri , $ newPath , $ schemaSource );
151
222
}
152
223
153
224
/**
@@ -178,6 +249,39 @@ private function getXmlErrors($internalErrors)
178
249
}
179
250
180
251
/**
252
+ * Gets xliff file version based on the root "version" attribute.
253
+ * Defaults to 1.2 for backwards compatibility.
254
+ *
255
+ * @param \DOMDocument $dom
256
+ *
257
+ * @throws \InvalidArgumentException
258
+ *
259
+ * @return string
260
+ */
261
+ private function getVersionNumber (\DOMDocument $ dom )
262
+ {
263
+ /** @var \DOMNode $xliff */
264
+ foreach ($ dom ->getElementsByTagName ('xliff ' ) as $ xliff ) {
265
+ $ version = $ xliff ->attributes ->getNamedItem ('version ' );
266
+ if ($ version ) {
267
+ return $ version ->nodeValue ;
268
+ }
269
+
270
+ $ namespace = $ xliff ->attributes ->getNamedItem ('xmlns ' );
271
+ if ($ namespace ) {
272
+ if (substr_compare ('urn:oasis:names:tc:xliff:document: ' , $ namespace ->nodeValue , 0 , 34 ) !== 0 ) {
273
+ throw new \InvalidArgumentException (sprintf ('Not a valid XLIFF namespace "%s" ' , $ namespace ));
274
+ }
275
+
276
+ return substr ($ namespace , 34 );
277
+ }
278
+ }
279
+
280
+ // Falls back to v1.2
281
+ return '1.2 ' ;
282
+ }
283
+
284
+ /*
181
285
* @param \SimpleXMLElement|null $noteElement
182
286
* @param string|null $encoding
183
287
*
0 commit comments