Skip to content

fix(csv): detect the dialect when a quoted field spans several lines - #3985

Open
r0h1tb wants to merge 2 commits into
docling-project:mainfrom
r0h1tb:fix/csv-quoted-newline-dialect-sniffing
Open

fix(csv): detect the dialect when a quoted field spans several lines#3985
r0h1tb wants to merge 2 commits into
docling-project:mainfrom
r0h1tb:fix/csv-quoted-newline-dialect-sniffing

Conversation

@r0h1tb

@r0h1tb r0h1tb commented Aug 13, 2026

Copy link
Copy Markdown

Problem

A CSV whose first field is quoted and contains a newline fails to convert:

DocumentConverter(allowed_formats=[InputFormat.CSV]).convert(
    DocumentStream(name="quoted.csv",
                   stream=BytesIO(b'"line one\nstill line one";b;c\n1;2;3\n')),
    raises_on_error=True,
)
# RuntimeError: Pipeline SimplePipeline failed
#   _csv.Error: ',' expected after '"'

convert() sniffs the dialect from self.content.readline(). That read stops at the newline inside the quoted field, so the sniffer gets '"line one' — an unterminated quote — and raises csv.Error. The handler falls back to csv.excel, and reading a semicolon file as comma-delimited under strict=True aborts the conversion.

Fix

Retry the sniff on a larger sample when the first line fails; the sample closes the quote and the dialect is detected.

The first line is still tried first, deliberately. Sniffing the larger sample unconditionally regresses existing fixtures: csv.Sniffer rejects samples whose rows carry different numbers of delimiters (csv-inconsistent-header, csv-too-many-columns), and it also infers quotechar from what it is given — on csv-too-few-columns it picks ' and strips the quotes from a 'b' cell, changing that file's groundtruth. Falling back only on failure leaves every currently-detected dialect untouched.

Tests

test_quoted_newline_in_first_field in tests/test_backend_csv.py. Against unfixed code:

FAILED tests/test_backend_csv.py::test_quoted_newline_in_first_field
  - RuntimeError: Pipeline SimplePipeline failed

tests/test_backend_csv.py goes 3 passed → 4 passed; no groundtruth was regenerated. ruff check and ruff format --check are clean.

Found while reading the backend, so there is no tracking issue. Related but distinct: #1716 is the single-column sniff failure already handled by the comma fallback.

The dialect was sniffed from the first line alone. A quoted field
containing a newline is cut mid-quote by that read, so the sniffer sees
an unterminated quote, raises, and the backend falls back to a comma.
Parsing the file with the wrong dialect under strict=True then aborts
the conversion.

Retry the sniff on a larger sample when the first line fails, which
closes the quote. The first line is still tried first: the sniffer
rejects samples whose rows hold different numbers of delimiters, and it
infers quotechar from the sample too, so widening it unconditionally
changes the dialect detected for ragged files.

Signed-off-by: Rohit Behera <126186063+r0h1tb@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

DCO Check Passed

Thanks @r0h1tb, all your commits are properly signed off. 🎉

@mergify

mergify Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Merge Protections

🟢 Merge protection satisfied — ready to merge.

Show 1 satisfied protection

🟢 Enforce conventional commit

Make sure that we follow https://www.conventionalcommits.org/en/v1.0.0/

  • title ~= ^(fix|feat|docs|style|refactor|perf|test|build|ci|chore|revert)(?:\(.+\))?(!)?:

@ceberam ceberam added csv issue related to csv backend bug Something isn't working labels Aug 13, 2026
@codecov

codecov Bot commented Aug 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@ceberam ceberam left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @r0h1tb for spotting this issue and suggesting this PR.
The fix looks correct to me. Just a couple of comments worth addressing.

Comment thread docling/backend/csv_backend.py Outdated
_log = logging.getLogger(__name__)

# Characters of the file handed to csv.Sniffer to detect the dialect.
_SNIFF_SAMPLE_SIZE: Final[int] = 65536

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sniffer only needs to see one complete quoted field plus its surrounding delimiters. 64 KiB is 64× larger than the stdlib recommendation (1024 bytes) with no benefit. We could set _SNIFF_SAMPLE_SIZE to 4,096 bytes at most, staying in the same order of magnitude as the stdlib default while being clearly sufficient for any realistic multi-line quoted field.

Comment thread docling/backend/csv_backend.py Outdated
# Detect CSV dialect
head = self.content.readline()
self.content.seek(0)
sample = self.content.read(_SNIFF_SAMPLE_SIZE)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor performance comment: the sample is unconditionally read even when the first-line sniff will succeed. It could be avoided by reading the sample lazily, only after the head sniff fails.

Review feedback on docling-project#3985: 4 KiB is plenty for a multi-line quoted field
and the sample is now only read when the first line fails to sniff.

Signed-off-by: Rohit Behera <126186063+r0h1tb@users.noreply.github.com>
@r0h1tb

r0h1tb commented Aug 21, 2026

Copy link
Copy Markdown
Author

Both points addressed in a4f6ba9: sample capped at 4096, and it is now only read when the first-line sniff raises. _sniff_dialect takes a callable so the fallback read stays on the fallback path. CSV suite 13 passed; ruff check and format clean.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working csv issue related to csv backend

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants