merged branch bschussek/issue7609 (PR #7902)

This PR was merged into the 2.2 branch.

Discussion
----------

[Form] Improved multi-byte handling of NumberToLocalizedStringTransformer

| Q             | A
| ------------- | ---
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #7609
| License       | MIT
| Doc PR        | -

Commits
-------

dcced01 [Form] Improved multi-byte handling of NumberToLocalizedStringTransformer
This commit is contained in:
Fabien Potencier 2013-05-03 11:03:51 +02:00
commit e57d87a2f2
2 changed files with 141 additions and 32 deletions

View File

@ -80,6 +80,9 @@ class NumberToLocalizedStringTransformer implements DataTransformerInterface
throw new TransformationFailedException($formatter->getErrorMessage()); throw new TransformationFailedException($formatter->getErrorMessage());
} }
// Convert fixed spaces to normal ones
$value = str_replace("\xc2\xa0", ' ', $value);
return $value; return $value;
} }
@ -130,19 +133,31 @@ class NumberToLocalizedStringTransformer implements DataTransformerInterface
throw new TransformationFailedException('I don\'t have a clear idea what infinity looks like'); throw new TransformationFailedException('I don\'t have a clear idea what infinity looks like');
} }
if (function_exists('mb_detect_encoding') && false !== $encoding = mb_detect_encoding($value)) {
$strlen = function ($string) use ($encoding) {
return mb_strlen($string, $encoding);
};
$substr = function ($string, $offset, $length) use ($encoding) {
return mb_substr($string, $offset, $length, $encoding);
};
} else {
$strlen = 'strlen';
$substr = 'substr';
}
$length = $strlen($value);
// After parsing, position holds the index of the character where the // After parsing, position holds the index of the character where the
// parsing stopped // parsing stopped
if ($position < strlen($value)) { if ($position < $length) {
// Check if there are unrecognized characters at the end of the // Check if there are unrecognized characters at the end of the
// number // number (excluding whitespace characters)
$remainder = substr($value, $position); $remainder = trim($substr($value, $position, $length), " \t\n\r\0\x0b\xc2\xa0");
// Remove all whitespace characters if ('' !== $remainder) {
if ('' !== preg_replace('/[\s\xc2\xa0]*/', '', $remainder)) {
throw new TransformationFailedException( throw new TransformationFailedException(
sprintf('The number contains unrecognized characters: "%s"', sprintf('The number contains unrecognized characters: "%s"', $remainder)
$remainder );
));
} }
} }

View File

@ -22,29 +22,52 @@ class NumberToLocalizedStringTransformerTest extends LocalizedTestCase
\Locale::setDefault('de_AT'); \Locale::setDefault('de_AT');
} }
public function testTransform() public function provideTransformations()
{ {
$transformer = new NumberToLocalizedStringTransformer(); return array(
array(null, '', 'de_AT'),
$this->assertEquals('1', $transformer->transform(1)); array(1, '1', 'de_AT'),
$this->assertEquals('1,5', $transformer->transform(1.5)); array(1.5, '1,5', 'de_AT'),
$this->assertEquals('1234,5', $transformer->transform(1234.5)); array(1234.5, '1234,5', 'de_AT'),
$this->assertEquals('12345,912', $transformer->transform(12345.9123)); array(12345.912, '12345,912', 'de_AT'),
array(1234.5, '1234,5', 'ru'),
array(1234.5, '1234,5', 'fi'),
);
} }
public function testTransformEmpty() /**
* @dataProvider provideTransformations
*/
public function testTransform($from, $to, $locale)
{ {
\Locale::setDefault($locale);
$transformer = new NumberToLocalizedStringTransformer(); $transformer = new NumberToLocalizedStringTransformer();
$this->assertSame('', $transformer->transform(null)); $this->assertSame($to, $transformer->transform($from));
} }
public function testTransformWithGrouping() public function provideTransformationsWithGrouping()
{ {
return array(
array(1234.5, '1.234,5', 'de_AT'),
array(12345.912, '12.345,912', 'de_AT'),
array(1234.5, '1 234,5', 'fr'),
array(1234.5, '1 234,5', 'ru'),
array(1234.5, '1 234,5', 'fi'),
);
}
/**
* @dataProvider provideTransformationsWithGrouping
*/
public function testTransformWithGrouping($from, $to, $locale)
{
\Locale::setDefault($locale);
$transformer = new NumberToLocalizedStringTransformer(null, true); $transformer = new NumberToLocalizedStringTransformer(null, true);
$this->assertEquals('1.234,5', $transformer->transform(1234.5)); $this->assertSame($to, $transformer->transform($from));
$this->assertEquals('12.345,912', $transformer->transform(12345.9123));
} }
public function testTransformWithPrecision() public function testTransformWithPrecision()
@ -65,30 +88,48 @@ class NumberToLocalizedStringTransformerTest extends LocalizedTestCase
} }
public function testReverseTransform() /**
* @dataProvider provideTransformations
*/
public function testReverseTransform($to, $from, $locale)
{ {
\Locale::setDefault($locale);
$transformer = new NumberToLocalizedStringTransformer(); $transformer = new NumberToLocalizedStringTransformer();
$this->assertEquals(1, $transformer->reverseTransform('1')); $this->assertEquals($to, $transformer->reverseTransform($from));
$this->assertEquals(1.5, $transformer->reverseTransform('1,5'));
$this->assertEquals(1234.5, $transformer->reverseTransform('1234,5'));
$this->assertEquals(12345.912, $transformer->reverseTransform('12345,912'));
} }
public function testReverseTransformEmpty() /**
* @dataProvider provideTransformationsWithGrouping
*/
public function testReverseTransformWithGrouping($to, $from, $locale)
{ {
$transformer = new NumberToLocalizedStringTransformer(); \Locale::setDefault($locale);
$this->assertNull($transformer->reverseTransform('')); $transformer = new NumberToLocalizedStringTransformer(null, true);
$this->assertEquals($to, $transformer->reverseTransform($from));
} }
public function testReverseTransformWithGrouping() // https://github.com/symfony/symfony/issues/7609
public function testReverseTransformWithGroupingAndFixedSpaces()
{
if (!extension_loaded('mbstring')) {
$this->markTestSkipped('The "mbstring" extension is required for this test.');
}
\Locale::setDefault('ru');
$transformer = new NumberToLocalizedStringTransformer(null, true);
$this->assertEquals(1234.5, $transformer->reverseTransform("1\xc2\xa0234,5"));
}
public function testReverseTransformWithGroupingButWithoutGroupSeparator()
{ {
$transformer = new NumberToLocalizedStringTransformer(null, true); $transformer = new NumberToLocalizedStringTransformer(null, true);
// completely valid format
$this->assertEquals(1234.5, $transformer->reverseTransform('1.234,5'));
$this->assertEquals(12345.912, $transformer->reverseTransform('12.345,912'));
// omit group separator // omit group separator
$this->assertEquals(1234.5, $transformer->reverseTransform('1234,5')); $this->assertEquals(1234.5, $transformer->reverseTransform('1234,5'));
$this->assertEquals(12345.912, $transformer->reverseTransform('12345,912')); $this->assertEquals(12345.912, $transformer->reverseTransform('12345,912'));
@ -299,6 +340,7 @@ class NumberToLocalizedStringTransformerTest extends LocalizedTestCase
/** /**
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
* @expectedExceptionMessage The number contains unrecognized characters: "foo3"
*/ */
public function testReverseTransformDisallowsCenteredExtraCharacters() public function testReverseTransformDisallowsCenteredExtraCharacters()
{ {
@ -309,6 +351,41 @@ class NumberToLocalizedStringTransformerTest extends LocalizedTestCase
/** /**
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
* @expectedExceptionMessage The number contains unrecognized characters: "foo8"
*/
public function testReverseTransformDisallowsCenteredExtraCharactersMultibyte()
{
if (!extension_loaded('mbstring')) {
$this->markTestSkipped('The "mbstring" extension is required for this test.');
}
\Locale::setDefault('ru');
$transformer = new NumberToLocalizedStringTransformer(null, true);
$transformer->reverseTransform("12\xc2\xa0345,67foo8");
}
/**
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
* @expectedExceptionMessage The number contains unrecognized characters: "foo8"
*/
public function testReverseTransformIgnoresTrailingSpacesInExceptionMessage()
{
if (!extension_loaded('mbstring')) {
$this->markTestSkipped('The "mbstring" extension is required for this test.');
}
\Locale::setDefault('ru');
$transformer = new NumberToLocalizedStringTransformer(null, true);
$transformer->reverseTransform("12\xc2\xa0345,67foo8 \xc2\xa0\t");
}
/**
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
* @expectedExceptionMessage The number contains unrecognized characters: "foo"
*/ */
public function testReverseTransformDisallowsTrailingExtraCharacters() public function testReverseTransformDisallowsTrailingExtraCharacters()
{ {
@ -316,4 +393,21 @@ class NumberToLocalizedStringTransformerTest extends LocalizedTestCase
$transformer->reverseTransform('123foo'); $transformer->reverseTransform('123foo');
} }
/**
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
* @expectedExceptionMessage The number contains unrecognized characters: "foo"
*/
public function testReverseTransformDisallowsTrailingExtraCharactersMultibyte()
{
if (!extension_loaded('mbstring')) {
$this->markTestSkipped('The "mbstring" extension is required for this test.');
}
\Locale::setDefault('ru');
$transformer = new NumberToLocalizedStringTransformer(null, true);
$transformer->reverseTransform("12\xc2\xa0345,678foo");
}
} }