@@ -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 ));
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
@@ -64,17 +103,47 @@ public function load($resource, $locale, $domain = 'messages')
64
103
$ metadata ['notes ' ] = $ notes ;
65
104
}
66
105
if (isset ($ translation ->target ) && $ translation ->target ->attributes ()) {
67
- $ metadata ['target-attributes ' ] = $ translation ->target ->attributes ();
106
+ $ metadata ['target-attributes ' ] = array ();
107
+ foreach ($ translation ->target ->attributes () as $ key => $ value ) {
108
+ $ metadata ['target-attributes ' ][$ key ] = (string ) $ value ;
109
+ }
68
110
}
69
111
70
112
$ catalogue ->setMetadata ((string ) $ source , $ metadata , $ domain );
71
113
}
114
+ }
72
115
73
- if (class_exists ('Symfony\Component\Config\Resource\FileResource ' )) {
74
- $ catalogue ->addResource (new FileResource ($ resource ));
75
- }
116
+ /**
117
+ * @param \DOMDocument $dom
118
+ * @param MessageCatalogue $catalogue
119
+ * @param string $domain
120
+ */
121
+ private function extractXliff2 (\DOMDocument $ dom , MessageCatalogue $ catalogue , $ domain )
122
+ {
123
+ $ xml = simplexml_import_dom ($ dom );
124
+ $ encoding = strtoupper ($ dom ->encoding );
76
125
77
- return $ catalogue ;
126
+ $ xml ->registerXPathNamespace ('xliff ' , 'urn:oasis:names:tc:xliff:document:2.0 ' );
127
+
128
+ foreach ($ xml ->xpath ('//xliff:unit/xliff:segment ' ) as $ segment ) {
129
+ $ source = $ segment ->source ;
130
+
131
+ // If the xlf file has another encoding specified, try to convert it because
132
+ // simple_xml will always return utf-8 encoded values
133
+ $ target = $ this ->utf8ToCharset ((string ) (isset ($ segment ->target ) ? $ segment ->target : $ source ), $ encoding );
134
+
135
+ $ catalogue ->set ((string ) $ source , $ target , $ domain );
136
+
137
+ $ metadata = array ();
138
+ if (isset ($ segment ->target ) && $ segment ->target ->attributes ()) {
139
+ $ metadata ['target-attributes ' ] = array ();
140
+ foreach ($ segment ->target ->attributes () as $ key => $ value ) {
141
+ $ metadata ['target-attributes ' ][$ key ] = (string ) $ value ;
142
+ }
143
+ }
144
+
145
+ $ catalogue ->setMetadata ((string ) $ source , $ metadata , $ domain );
146
+ }
78
147
}
79
148
80
149
/**
@@ -103,51 +172,64 @@ private function utf8ToCharset($content, $encoding = null)
103
172
}
104
173
105
174
/**
106
- * Validates and parses the given file into a SimpleXMLElement.
107
- *
108
- * @param string $file
109
- *
110
- * @throws \RuntimeException
111
- *
112
- * @return \SimpleXMLElement
175
+ * @param string $file
176
+ * @param \DOMDocument $dom
177
+ * @param string $schema source of the schema
113
178
*
114
179
* @throws InvalidResourceException
115
180
*/
116
- private function parseFile ($ file )
181
+ private function validateSchema ($ file, \ DOMDocument $ dom , $ schema )
117
182
{
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
183
$ internalErrors = libxml_use_internal_errors (true );
125
184
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 )) {
185
+ if (!@$ dom ->schemaValidateSource ($ schema )) {
142
186
throw new InvalidResourceException (sprintf ('Invalid resource provided: "%s"; Errors: %s ' , $ file , implode ("\n" , $ this ->getXmlErrors ($ internalErrors ))));
143
187
}
144
188
145
189
$ dom ->normalizeDocument ();
146
190
147
191
libxml_clear_errors ();
148
192
libxml_use_internal_errors ($ internalErrors );
193
+ }
194
+
195
+ private function getSchema ($ xliffVersion )
196
+ {
197
+ if ('1.2 ' === $ xliffVersion ) {
198
+ $ schemaSource = file_get_contents (__DIR__ .'/schema/dic/xliff-core/xliff-core-1.2-strict.xsd ' );
199
+ $ xmlUri = 'http://www.w3.org/2001/xml.xsd ' ;
200
+ } elseif ('2.0 ' === $ xliffVersion ) {
201
+ $ schemaSource = file_get_contents (__DIR__ .'/schema/dic/xliff-core/xliff-core-2.0.xsd ' );
202
+ $ xmlUri = 'informativeCopiesOf3rdPartySchemas/w3c/xml.xsd ' ;
203
+ } else {
204
+ throw new \InvalidArgumentException (sprintf ('No support implemented for loading XLIFF version "%s". ' , $ xliffVersion ));
205
+ }
149
206
150
- return array (simplexml_import_dom ($ dom ), strtoupper ($ dom ->encoding ));
207
+ return $ this ->fixXmlLocation ($ schemaSource , $ xmlUri );
208
+ }
209
+
210
+ /**
211
+ * Internally changes the URI of a dependent xsd to be loaded locally.
212
+ *
213
+ * @param string $schemaSource Current content of schema file
214
+ * @param string $xmlUri External URI of XML to convert to local
215
+ *
216
+ * @return string
217
+ */
218
+ private function fixXmlLocation ($ schemaSource , $ xmlUri )
219
+ {
220
+ $ newPath = str_replace ('\\' , '/ ' , __DIR__ ).'/schema/dic/xliff-core/xml.xsd ' ;
221
+ $ parts = explode ('/ ' , $ newPath );
222
+ if (0 === stripos ($ newPath , 'phar:// ' )) {
223
+ $ tmpfile = tempnam (sys_get_temp_dir (), 'sf2 ' );
224
+ if ($ tmpfile ) {
225
+ copy ($ newPath , $ tmpfile );
226
+ $ parts = explode ('/ ' , str_replace ('\\' , '/ ' , $ tmpfile ));
227
+ }
228
+ }
229
+ $ drive = '\\' === DIRECTORY_SEPARATOR ? array_shift ($ parts ).'/ ' : '' ;
230
+ $ newPath = 'file:/// ' .$ drive .implode ('/ ' , array_map ('rawurlencode ' , $ parts ));
231
+
232
+ return str_replace ($ xmlUri , $ newPath , $ schemaSource );
151
233
}
152
234
153
235
/**
@@ -178,6 +260,39 @@ private function getXmlErrors($internalErrors)
178
260
}
179
261
180
262
/**
263
+ * Gets xliff file version based on the root "version" attribute.
264
+ * Defaults to 1.2 for backwards compatibility.
265
+ *
266
+ * @param \DOMDocument $dom
267
+ *
268
+ * @throws \InvalidArgumentException
269
+ *
270
+ * @return string
271
+ */
272
+ private function getVersionNumber (\DOMDocument $ dom )
273
+ {
274
+ /** @var \DOMNode $xliff */
275
+ foreach ($ dom ->getElementsByTagName ('xliff ' ) as $ xliff ) {
276
+ $ version = $ xliff ->attributes ->getNamedItem ('version ' );
277
+ if ($ version ) {
278
+ return $ version ->nodeValue ;
279
+ }
280
+
281
+ $ namespace = $ xliff ->attributes ->getNamedItem ('xmlns ' );
282
+ if ($ namespace ) {
283
+ if (substr_compare ('urn:oasis:names:tc:xliff:document: ' , $ namespace ->nodeValue , 0 , 34 ) !== 0 ) {
284
+ throw new \InvalidArgumentException (sprintf ('Not a valid XLIFF namespace "%s" ' , $ namespace ));
285
+ }
286
+
287
+ return substr ($ namespace , 34 );
288
+ }
289
+ }
290
+
291
+ // Falls back to v1.2
292
+ return '1.2 ' ;
293
+ }
294
+
295
+ /*
181
296
* @param \SimpleXMLElement|null $noteElement
182
297
* @param string|null $encoding
183
298
*
0 commit comments