From 3e45e5759bed2ee3a1b28c0ddf13b31aa77617ef Mon Sep 17 00:00:00 2001 From: Berny Cantos Date: Sat, 29 Nov 2014 14:46:27 +0100 Subject: [PATCH 1/5] add xliff-code-2.0 from oasis --- .../schema/dic/xliff-core/xliff-core-2.0.xsd | 411 ++++++++++++++++++ 1 file changed, 411 insertions(+) create mode 100644 src/Symfony/Component/Translation/Loader/schema/dic/xliff-core/xliff-core-2.0.xsd diff --git a/src/Symfony/Component/Translation/Loader/schema/dic/xliff-core/xliff-core-2.0.xsd b/src/Symfony/Component/Translation/Loader/schema/dic/xliff-core/xliff-core-2.0.xsd new file mode 100644 index 000000000000..f429bb0f376d --- /dev/null +++ b/src/Symfony/Component/Translation/Loader/schema/dic/xliff-core/xliff-core-2.0.xsd @@ -0,0 +1,411 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From f9de3f3cb3f9ff8af53970223ab6b95c99f4df5c Mon Sep 17 00:00:00 2001 From: Berny Cantos Date: Sat, 29 Nov 2014 14:45:34 +0100 Subject: [PATCH 2/5] detect xliff version --- .../Translation/Loader/XliffFileLoader.php | 66 ++++++++++++++----- 1 file changed, 51 insertions(+), 15 deletions(-) diff --git a/src/Symfony/Component/Translation/Loader/XliffFileLoader.php b/src/Symfony/Component/Translation/Loader/XliffFileLoader.php index aa60c438d779..5512e3731c14 100644 --- a/src/Symfony/Component/Translation/Loader/XliffFileLoader.php +++ b/src/Symfony/Component/Translation/Loader/XliffFileLoader.php @@ -129,22 +129,10 @@ private function parseFile($file) $internalErrors = libxml_use_internal_errors(true); - $location = str_replace('\\', '/', __DIR__).'/schema/dic/xliff-core/xml.xsd'; - $parts = explode('/', $location); - if (0 === stripos($location, 'phar://')) { - $tmpfile = tempnam(sys_get_temp_dir(), 'sf2'); - if ($tmpfile) { - copy($location, $tmpfile); - $parts = explode('/', str_replace('\\', '/', $tmpfile)); - } - } - $drive = '\\' === DIRECTORY_SEPARATOR ? array_shift($parts).'/' : ''; - $location = 'file:///'.$drive.implode('/', array_map('rawurlencode', $parts)); + $version = $this->getVersion($dom); + $schemaSource = $this->getSchemaSourceFromVersion($version); - $source = file_get_contents(__DIR__.'/schema/dic/xliff-core/xliff-core-1.2-strict.xsd'); - $source = str_replace('http://www.w3.org/2001/xml.xsd', $location, $source); - - if (!@$dom->schemaValidateSource($source)) { + if (!@$dom->schemaValidateSource($schemaSource)) { throw new InvalidResourceException(implode("\n", $this->getXmlErrors($internalErrors))); } @@ -182,4 +170,52 @@ private function getXmlErrors($internalErrors) return $errors; } + + /** + * @param \DOMDocument $dom + */ + private function getVersion(\DOMDocument $dom) + { + /** @var \DOMNode $xliff */ + foreach ($dom->getElementsByTagName('xliff') as $xliff) { + $version = $xliff->attributes->getNamedItem('version'); + if ($version) { + return $version->nodeValue; + } + } + + // Falls back to v1.2 + return '1.2'; + } + + /** + * @param $version + */ + private function getSchemaSourceFromVersion($version) + { + $newPath = str_replace('\\', '/', __DIR__).'/schema/dic/xliff-core/xml.xsd'; + $parts = explode('/', $newPath); + if (0 === stripos($newPath, 'phar://')) { + $tmpfile = tempnam(sys_get_temp_dir(), 'sf2'); + if ($tmpfile) { + copy($newPath, $tmpfile); + $parts = explode('/', str_replace('\\', '/', $tmpfile)); + } + } + $drive = '\\' === DIRECTORY_SEPARATOR ? array_shift($parts).'/' : ''; + $newPath = 'file:///'.$drive.implode('/', array_map('rawurlencode', $parts)); + + if ($version === '1.2') { + $schema = 'xliff-core-1.2-strict.xsd'; + $oldPath = 'http://www.w3.org/2001/xml.xsd'; + + } else if ($version === '2.0') { + $schema = 'xliff-core-2.0.xsd'; + $oldPath = 'informativeCopiesOf3rdPartySchemas/w3c/xml.xsd'; + } + + $source = file_get_contents(__DIR__.'/schema/dic/xliff-core/'.$schema); + + return str_replace($oldPath, $newPath, $source); + } } From 0c5dfd2d15e77150ba064a86063eb30ca0dcb0f6 Mon Sep 17 00:00:00 2001 From: Berny Cantos Date: Wed, 3 Dec 2014 06:27:28 +0100 Subject: [PATCH 3/5] extract XliffVersion 1.2 --- .../Translation/Loader/XliffFileLoader.php | 145 ++++++------------ .../Loader/XliffVersion/XliffVersion12.php | 137 +++++++++++++++++ 2 files changed, 183 insertions(+), 99 deletions(-) create mode 100644 src/Symfony/Component/Translation/Loader/XliffVersion/XliffVersion12.php diff --git a/src/Symfony/Component/Translation/Loader/XliffFileLoader.php b/src/Symfony/Component/Translation/Loader/XliffFileLoader.php index 5512e3731c14..b5c40ef398b3 100644 --- a/src/Symfony/Component/Translation/Loader/XliffFileLoader.php +++ b/src/Symfony/Component/Translation/Loader/XliffFileLoader.php @@ -41,81 +41,25 @@ public function load($resource, $locale, $domain = 'messages') throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource)); } - list($xml, $encoding) = $this->parseFile($resource); - $xml->registerXPathNamespace('xliff', 'urn:oasis:names:tc:xliff:document:1.2'); + $dom = $this->parseFile($resource); + $version = $this->getVersion($dom); + $this->validateSchema($dom, $version->getSchema()); $catalogue = new MessageCatalogue($locale); - foreach ($xml->xpath('//xliff:trans-unit') as $translation) { - $attributes = $translation->attributes(); - - if (!(isset($attributes['resname']) || isset($translation->source)) || !isset($translation->target)) { - continue; - } - - $source = isset($attributes['resname']) && $attributes['resname'] ? $attributes['resname'] : $translation->source; - // If the xlf file has another encoding specified, try to convert it because - // simple_xml will always return utf-8 encoded values - $target = $this->utf8ToCharset((string) $translation->target, $encoding); - - $catalogue->set((string) $source, $target, $domain); - - if (isset($translation->note)) { - $notes = array(); - foreach ($translation->note as $xmlNote) { - $noteAttributes = $xmlNote->attributes(); - $note = array('content' => $this->utf8ToCharset((string) $xmlNote, $encoding)); - if (isset($noteAttributes['priority'])) { - $note['priority'] = (int) $noteAttributes['priority']; - } - - if (isset($noteAttributes['from'])) { - $note['from'] = (string) $noteAttributes['from']; - } - - $notes[] = $note; - } - - $catalogue->setMetadata((string) $source, array('notes' => $notes), $domain); - } - } + $version->extract($dom, $catalogue, $domain); $catalogue->addResource(new FileResource($resource)); return $catalogue; } /** - * Convert a UTF8 string to the specified encoding. - * - * @param string $content String to decode - * @param string $encoding Target encoding - * - * @return string - */ - private function utf8ToCharset($content, $encoding = null) - { - if ('UTF-8' !== $encoding && !empty($encoding)) { - if (function_exists('mb_convert_encoding')) { - return mb_convert_encoding($content, $encoding, 'UTF-8'); - } - - if (function_exists('iconv')) { - return iconv('UTF-8', $encoding, $content); - } - - throw new \RuntimeException('No suitable convert encoding function (use UTF-8 as your encoding or install the iconv or mbstring extension).'); - } - - return $content; - } - - /** - * Validates and parses the given file into a SimpleXMLElement. + * Parses the given file into a DOMDocument. * * @param string $file * * @throws \RuntimeException * - * @return \SimpleXMLElement + * @return \DOMDocument * * @throws InvalidResourceException */ @@ -127,12 +71,20 @@ private function parseFile($file) throw new InvalidResourceException(sprintf('Unable to load "%s": %s', $file, $e->getMessage()), $e->getCode(), $e); } - $internalErrors = libxml_use_internal_errors(true); + return $dom; + } - $version = $this->getVersion($dom); - $schemaSource = $this->getSchemaSourceFromVersion($version); + /** + * @param \DOMDocument $dom + * @param string $schema source of the schema + * + * @throws InvalidResourceException + */ + private function validateSchema(\DOMDocument $dom, $schema) + { + $internalErrors = libxml_use_internal_errors(true); - if (!@$dom->schemaValidateSource($schemaSource)) { + if (!@$dom->schemaValidateSource($schema)) { throw new InvalidResourceException(implode("\n", $this->getXmlErrors($internalErrors))); } @@ -140,8 +92,6 @@ private function parseFile($file) libxml_clear_errors(); libxml_use_internal_errors($internalErrors); - - return array(simplexml_import_dom($dom), strtoupper($dom->encoding)); } /** @@ -172,50 +122,47 @@ private function getXmlErrors($internalErrors) } /** + * Detects xliff version from file. + * * @param \DOMDocument $dom + * + * @throws InvalidResourceException + * + * @return XliffVersion\AbstractXliffVersion */ private function getVersion(\DOMDocument $dom) { - /** @var \DOMNode $xliff */ - foreach ($dom->getElementsByTagName('xliff') as $xliff) { - $version = $xliff->attributes->getNamedItem('version'); - if ($version) { - return $version->nodeValue; - } + $versionNumber = $this->getVersionNumber($dom); + + if ('1.2' === $versionNumber) { + return new XliffVersion\XliffVersion12(); } - // Falls back to v1.2 - return '1.2'; + throw new InvalidResourceException(sprintf( + 'No support implemented for loading XLIFF version "%s".', + $versionNumber + )); } /** - * @param $version + * Gets xliff file version based on the root "version" attribute. + * Defaults to 1.2 for backwards compatibility + * + * @param \DOMDocument $dom + * + * @return string */ - private function getSchemaSourceFromVersion($version) + private function getVersionNumber(\DOMDocument $dom) { - $newPath = str_replace('\\', '/', __DIR__).'/schema/dic/xliff-core/xml.xsd'; - $parts = explode('/', $newPath); - if (0 === stripos($newPath, 'phar://')) { - $tmpfile = tempnam(sys_get_temp_dir(), 'sf2'); - if ($tmpfile) { - copy($newPath, $tmpfile); - $parts = explode('/', str_replace('\\', '/', $tmpfile)); + /** @var \DOMNode $xliff */ + foreach ($dom->getElementsByTagName('xliff') as $xliff) { + $version = $xliff->attributes->getNamedItem('version'); + if ($version) { + return $version->nodeValue; } } - $drive = '\\' === DIRECTORY_SEPARATOR ? array_shift($parts).'/' : ''; - $newPath = 'file:///'.$drive.implode('/', array_map('rawurlencode', $parts)); - - if ($version === '1.2') { - $schema = 'xliff-core-1.2-strict.xsd'; - $oldPath = 'http://www.w3.org/2001/xml.xsd'; - - } else if ($version === '2.0') { - $schema = 'xliff-core-2.0.xsd'; - $oldPath = 'informativeCopiesOf3rdPartySchemas/w3c/xml.xsd'; - } - - $source = file_get_contents(__DIR__.'/schema/dic/xliff-core/'.$schema); - return str_replace($oldPath, $newPath, $source); + // Falls back to v1.2 + return '1.2'; } } diff --git a/src/Symfony/Component/Translation/Loader/XliffVersion/XliffVersion12.php b/src/Symfony/Component/Translation/Loader/XliffVersion/XliffVersion12.php new file mode 100644 index 000000000000..8f64ad250fde --- /dev/null +++ b/src/Symfony/Component/Translation/Loader/XliffVersion/XliffVersion12.php @@ -0,0 +1,137 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader\XliffVersion; + +use DOMDocument; +use Symfony\Component\Translation\MessageCatalogue; + +/** + * XliffVersion12 loads XLIFF files identified with version 1.2 + * + * @author Berny Cantos + */ +class XliffVersion12 +{ + /** + * Get validation schema source for this version + * + * @return string + */ + public function getSchema() + { + $source = file_get_contents(__DIR__.'/../schema/dic/xliff-core/xliff-core-1.2-strict.xsd'); + + return $this->fixXmlLocation($source, 'http://www.w3.org/2001/xml.xsd'); + } + + /** + * Extract messages and metadata from DOMDocument into a MessageCatalogue + * + * @param DOMDocument $dom Source to extract messages and metadata + * @param MessageCatalogue $catalogue Catalogue where we'll collect messages and metadata + * @param string $domain The domain + */ + public function extract(DOMDocument $dom, MessageCatalogue $catalogue, $domain) + { + $xml = simplexml_import_dom($dom); + $encoding = strtoupper($dom->encoding); + + $xml->registerXPathNamespace('xliff', 'urn:oasis:names:tc:xliff:document:1.2'); + + foreach ($xml->xpath('//xliff:trans-unit') as $translation) { + $attributes = $translation->attributes(); + + if (!(isset($attributes['resname']) || isset($translation->source)) || !isset($translation->target)) { + continue; + } + + $source = isset($attributes['resname']) && $attributes['resname'] ? $attributes['resname'] : $translation->source; + // If the xlf file has another encoding specified, try to convert it because + // simple_xml will always return utf-8 encoded values + $target = $this->utf8ToCharset((string) $translation->target, $encoding); + + $catalogue->set((string) $source, $target, $domain); + + if (isset($translation->note)) { + $notes = array(); + foreach ($translation->note as $xmlNote) { + $noteAttributes = $xmlNote->attributes(); + $note = array('content' => $this->utf8ToCharset((string) $xmlNote, $encoding)); + if (isset($noteAttributes['priority'])) { + $note['priority'] = (int) $noteAttributes['priority']; + } + + if (isset($noteAttributes['from'])) { + $note['from'] = (string) $noteAttributes['from']; + } + + $notes[] = $note; + } + + $catalogue->setMetadata((string) $source, array('notes' => $notes), $domain); + } + } + } + + /** + * Internally changes the URI of a dependent xsd to be loaded locally + * + * @param string $schemaSource Current content of schema file + * @param string $xmlUri External URI of XML to convert to local + * + * @return string + */ + protected function fixXmlLocation($schemaSource, $xmlUri) + { + $newPath = str_replace('\\', '/', __DIR__).'/../schema/dic/xliff-core/xml.xsd'; + $parts = explode('/', $newPath); + if (0 === stripos($newPath, 'phar://')) { + $tmpfile = tempnam(sys_get_temp_dir(), 'sf2'); + if ($tmpfile) { + copy($newPath, $tmpfile); + $parts = explode('/', str_replace('\\', '/', $tmpfile)); + } + } + $drive = '\\' === DIRECTORY_SEPARATOR ? array_shift($parts).'/' : ''; + $newPath = 'file:///'.$drive.implode('/', array_map('rawurlencode', $parts)); + + return str_replace($xmlUri, $newPath, $schemaSource); + } + + /** + * Convert a UTF8 string to the specified encoding + * + * @param string $content String to decode + * @param string $encoding Target encoding + * + * @throws \RuntimeException + * @return string + */ + protected function utf8ToCharset($content, $encoding = null) + { + if (empty($encoding) || 'UTF-8' === $encoding) { + return $content; + } + + if (function_exists('mb_convert_encoding')) { + return mb_convert_encoding($content, $encoding, 'UTF-8'); + } + + if (function_exists('iconv')) { + return iconv('UTF-8', $encoding, $content); + } + + throw new \RuntimeException( + 'No suitable convert encoding function (use UTF-8 as your encoding or install the iconv or mbstring extension).' + ); + } +} From 971b03247845efe65ce5dd1cef9bf28887f6761f Mon Sep 17 00:00:00 2001 From: Berny Cantos Date: Wed, 3 Dec 2014 06:34:21 +0100 Subject: [PATCH 4/5] extract AbstractXliffVersion --- .../XliffVersion/AbstractXliffVersion.php | 91 +++++++++++++++++++ .../Loader/XliffVersion/XliffVersion12.php | 60 +----------- 2 files changed, 94 insertions(+), 57 deletions(-) create mode 100644 src/Symfony/Component/Translation/Loader/XliffVersion/AbstractXliffVersion.php diff --git a/src/Symfony/Component/Translation/Loader/XliffVersion/AbstractXliffVersion.php b/src/Symfony/Component/Translation/Loader/XliffVersion/AbstractXliffVersion.php new file mode 100644 index 000000000000..8ec33ec3fd3d --- /dev/null +++ b/src/Symfony/Component/Translation/Loader/XliffVersion/AbstractXliffVersion.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader\XliffVersion; + +use Symfony\Component\Translation\MessageCatalogue; + +/** + * Base Xliff version loader class. + * + * @author Berny Cantos + */ +abstract class AbstractXliffVersion +{ + /** + * Get validation schema source for this version + * + * @return string + */ + abstract public function getSchema(); + + /** + * Extract messages and metadata from DOMDocument into a MessageCatalogue + * + * @param \DOMDocument $dom Source to extract messages and metadata + * @param MessageCatalogue $catalogue Catalogue where we'll collect messages and metadata + * @param string $domain The domain + */ + abstract public function extract(\DOMDocument $dom, MessageCatalogue $catalogue, $domain); + + /** + * Internally changes the URI of a dependent xsd to be loaded locally + * + * @param string $schemaSource Current content of schema file + * @param string $xmlUri External URI of XML to convert to local + * + * @return string + */ + protected function fixXmlLocation($schemaSource, $xmlUri) + { + $newPath = str_replace('\\', '/', __DIR__).'/../schema/dic/xliff-core/xml.xsd'; + $parts = explode('/', $newPath); + if (0 === stripos($newPath, 'phar://')) { + $tmpfile = tempnam(sys_get_temp_dir(), 'sf2'); + if ($tmpfile) { + copy($newPath, $tmpfile); + $parts = explode('/', str_replace('\\', '/', $tmpfile)); + } + } + $drive = '\\' === DIRECTORY_SEPARATOR ? array_shift($parts).'/' : ''; + $newPath = 'file:///'.$drive.implode('/', array_map('rawurlencode', $parts)); + + return str_replace($xmlUri, $newPath, $schemaSource); + } + + /** + * Convert a UTF8 string to the specified encoding + * + * @param string $content String to decode + * @param string $encoding Target encoding + * + * @throws \RuntimeException + * @return string + */ + protected function utf8ToCharset($content, $encoding = null) + { + if (empty($encoding) || 'UTF-8' === $encoding) { + return $content; + } + + if (function_exists('mb_convert_encoding')) { + return mb_convert_encoding($content, $encoding, 'UTF-8'); + } + + if (function_exists('iconv')) { + return iconv('UTF-8', $encoding, $content); + } + + throw new \RuntimeException( + 'No suitable convert encoding function (use UTF-8 as your encoding or install the iconv or mbstring extension).' + ); + } +} diff --git a/src/Symfony/Component/Translation/Loader/XliffVersion/XliffVersion12.php b/src/Symfony/Component/Translation/Loader/XliffVersion/XliffVersion12.php index 8f64ad250fde..78e54469a54e 100644 --- a/src/Symfony/Component/Translation/Loader/XliffVersion/XliffVersion12.php +++ b/src/Symfony/Component/Translation/Loader/XliffVersion/XliffVersion12.php @@ -11,7 +11,6 @@ namespace Symfony\Component\Translation\Loader\XliffVersion; -use DOMDocument; use Symfony\Component\Translation\MessageCatalogue; /** @@ -19,7 +18,7 @@ * * @author Berny Cantos */ -class XliffVersion12 +class XliffVersion12 extends AbstractXliffVersion { /** * Get validation schema source for this version @@ -36,11 +35,11 @@ public function getSchema() /** * Extract messages and metadata from DOMDocument into a MessageCatalogue * - * @param DOMDocument $dom Source to extract messages and metadata + * @param \DOMDocument $dom Source to extract messages and metadata * @param MessageCatalogue $catalogue Catalogue where we'll collect messages and metadata * @param string $domain The domain */ - public function extract(DOMDocument $dom, MessageCatalogue $catalogue, $domain) + public function extract(\DOMDocument $dom, MessageCatalogue $catalogue, $domain) { $xml = simplexml_import_dom($dom); $encoding = strtoupper($dom->encoding); @@ -81,57 +80,4 @@ public function extract(DOMDocument $dom, MessageCatalogue $catalogue, $domain) } } } - - /** - * Internally changes the URI of a dependent xsd to be loaded locally - * - * @param string $schemaSource Current content of schema file - * @param string $xmlUri External URI of XML to convert to local - * - * @return string - */ - protected function fixXmlLocation($schemaSource, $xmlUri) - { - $newPath = str_replace('\\', '/', __DIR__).'/../schema/dic/xliff-core/xml.xsd'; - $parts = explode('/', $newPath); - if (0 === stripos($newPath, 'phar://')) { - $tmpfile = tempnam(sys_get_temp_dir(), 'sf2'); - if ($tmpfile) { - copy($newPath, $tmpfile); - $parts = explode('/', str_replace('\\', '/', $tmpfile)); - } - } - $drive = '\\' === DIRECTORY_SEPARATOR ? array_shift($parts).'/' : ''; - $newPath = 'file:///'.$drive.implode('/', array_map('rawurlencode', $parts)); - - return str_replace($xmlUri, $newPath, $schemaSource); - } - - /** - * Convert a UTF8 string to the specified encoding - * - * @param string $content String to decode - * @param string $encoding Target encoding - * - * @throws \RuntimeException - * @return string - */ - protected function utf8ToCharset($content, $encoding = null) - { - if (empty($encoding) || 'UTF-8' === $encoding) { - return $content; - } - - if (function_exists('mb_convert_encoding')) { - return mb_convert_encoding($content, $encoding, 'UTF-8'); - } - - if (function_exists('iconv')) { - return iconv('UTF-8', $encoding, $content); - } - - throw new \RuntimeException( - 'No suitable convert encoding function (use UTF-8 as your encoding or install the iconv or mbstring extension).' - ); - } } From 0971c65d89b57e2046ed8013954b3f95e03cf5ac Mon Sep 17 00:00:00 2001 From: Berny Cantos Date: Thu, 4 Dec 2014 14:22:15 +0100 Subject: [PATCH 5/5] add XliffVersion 2.0 --- .../Translation/Loader/XliffFileLoader.php | 62 ++++++++++--------- .../Loader/XliffVersion/XliffVersion20.php | 55 ++++++++++++++++ .../Tests/Loader/XliffFileLoaderTest.php | 19 +++++- .../Tests/fixtures/resources-2.0.xlf | 25 ++++++++ 4 files changed, 131 insertions(+), 30 deletions(-) create mode 100644 src/Symfony/Component/Translation/Loader/XliffVersion/XliffVersion20.php create mode 100644 src/Symfony/Component/Translation/Tests/fixtures/resources-2.0.xlf diff --git a/src/Symfony/Component/Translation/Loader/XliffFileLoader.php b/src/Symfony/Component/Translation/Loader/XliffFileLoader.php index b5c40ef398b3..36019a6326fb 100644 --- a/src/Symfony/Component/Translation/Loader/XliffFileLoader.php +++ b/src/Symfony/Component/Translation/Loader/XliffFileLoader.php @@ -41,42 +41,28 @@ public function load($resource, $locale, $domain = 'messages') throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource)); } - $dom = $this->parseFile($resource); - $version = $this->getVersion($dom); + try { + $dom = XmlUtils::loadFile($resource); + $version = $this->getVersion($dom); + } catch (\InvalidArgumentException $e) { + $message = sprintf('Unable to load "%s": %s', $resource, $e->getMessage()); + + throw new InvalidResourceException($message, $e->getCode(), $e); + } + $this->validateSchema($dom, $version->getSchema()); $catalogue = new MessageCatalogue($locale); $version->extract($dom, $catalogue, $domain); + $catalogue->addResource(new FileResource($resource)); return $catalogue; } - /** - * Parses the given file into a DOMDocument. - * - * @param string $file - * - * @throws \RuntimeException - * - * @return \DOMDocument - * - * @throws InvalidResourceException - */ - private function parseFile($file) - { - try { - $dom = XmlUtils::loadFile($file); - } catch (\InvalidArgumentException $e) { - throw new InvalidResourceException(sprintf('Unable to load "%s": %s', $file, $e->getMessage()), $e->getCode(), $e); - } - - return $dom; - } - /** * @param \DOMDocument $dom - * @param string $schema source of the schema + * @param string $schema source of the schema * * @throws InvalidResourceException */ @@ -126,7 +112,7 @@ private function getXmlErrors($internalErrors) * * @param \DOMDocument $dom * - * @throws InvalidResourceException + * @throws \InvalidArgumentException * * @return XliffVersion\AbstractXliffVersion */ @@ -134,11 +120,15 @@ private function getVersion(\DOMDocument $dom) { $versionNumber = $this->getVersionNumber($dom); - if ('1.2' === $versionNumber) { - return new XliffVersion\XliffVersion12(); + switch ($versionNumber) { + case '1.2': + return new XliffVersion\XliffVersion12(); + + case '2.0': + return new XliffVersion\XliffVersion20(); } - throw new InvalidResourceException(sprintf( + throw new \InvalidArgumentException(sprintf( 'No support implemented for loading XLIFF version "%s".', $versionNumber )); @@ -150,6 +140,8 @@ private function getVersion(\DOMDocument $dom) * * @param \DOMDocument $dom * + * @throws \InvalidArgumentException + * * @return string */ private function getVersionNumber(\DOMDocument $dom) @@ -160,6 +152,18 @@ private function getVersionNumber(\DOMDocument $dom) if ($version) { return $version->nodeValue; } + + $namespace = $xliff->attributes->getNamedItem('xmlns'); + if ($namespace) { + if (substr_compare('urn:oasis:names:tc:xliff:document:', $namespace->nodeValue, 0, 34) !== 0) { + throw new \InvalidArgumentException(sprintf( + 'Not a valid XLIFF namespace "%s"', + $namespace + )); + } + + return substr($namespace, 34); + } } // Falls back to v1.2 diff --git a/src/Symfony/Component/Translation/Loader/XliffVersion/XliffVersion20.php b/src/Symfony/Component/Translation/Loader/XliffVersion/XliffVersion20.php new file mode 100644 index 000000000000..a3d942925770 --- /dev/null +++ b/src/Symfony/Component/Translation/Loader/XliffVersion/XliffVersion20.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader\XliffVersion; + +use Symfony\Component\Translation\MessageCatalogue; + +/** + * XliffVersion20 loads XLIFF files identified with version 2.0 + * + * @author Berny Cantos + */ +class XliffVersion20 extends AbstractXliffVersion +{ + /** + * @return string + */ + public function getSchema() + { + $source = file_get_contents(__DIR__.'/../schema/dic/xliff-core/xliff-core-2.0.xsd'); + + return $this->fixXmlLocation($source, 'informativeCopiesOf3rdPartySchemas/w3c/xml.xsd'); + } + + /** + * @param \DOMDocument $dom + * @param MessageCatalogue $catalogue + * @param string $domain + */ + public function extract(\DOMDocument $dom, MessageCatalogue $catalogue, $domain) + { + $xml = simplexml_import_dom($dom); + $encoding = strtoupper($dom->encoding); + + $xml->registerXPathNamespace('xliff', 'urn:oasis:names:tc:xliff:document:2.0'); + + foreach ($xml->xpath('//xliff:unit/xliff:segment') as $segment) { + $source = $segment->source; + + // If the xlf file has another encoding specified, try to convert it because + // simple_xml will always return utf-8 encoded values + $target = $this->utf8ToCharset((string) $segment->target, $encoding); + + $catalogue->set((string) $source, $target, $domain); + } + } +} diff --git a/src/Symfony/Component/Translation/Tests/Loader/XliffFileLoaderTest.php b/src/Symfony/Component/Translation/Tests/Loader/XliffFileLoaderTest.php index c3d65b493202..a8a5a228f947 100644 --- a/src/Symfony/Component/Translation/Tests/Loader/XliffFileLoaderTest.php +++ b/src/Symfony/Component/Translation/Tests/Loader/XliffFileLoaderTest.php @@ -11,8 +11,8 @@ namespace Symfony\Component\Translation\Tests\Loader; -use Symfony\Component\Translation\Loader\XliffFileLoader; use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Translation\Loader\XliffFileLoader; class XliffFileLoaderTest extends \PHPUnit_Framework_TestCase { @@ -139,4 +139,21 @@ public function testLoadNotes() $this->assertNull($catalogue->getMetadata('extra', 'domain1')); $this->assertEquals(array('notes' => array(array('content' => 'baz'), array('priority' => 2, 'from' => 'bar', 'content' => 'qux'))), $catalogue->getMetadata('key', 'domain1')); } + + public function testLoadVersion2() + { + $loader = new XliffFileLoader(); + $resource = __DIR__.'/../fixtures/resources-2.0.xlf'; + $catalogue = $loader->load($resource, 'en', 'domain1'); + + $this->assertEquals('en', $catalogue->getLocale()); + $this->assertEquals(array(new FileResource($resource)), $catalogue->getResources()); + $this->assertSame(array(), libxml_get_errors()); + + $domains = $catalogue->all(); + $this->assertCount(3, $domains['domain1']); + + // Notes aren't assigned to specific segments, but to whole units, so there's no way to do a mapping + $this->assertEmpty($catalogue->getMetadata()); + } } diff --git a/src/Symfony/Component/Translation/Tests/fixtures/resources-2.0.xlf b/src/Symfony/Component/Translation/Tests/fixtures/resources-2.0.xlf new file mode 100644 index 000000000000..081ea69a9388 --- /dev/null +++ b/src/Symfony/Component/Translation/Tests/fixtures/resources-2.0.xlf @@ -0,0 +1,25 @@ + + + + + + Quetzal + Quetzal + + + + + + An application to manipulate and process XLIFF documents + XLIFF 文書を編集、または処理 するアプリケーションです。 + + + + + XLIFF Data Manager + XLIFF データ・マネージャ + + + + +