[Form] Improved multi-byte handling of NumberToLocalizedStringTransformer
This commit is contained in:
parent
9f02b05997
commit
dcced01fd5
@ -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
|
);
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user