diff options
-rw-r--r-- | babel/numbers.py | 25 | ||||
-rw-r--r-- | tests/test_numbers.py | 19 |
2 files changed, 41 insertions, 3 deletions
diff --git a/babel/numbers.py b/babel/numbers.py index 518d494..509150c 100644 --- a/babel/numbers.py +++ b/babel/numbers.py @@ -661,7 +661,7 @@ def parse_number(string, locale=LC_NUMERIC): raise NumberFormatError('%r is not a valid number' % string) -def parse_decimal(string, locale=LC_NUMERIC): +def parse_decimal(string, locale=LC_NUMERIC, strict=False): """Parse localized decimal string into a decimal. >>> parse_decimal('1,099.98', locale='en_US') @@ -676,17 +676,36 @@ def parse_decimal(string, locale=LC_NUMERIC): ... NumberFormatError: '2,109,998' is not a valid decimal number + If `strict` is set to `True` and the given string contains a number + formatted in an irregular way, an exception is raised: + + >>> parse_decimal('30.00', locale='de', strict=True) + Traceback (most recent call last): + ... + NumberFormatError: '30.00' is not a properly formatted decimal number + :param string: the string to parse :param locale: the `Locale` object or locale identifier + :param strict: controls whether numbers formatted in a weird way are + accepted or rejected :raise NumberFormatError: if the string can not be converted to a decimal number """ locale = Locale.parse(locale) + group_symbol = get_group_symbol(locale) + decimal_symbol = get_decimal_symbol(locale) try: - return decimal.Decimal(string.replace(get_group_symbol(locale), '') - .replace(get_decimal_symbol(locale), '.')) + parsed = decimal.Decimal(string.replace(group_symbol, '') + .replace(decimal_symbol, '.')) except decimal.InvalidOperation: raise NumberFormatError('%r is not a valid decimal number' % string) + if strict and group_symbol in string: + proper = format_decimal(parsed, locale=locale, decimal_quantization=False) + if string != proper and string.rstrip('0') != (proper + decimal_symbol): + raise NumberFormatError( + "%r is not a properly formatted decimal number" % string + ) + return parsed PREFIX_END = r'[^0-9@#.,]' diff --git a/tests/test_numbers.py b/tests/test_numbers.py index 32f4280..50c53de 100644 --- a/tests/test_numbers.py +++ b/tests/test_numbers.py @@ -165,6 +165,25 @@ class NumberParsingTestCase(unittest.TestCase): self.assertRaises(numbers.NumberFormatError, lambda: numbers.parse_decimal('2,109,998', locale='de')) + def test_parse_decimal_strict_mode(self): + # Numbers with a misplaced grouping symbol should be rejected + with self.assertRaises(numbers.NumberFormatError): + numbers.parse_decimal('11.11', locale='de', strict=True) + # Partially grouped numbers should be rejected + with self.assertRaises(numbers.NumberFormatError): + numbers.parse_decimal('2000,000', locale='en_US', strict=True) + # Numbers with duplicate grouping symbols should be rejected + with self.assertRaises(numbers.NumberFormatError): + numbers.parse_decimal('0,,000', locale='en_US', strict=True) + # Properly formatted numbers should be accepted + assert str(numbers.parse_decimal('1.001', locale='de', strict=True)) == '1001' + # Trailing zeroes should be accepted + assert str(numbers.parse_decimal('3.00', locale='en_US', strict=True)) == '3.00' + # Numbers without any grouping symbol should be accepted + assert str(numbers.parse_decimal('2000.1', locale='en_US', strict=True)) == '2000.1' + # High precision numbers should be accepted + assert str(numbers.parse_decimal('5,000001', locale='fr', strict=True)) == '5.000001' + def test_list_currencies(): assert isinstance(list_currencies(), set) |