Browse Source

Fixed #35816 -- Handled parsing of scientific notation in DTL. (#19213)

* Refs #35816 -- Improved test coverage of FilterExpression.

* Fixed #35816 -- Made FilterExpression parse scientific numbers.

---------

Co-authored-by: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com>
haileyajohnson 1 week ago
parent
commit
5183f7c287
2 changed files with 71 additions and 4 deletions
  1. 3 4
      django/template/base.py
  2. 68 0
      tests/template_tests/test_parser.py

+ 3 - 4
django/template/base.py

@@ -633,19 +633,18 @@ constant_string = constant_string.replace("\n", "")
 
 filter_raw_string = r"""
 ^(?P<constant>%(constant)s)|
-^(?P<var>[%(var_chars)s]+|%(num)s)|
+^(?P<var>[%(var_chars)s]+)|
  (?:\s*%(filter_sep)s\s*
      (?P<filter_name>\w+)
          (?:%(arg_sep)s
              (?:
               (?P<constant_arg>%(constant)s)|
-              (?P<var_arg>[%(var_chars)s]+|%(num)s)
+              (?P<var_arg>[%(var_chars)s]+)
              )
          )?
  )""" % {
     "constant": constant_string,
-    "num": r"[-+.]?\d[\d.e]*",
-    "var_chars": r"\w\.",
+    "var_chars": r"\w\.\+-",
     "filter_sep": re.escape(FILTER_SEPARATOR),
     "arg_sep": re.escape(FILTER_ARGUMENT_SEPARATOR),
 }

+ 68 - 0
tests/template_tests/test_parser.py

@@ -11,6 +11,7 @@ from django.template.base import (
     Token,
     TokenType,
     Variable,
+    VariableDoesNotExist,
 )
 from django.template.defaultfilters import register as filter_library
 from django.test import SimpleTestCase
@@ -72,6 +73,27 @@ class ParserTests(SimpleTestCase):
         with self.assertRaisesMessage(TemplateSyntaxError, msg):
             FilterExpression("article._hidden|upper", p)
 
+    def test_cannot_parse_characters(self):
+        p = Parser("", builtins=[filter_library])
+        for filter_expression, characters in [
+            ('<>|default:"Default"|upper', '|<>||default:"Default"|upper'),
+            ("test|<>|upper", "test||<>||upper"),
+        ]:
+            with self.subTest(filter_expression=filter_expression):
+                with self.assertRaisesMessage(
+                    TemplateSyntaxError,
+                    f"Could not parse some characters: {characters}",
+                ):
+                    FilterExpression(filter_expression, p)
+
+    def test_cannot_find_variable(self):
+        p = Parser("", builtins=[filter_library])
+        with self.assertRaisesMessage(
+            TemplateSyntaxError,
+            'Could not find variable at start of |default:"Default"',
+        ):
+            FilterExpression('|default:"Default"', p)
+
     def test_variable_parsing(self):
         c = {"article": {"section": "News"}}
         self.assertEqual(Variable("article.section").resolve(c), "News")
@@ -148,3 +170,49 @@ class ParserTests(SimpleTestCase):
             '1|two_one_opt_arg:"1"',
         ):
             FilterExpression(expr, parser)
+
+    def test_filter_numeric_argument_parsing(self):
+        p = Parser("", builtins=[filter_library])
+
+        cases = {
+            "5": 5,
+            "-5": -5,
+            "5.2": 5.2,
+            ".4": 0.4,
+            "5.2e3": 5200.0,  # 5.2 × 10³ = 5200.0.
+            "5.2E3": 5200.0,  # Case-insensitive.
+            "5.2e-3": 0.0052,  # Negative exponent.
+            "-1.5E4": -15000.0,
+            "+3.0e2": 300.0,
+            ".5e2": 50.0,  # 0.5 × 10² = 50.0
+        }
+        for num, expected in cases.items():
+            with self.subTest(num=num):
+                self.assertEqual(FilterExpression(num, p).resolve({}), expected)
+                self.assertEqual(
+                    FilterExpression(f"0|default:{num}", p).resolve({}), expected
+                )
+
+        invalid_numbers = [
+            "abc123",
+            "123abc",
+            "foo",
+            "error",
+            "1e",
+            "e400",
+            "1e.2",
+            "1e2.",
+            "1e2.0",
+            "1e2a",
+            "1e2e3",
+            "1e-",
+            "1e-a",
+        ]
+
+        for num in invalid_numbers:
+            with self.subTest(num=num):
+                self.assertIsNone(
+                    FilterExpression(num, p).resolve({}, ignore_failures=True)
+                )
+                with self.assertRaises(VariableDoesNotExist):
+                    FilterExpression(f"0|default:{num}", p).resolve({})