From ae980a18f73b88b3394510e07ed0f343f252ca4f Mon Sep 17 00:00:00 2001 From: Gennady Telegin Date: Wed, 22 Apr 2015 00:28:17 +0300 Subject: [PATCH] [Translation] add options 'as_tree', 'inline' to YamlFileDumper to dump messages as tree instead of simple list. Dump messages as tree based on '.' character as a delimeter in path. For example this rray('foo.bar' => 'value') will be converted to array('foo' => array('bar' => 'value')). Correctly process cases like this ['foo.bar' => 'test1', 'foo' => 'test2']. --- CHANGELOG.md | 1 + Dumper/FileDumper.php | 18 +++++- Dumper/YamlFileDumper.php | 23 ++++++- Tests/Dumper/YamlFileDumperTest.php | 28 +++++++- Tests/Util/ArrayConverterTest.php | 73 +++++++++++++++++++++ Tests/fixtures/messages.yml | 3 + Tests/fixtures/messages_linear.yml | 2 + Util/ArrayConverter.php | 99 +++++++++++++++++++++++++++++ 8 files changed, 241 insertions(+), 6 deletions(-) create mode 100644 Tests/Util/ArrayConverterTest.php create mode 100644 Tests/fixtures/messages.yml create mode 100644 Tests/fixtures/messages_linear.yml create mode 100644 Util/ArrayConverter.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 64ce8aba..8b2661e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG ----- * deprecated Translator::getMessages(), use TranslatorBagInterface::getCatalogue() instead. + * added options 'as_tree', 'inline' to YamlFileDumper 2.7.0 ----- diff --git a/Dumper/FileDumper.php b/Dumper/FileDumper.php index f2f17d64..7620526f 100644 --- a/Dumper/FileDumper.php +++ b/Dumper/FileDumper.php @@ -82,10 +82,26 @@ public function dump(MessageCatalogue $messages, $options = array()) } } // save file - file_put_contents($fullpath, $this->format($messages, $domain)); + file_put_contents($fullpath, $this->formatCatalogue($messages, $domain, $options)); } } + /** + * Transforms a domain of a message catalogue to its string representation. + * + * Override this function in child class if $options is used for message formatting. + * + * @param MessageCatalogue $messages + * @param string $domain + * @param array $options + * + * @return string representation + */ + protected function formatCatalogue(MessageCatalogue $messages, $domain, array $options = array()) + { + return $this->format($messages, $domain); + } + /** * Transforms a domain of a message catalogue to its string representation. * diff --git a/Dumper/YamlFileDumper.php b/Dumper/YamlFileDumper.php index 870fb983..e27f8b37 100644 --- a/Dumper/YamlFileDumper.php +++ b/Dumper/YamlFileDumper.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Translation\Dumper; use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\Util\ArrayConverter; use Symfony\Component\Yaml\Yaml; /** @@ -24,13 +25,31 @@ class YamlFileDumper extends FileDumper /** * {@inheritdoc} */ - protected function format(MessageCatalogue $messages, $domain) + protected function formatCatalogue(MessageCatalogue $messages, $domain, array $options = array()) { if (!class_exists('Symfony\Component\Yaml\Yaml')) { throw new \LogicException('Dumping translations in the YAML format requires the Symfony Yaml component.'); } - return Yaml::dump($messages->all($domain)); + $data = $messages->all($domain); + + if (isset($options['as_tree']) && $options['as_tree']) { + $data = ArrayConverter::expandToTree($data); + } + + if (isset($options['inline']) && ($inline = (int) $options['inline']) > 0) { + return Yaml::dump($data, $inline); + } + + return Yaml::dump($data); + } + + /** + * {@inheritdoc} + */ + protected function format(MessageCatalogue $messages, $domain) + { + return $this->formatCatalogue($messages, $domain); } /** diff --git a/Tests/Dumper/YamlFileDumperTest.php b/Tests/Dumper/YamlFileDumperTest.php index 3c68ade7..161925cd 100644 --- a/Tests/Dumper/YamlFileDumperTest.php +++ b/Tests/Dumper/YamlFileDumperTest.php @@ -16,16 +16,38 @@ class YamlFileDumperTest extends \PHPUnit_Framework_TestCase { - public function testDump() + public function testTreeDump() { $catalogue = new MessageCatalogue('en'); - $catalogue->add(array('foo' => 'bar')); + $catalogue->add( + array( + 'foo.bar1' => 'value1', + 'foo.bar2' => 'value2', + )); + + $tempDir = sys_get_temp_dir(); + $dumper = new YamlFileDumper(); + $dumper->dump($catalogue, array('path' => $tempDir, 'as_tree' => true, 'inline' => 999)); + + $this->assertEquals(file_get_contents(__DIR__.'/../fixtures/messages.yml'), file_get_contents($tempDir.'/messages.en.yml')); + + unlink($tempDir.'/messages.en.yml'); + } + + public function testLinearDump() + { + $catalogue = new MessageCatalogue('en'); + $catalogue->add( + array( + 'foo.bar1' => 'value1', + 'foo.bar2' => 'value2', + )); $tempDir = sys_get_temp_dir(); $dumper = new YamlFileDumper(); $dumper->dump($catalogue, array('path' => $tempDir)); - $this->assertEquals(file_get_contents(__DIR__.'/../fixtures/resources.yml'), file_get_contents($tempDir.'/messages.en.yml')); + $this->assertEquals(file_get_contents(__DIR__.'/../fixtures/messages_linear.yml'), file_get_contents($tempDir.'/messages.en.yml')); unlink($tempDir.'/messages.en.yml'); } diff --git a/Tests/Util/ArrayConverterTest.php b/Tests/Util/ArrayConverterTest.php new file mode 100644 index 00000000..9eea275b --- /dev/null +++ b/Tests/Util/ArrayConverterTest.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Tests\Util; + +use Symfony\Component\Translation\Util\ArrayConverter; + +class ArrayConverterTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider messsagesData + */ + public function testDump($input, $expectedOutput) + { + $this->assertEquals($expectedOutput, ArrayConverter::expandToTree($input)); + } + + public function messsagesData() + { + return array( + array( + // input + array( + 'foo1' => 'bar', + 'foo.bar' => 'value', + ), + // expected output + array( + 'foo1' => 'bar', + 'foo' => array('bar' => 'value'), + ), + ), + array( + // input + array( + 'foo.bar' => 'value1', + 'foo.bar.test' => 'value2', + ), + // expected output + array( + 'foo' => array( + 'bar' => 'value1', + 'bar.test' => 'value2', + ), + ), + ), + array( + // input + array( + 'foo.level2.level3.level4' => 'value1', + 'foo.level2' => 'value2', + 'foo.bar' => 'value3', + ), + // expected output + array( + 'foo' => array( + 'level2' => 'value2', + 'level2.level3.level4' => 'value1', + 'bar' => 'value3', + ), + ), + ), + ); + } +} diff --git a/Tests/fixtures/messages.yml b/Tests/fixtures/messages.yml new file mode 100644 index 00000000..d4f82d78 --- /dev/null +++ b/Tests/fixtures/messages.yml @@ -0,0 +1,3 @@ +foo: + bar1: value1 + bar2: value2 diff --git a/Tests/fixtures/messages_linear.yml b/Tests/fixtures/messages_linear.yml new file mode 100644 index 00000000..6c1687d0 --- /dev/null +++ b/Tests/fixtures/messages_linear.yml @@ -0,0 +1,2 @@ +foo.bar1: value1 +foo.bar2: value2 diff --git a/Util/ArrayConverter.php b/Util/ArrayConverter.php new file mode 100644 index 00000000..60a55e9d --- /dev/null +++ b/Util/ArrayConverter.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Util; + +/** + * ArrayConverter generates tree like structure from a message catalogue. + * e.g. this + * 'foo.bar1' => 'test1', + * 'foo.bar2' => 'test2' + * converts to follows: + * foo: + * bar1: test1 + * bar2: test2. + * + * @author Gennady Telegin + */ +class ArrayConverter +{ + /** + * Converts linear messages array to tree-like array. + * For example this rray('foo.bar' => 'value') will be converted to array('foo' => array('bar' => 'value')). + * + * @param array $messages Linear messages array + * + * @return array Tree-like messages array + */ + public static function expandToTree(array $messages) + { + $tree = array(); + + foreach ($messages as $id => $value) { + $referenceToElement = &self::getElementByPath($tree, explode('.', $id)); + + $referenceToElement = $value; + + unset($referenceToElement); + } + + return $tree; + } + + private static function &getElementByPath(array &$tree, array $parts) + { + $elem = &$tree; + $parentOfElem = null; + + foreach ($parts as $i => $part) { + if (isset($elem[$part]) && is_string($elem[$part])) { + /* Process next case: + * 'foo': 'test1', + * 'foo.bar': 'test2' + * + * $tree['foo'] was string before we found array {bar: test2}. + * Treat new element as string too, e.g. add $tree['foo.bar'] = 'test2'; + */ + $elem = &$elem[ implode('.', array_slice($parts, $i)) ]; + break; + } + $parentOfElem = &$elem; + $elem = &$elem[$part]; + } + + if (is_array($elem) && count($elem) > 0 && $parentOfElem) { + /* Process next case: + * 'foo.bar': 'test1' + * 'foo': 'test2' + * + * $tree['foo'] was array = {bar: 'test1'} before we found string constant `foo`. + * Cancel treating $tree['foo'] as array and cancel back it expansion, + * e.g. make it $tree['foo.bar'] = 'test1' again. + */ + self::cancelExpand($parentOfElem, $part, $elem); + } + + return $elem; + } + + private static function cancelExpand(array &$tree, $prefix, array $node) + { + $prefix .= '.'; + + foreach ($node as $id => $value) { + if (is_string($value)) { + $tree[$prefix.$id] = $value; + } else { + self::cancelExpand($tree, $prefix.$id, $value); + } + } + } +}