mirror of
https://github.com/python/cpython.git
synced 2025-08-11 12:29:34 +00:00
[3.12] gh-67044: Always quote or escape \r and \n in csv.writer() (GH-115741) (GH-115866)
(cherry picked from commit c688c0f130
)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
This commit is contained in:
parent
10907bdad3
commit
4ac657a62f
3 changed files with 43 additions and 15 deletions
|
@ -240,9 +240,11 @@ class Test_Csv(unittest.TestCase):
|
||||||
writer = csv.writer(sio, lineterminator=lineterminator)
|
writer = csv.writer(sio, lineterminator=lineterminator)
|
||||||
writer.writerow(['a', 'b'])
|
writer.writerow(['a', 'b'])
|
||||||
writer.writerow([1, 2])
|
writer.writerow([1, 2])
|
||||||
|
writer.writerow(['\r', '\n'])
|
||||||
self.assertEqual(sio.getvalue(),
|
self.assertEqual(sio.getvalue(),
|
||||||
f'a,b{lineterminator}'
|
f'a,b{lineterminator}'
|
||||||
f'1,2{lineterminator}')
|
f'1,2{lineterminator}'
|
||||||
|
f'"\r","\n"{lineterminator}')
|
||||||
|
|
||||||
def test_write_iterable(self):
|
def test_write_iterable(self):
|
||||||
self._write_test(iter(['a', 1, 'p,q']), 'a,1,"p,q"')
|
self._write_test(iter(['a', 1, 'p,q']), 'a,1,"p,q"')
|
||||||
|
@ -469,22 +471,44 @@ class Test_Csv(unittest.TestCase):
|
||||||
self.assertEqual(r.line_num, 3)
|
self.assertEqual(r.line_num, 3)
|
||||||
|
|
||||||
def test_roundtrip_quoteed_newlines(self):
|
def test_roundtrip_quoteed_newlines(self):
|
||||||
with TemporaryFile("w+", encoding="utf-8", newline='') as fileobj:
|
rows = [
|
||||||
writer = csv.writer(fileobj)
|
['\na', 'b\nc', 'd\n'],
|
||||||
rows = [['a\nb','b'],['c','x\r\nd']]
|
['\re', 'f\rg', 'h\r'],
|
||||||
writer.writerows(rows)
|
['\r\ni', 'j\r\nk', 'l\r\n'],
|
||||||
fileobj.seek(0)
|
['\n\rm', 'n\n\ro', 'p\n\r'],
|
||||||
for i, row in enumerate(csv.reader(fileobj)):
|
['\r\rq', 'r\r\rs', 't\r\r'],
|
||||||
self.assertEqual(row, rows[i])
|
['\n\nu', 'v\n\nw', 'x\n\n'],
|
||||||
|
]
|
||||||
|
for lineterminator in '\r\n', '\n', '\r':
|
||||||
|
with self.subTest(lineterminator=lineterminator):
|
||||||
|
with TemporaryFile("w+", encoding="utf-8", newline='') as fileobj:
|
||||||
|
writer = csv.writer(fileobj, lineterminator=lineterminator)
|
||||||
|
writer.writerows(rows)
|
||||||
|
fileobj.seek(0)
|
||||||
|
for i, row in enumerate(csv.reader(fileobj)):
|
||||||
|
self.assertEqual(row, rows[i])
|
||||||
|
|
||||||
def test_roundtrip_escaped_unquoted_newlines(self):
|
def test_roundtrip_escaped_unquoted_newlines(self):
|
||||||
with TemporaryFile("w+", encoding="utf-8", newline='') as fileobj:
|
rows = [
|
||||||
writer = csv.writer(fileobj,quoting=csv.QUOTE_NONE,escapechar="\\")
|
['\na', 'b\nc', 'd\n'],
|
||||||
rows = [['a\nb','b'],['c','x\r\nd']]
|
['\re', 'f\rg', 'h\r'],
|
||||||
writer.writerows(rows)
|
['\r\ni', 'j\r\nk', 'l\r\n'],
|
||||||
fileobj.seek(0)
|
['\n\rm', 'n\n\ro', 'p\n\r'],
|
||||||
for i, row in enumerate(csv.reader(fileobj,quoting=csv.QUOTE_NONE,escapechar="\\")):
|
['\r\rq', 'r\r\rs', 't\r\r'],
|
||||||
self.assertEqual(row,rows[i])
|
['\n\nu', 'v\n\nw', 'x\n\n'],
|
||||||
|
]
|
||||||
|
for lineterminator in '\r\n', '\n', '\r':
|
||||||
|
with self.subTest(lineterminator=lineterminator):
|
||||||
|
with TemporaryFile("w+", encoding="utf-8", newline='') as fileobj:
|
||||||
|
writer = csv.writer(fileobj, lineterminator=lineterminator,
|
||||||
|
quoting=csv.QUOTE_NONE, escapechar="\\")
|
||||||
|
writer.writerows(rows)
|
||||||
|
fileobj.seek(0)
|
||||||
|
for i, row in enumerate(csv.reader(fileobj,
|
||||||
|
quoting=csv.QUOTE_NONE,
|
||||||
|
escapechar="\\")):
|
||||||
|
self.assertEqual(row, rows[i])
|
||||||
|
|
||||||
|
|
||||||
class TestDialectRegistry(unittest.TestCase):
|
class TestDialectRegistry(unittest.TestCase):
|
||||||
def test_registry_badargs(self):
|
def test_registry_badargs(self):
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
:func:`csv.writer` now always quotes or escapes ``'\r'`` and ``'\n'``,
|
||||||
|
regardless of *lineterminator* value.
|
|
@ -1109,6 +1109,8 @@ join_append_data(WriterObj *self, int field_kind, const void *field_data,
|
||||||
if (c == dialect->delimiter ||
|
if (c == dialect->delimiter ||
|
||||||
c == dialect->escapechar ||
|
c == dialect->escapechar ||
|
||||||
c == dialect->quotechar ||
|
c == dialect->quotechar ||
|
||||||
|
c == '\n' ||
|
||||||
|
c == '\r' ||
|
||||||
PyUnicode_FindChar(
|
PyUnicode_FindChar(
|
||||||
dialect->lineterminator, c, 0,
|
dialect->lineterminator, c, 0,
|
||||||
PyUnicode_GET_LENGTH(dialect->lineterminator), 1) >= 0) {
|
PyUnicode_GET_LENGTH(dialect->lineterminator), 1) >= 0) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue