Skip to content

Commit d3c62be

Browse files
committed
gh-109638: Keep space-delimiter matching linear
1 parent c62c513 commit d3c62be

2 files changed

Lines changed: 16 additions & 1 deletion

File tree

Lib/csv.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,8 +352,10 @@ def _detect_doublequote(self, data, delimiter, quotechar):
352352
escaped_quote = re.escape(quotechar)
353353
values = {'delim': escaped_delimiter, 'quote': escaped_quote}
354354
if delimiter:
355+
# Spaces after a space delimiter are delimiters, not padding.
356+
values['space'] = '' if delimiter == ' ' else ' *+'
355357
candidate = re.compile(
356-
r"(?:%(delim)s|\r|^) *+%(quote)s"
358+
r"(?:%(delim)s|\r|^)%(space)s%(quote)s"
357359
r"[^%(quote)s]*+%(quote)s%(quote)s"
358360
r"(?:%(quote)s%(quote)s|[^%(quote)s]++)*+"
359361
r"%(quote)s(?:%(delim)s|(?=\r)|$)"

Lib/test/test_csv.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1507,6 +1507,19 @@ def test_sniff_regex_backtracking(self):
15071507
sample = '"",' * 100 + '"' * 100 + '0' + '"' * 100 + '0'
15081508
self.assertEqual(sniffer.sniff(sample).delimiter, ',')
15091509

1510+
def test_sniff_space_delimiter(self):
1511+
# This sample used to be quadratic.
1512+
sniffer = csv.Sniffer()
1513+
sample = '"a" "b"\n' + ' ' * 100_000
1514+
dialect = sniffer.sniff(sample)
1515+
self.assertEqual(dialect.delimiter, ' ')
1516+
self.assertIs(dialect.doublequote, False)
1517+
1518+
# A quoted field can still start after multiple space delimiters.
1519+
dialect = sniffer.sniff('"a" "b""c"')
1520+
self.assertEqual(dialect.delimiter, ' ')
1521+
self.assertIs(dialect.doublequote, True)
1522+
15101523
def test_doublequote(self):
15111524
sniffer = csv.Sniffer()
15121525
dialect = sniffer.sniff(self.header1)

0 commit comments

Comments
 (0)