diff --git a/babel/numbers.py b/babel/numbers.py index b782521e5..5588ef44f 100644 --- a/babel/numbers.py +++ b/babel/numbers.py @@ -1306,10 +1306,16 @@ def _match_number(pattern): return rv.groups() pos_pattern = pattern + neg_pattern = None - # Do we have a negative subpattern? - if ';' in pattern: - pos_pattern, neg_pattern = pattern.split(';', 1) + # Only an unquoted semicolon separates the negative subpattern. + for match in re.finditer(r"'[^']*'|;", pattern): + if match.group() == ';': + pos_pattern = pattern[:match.start()] + neg_pattern = pattern[match.end():] + break + + if neg_pattern is not None: pos_prefix, number, pos_suffix = _match_number(pos_pattern) neg_prefix, _, neg_suffix = _match_number(neg_pattern) else: diff --git a/tests/test_numbers.py b/tests/test_numbers.py index d20a18315..20252f54f 100644 --- a/tests/test_numbers.py +++ b/tests/test_numbers.py @@ -660,6 +660,24 @@ def test_single_quotes_in_pattern(): assert numbers.format_decimal(12, "'#'0 o''clock", locale='en') == "#12 o'clock" +@pytest.mark.parametrize( + ('pattern', 'positive', 'negative'), + [ + ("0';'", '12;', '-12;'), + ("';'0", ';12', '-;12'), + ("0'items; total'", '12items; total', '-12items; total'), + ("0';';(0';')", '12;', '(12;)'), + ("';'0;';-'0", ';12', ';-12'), + ("0''';'", "12';", "-12';"), + ("0'';(0'')", "12'", "(12')"), + ('0;(0)', '12', '(12)'), + ], +) +def test_quoted_semicolons_in_pattern(pattern, positive, negative): + assert numbers.format_decimal(12, pattern, locale='en') == positive + assert numbers.format_decimal(-12, pattern, locale='en') == negative + + def test_format_currency_with_none_locale_with_default(monkeypatch): """Test that the default locale is used when locale is None.""" monkeypatch.setattr(numbers, "LC_MONETARY", "fi_FI")