Skip to content

feat(csv): probe for a dialect instead of checking for one - #666

Open
andiwand wants to merge 1 commit into
mainfrom
feat/csv-probe
Open

feat(csv): probe for a dialect instead of checking for one#666
andiwand wants to merge 1 commit into
mainfrom
feat/csv-probe

Conversation

@andiwand

@andiwand andiwand commented Aug 9, 2026

Copy link
Copy Markdown
Member

🤖 Generated with Claude Code

Second of five stacked PRs. Based on #665 — review that first. Plan: src/odr/internal/csv/PLAN.md.

What was wrong

check_csv_file conflated two jobs — is this a csv and can this be parsed — and answered both by reading the whole file. open_strategy.cpp:281 called it speculatively for every unrecognised text file, so classifying one large .txt cost a full pass over it. It also hard-coded , and ", and refused any file whose records disagreed on a field count.

The split

RecordReader reads records out of decoded UTF-8 text for a given dialect and judges nothing. Ragged records come out ragged. An unterminated quote still yields its field and sets a flag. Whether either is a reason to refuse the file is a question for detection, not parsing.

probe scores a bounded sample and returns the dialect it resolved plus a verdict:

  • tries ,, ;, tab, |; takes the field count most records carry; picks the separator explaining the most records
  • Excel's sep=; opening line wins outright
  • verdict wants ≥ 2 columns and no dangling quote in a complete file

Both verdict rules are heuristics, not validity rules — one column is every line of prose ever written, and a dangling quote is good evidence of not-csv. Neither says a one-column csv is illegitimate. Once a caller declares a file to be csv (next PR), it goes straight to RecordReader and is never asked to pass this.

A sample cut mid-record says nothing about the record it cut, so the last record is dropped unless the sample is the whole file. That is why a_dangling_quote_in_a_complete_file_is_evidence_against and a_dangling_quote_in_a_sample_is_not are separate tests.

The sample is decoded before scanning, which is what makes a UTF-16 csv detectable at all — Excel's "Unicode Text" export is UTF-16LE and tab-separated, so the separator is invisible in the raw bytes.

Tests

The old csv_file_test assertions move to the probe side, where they belong. a_quoted_field_must_be_terminated carried a comment recording a real misclassification; that rule survives, on the detection side only. New coverage for separator scoring, the sep= directive, the complete-vs-sample distinction, and RecordReader's new tolerance.

Full suite green (559 unit, 234 reference-output, no drift).

@chatgpt-codex-connector

Copy link
Copy Markdown

💡 Codex Review

https://github.com/opendocument-app/OpenDocument.core/blob/528cb2f887ef63098dbf28734392ff4ff557b523/output/odr-private/output/pdf/mova-viax-500-user-manual-de.pdf/document.html#L1
P1 Badge Remove generated private render output

This adds 624 generated artifacts under output/odr-private, output/odr-public, and media_* (about 459 MB of HTML/media) to the source tree. In the checked test workflow these are transient translate/test outputs that should stay outside the repo tree; keeping them in this feature commit will massively bloat every clone and risks publishing private corpus render output, so please drop these files from the commit.

AGENTS.md reference: AGENTS.md:L91-L91

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@andiwand
andiwand force-pushed the feat/text-encoding branch from c462066 to ede0212 Compare August 9, 2026 08:17
@andiwand
andiwand force-pushed the feat/text-encoding branch from ede0212 to cbc058a Compare August 9, 2026 08:33
Base automatically changed from feat/text-encoding to main August 9, 2026 10:19
`check_csv_file` conflated two jobs: deciding whether a file is a csv, and
deciding whether it can be parsed. It answered both by reading the *whole*
file, and `open_strategy` called it speculatively for every unrecognised text
file, so classifying a large one cost a full pass over it. It also hard-coded
`,` and refused any file whose records disagreed on a field count.

Detection and parsing split. `RecordReader` reads records out of decoded UTF-8
text for a given dialect and judges nothing: ragged records come out ragged, an
unterminated quote still yields its field and sets a flag. `probe` scores a
bounded sample and returns the dialect it resolved plus a verdict.

Scoring tries `,`, `;`, tab and `|`, takes the field count most records carry,
and picks the separator explaining the most records. Excel's `sep=` opening
line wins outright. The verdict is a heuristic and says so: at least two
columns, because one column is every line of prose ever written, and no
dangling quote in a file we have all of. Neither is a statement about validity
— a one-column csv is a legitimate csv, and once a caller declares a file to be
one, it goes straight to `RecordReader`.

A sample cut mid-record says nothing about the record it cut, so the last one
is dropped unless the sample is the whole file.

The sample is decoded first, which is what makes a UTF-16 csv detectable at
all — Excel's "Unicode Text" export is UTF-16LE and tab-separated.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QSgWdLTSLCWDeFvbVwZDVU
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant