Skip to content

Commit 42459c9

Browse files
miss-islingtonlilabocserhiy-storchakaclaude
authored
[3.14] gh-103925: Fix csv.Sniffer for a quoted field ending a CRLF line (GH-103926) (GH-154323)
"$" does not match before "\r" even in the MULTILINE mode, so such a field was not found and the delimiter was guessed from character frequencies instead, which could give a letter. (cherry picked from commit 70f7c6c) Co-authored-by: Zhou Wei <lilaboc.cn@gmail.com> Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 856c811 commit 42459c9

3 files changed

Lines changed: 13 additions & 4 deletions

File tree

Lib/csv.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -283,10 +283,10 @@ def _guess_quote_and_delimiter(self, data, delimiters):
283283
import re
284284

285285
matches = []
286-
for restr in (r'(?P<delim>[^\w\n"\'])(?P<space> ?)(?P<quote>["\']).*?(?P=quote)(?P=delim)', # ,".*?",
287-
r'(?:^|\n)(?P<quote>["\']).*?(?P=quote)(?P<delim>[^\w\n"\'])(?P<space> ?)', # ".*?",
288-
r'(?P<delim>[^\w\n"\'])(?P<space> ?)(?P<quote>["\']).*?(?P=quote)(?:$|\n)', # ,".*?"
289-
r'(?:^|\n)(?P<quote>["\']).*?(?P=quote)(?:$|\n)'): # ".*?" (no delim, no space)
286+
for restr in (r'(?P<delim>[^\w\n"\'])(?P<space> ?)(?P<quote>["\']).*?(?P=quote)(?P=delim)', # ,".*?",
287+
r'(?:^|\n)(?P<quote>["\']).*?(?P=quote)(?P<delim>[^\w\n"\'])(?P<space> ?)', # ".*?",
288+
r'(?P<delim>[^\w\n"\'])(?P<space> ?)(?P<quote>["\']).*?(?P=quote)(?:$|\r|\n)', # ,".*?"
289+
r'(?:^|\n)(?P<quote>["\']).*?(?P=quote)(?:$|\r|\n)'): # ".*?" (no delim, no space)
290290
regexp = re.compile(restr, re.DOTALL | re.MULTILINE)
291291
matches = regexp.findall(data)
292292
if matches:

Lib/test/test_csv.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1402,6 +1402,9 @@ class TestSniffer(unittest.TestCase):
14021402
sample18.append("v,twenty_one") # 'u' was not skipped
14031403
sample18 = '\n'.join(sample18)
14041404

1405+
sample19 = ('time,title\r\n'
1406+
'2020-10-01,"Pocket - Save news, videos, stories and more"\r\n')
1407+
14051408
def test_issue43625(self):
14061409
sniffer = csv.Sniffer()
14071410
self.assertTrue(sniffer.has_header(self.sample12))
@@ -1481,6 +1484,9 @@ def test_delimiters(self):
14811484
sniffer.sniff, self.sample15)
14821485
self.assertRaisesRegex(csv.Error, "Could not determine delimiter",
14831486
sniffer.sniff, self.sample16)
1487+
dialect = sniffer.sniff(self.sample19)
1488+
self.assertEqual(dialect.delimiter, ',')
1489+
self.assertEqual(dialect.quotechar, '"')
14841490

14851491
def test_doublequote(self):
14861492
sniffer = csv.Sniffer()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix :meth:`csv.Sniffer.sniff` for a sample with ``\r\n`` line endings in
2+
which a quoted field ends a line: a letter could be detected as the
3+
delimiter.

0 commit comments

Comments
 (0)