Skip to content

Implement preserve quotes - #19

Open
Kwanddwo wants to merge 2 commits into
csvtoolkit:mainfrom
Kwanddwo:implement-preserve-quotes
Open

Implement preserve quotes#19
Kwanddwo wants to merge 2 commits into
csvtoolkit:mainfrom
Kwanddwo:implement-preserve-quotes

Conversation

@Kwanddwo

@Kwanddwo Kwanddwo commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

This pr implements the preserveQuotes feature, where setting config->preserveQuotes to true will let you preserve quotes in quoted fields.

Summary by CodeRabbit

  • Bug Fixes

    • Improved CSV parsing so quoted fields now correctly keep or drop surrounding quotes based on the selected setting.
    • Fixed handling of quoted values at both the start and end of a field for more consistent parsed output.
  • Tests

    • Added coverage to verify quoted fields are preserved when the quote-preservation setting is enabled.

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The CSV parser's quote-handling logic in csv_parse_line_inplace is modified to conditionally preserve enclosing quote characters in parsed field content when config->preserveQuotes is enabled, affecting both opening and closing enclosure handling. A corresponding test case is added.

Changes

Preserve Quotes Feature

Layer / File(s) Summary
Quote preservation logic and test
csv_parser.c, tests/test_csv_parser.c
FIELD_START and QUOTED_FIELD states branch on config->preserveQuotes to include or exclude opening/closing quote characters in field content; test confirms quoted field retains surrounding quotes when the option is enabled.

Estimated code review effort: 1 (Trivial) | ~5 minutes

Sequence Diagram(s)

Not applicable.

Related issues: None specified.

Related PRs: None specified.

Suggested labels: enhancement, tests

Suggested reviewers: None specified.

Poem
A rabbit nibbled at a quoted field,
Deciding if the quotes should yield.
With preserveQuotes now in play,
The marks can stay or slip away.
Tests confirm the whiskered claim—
Quotes preserved, the field's the same! 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding preserveQuotes support for quoted CSV fields.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/test_csv_parser.c (1)

61-68: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add edge-case coverage for preserveQuotes with empty/adjacent quotes.

The new test only covers a quoted field with no internal or boundary-adjacent quotes. Add cases like "" (empty quoted field) and """quoted""" (escaped quote touching the boundary) with preserveQuotes = true — these currently produce incorrect output due to the boundary-quote collapsing bug in csv_parser.c (see comment there).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_csv_parser.c` around lines 61 - 68, Extend the preserveQuotes
coverage in the CSV parser tests to include edge cases for empty and
boundary-adjacent quotes, using csv_parse_line_inplace and the existing
preserveQuotes setup in test_csv_parser.c. Add assertions for inputs like "" and
"""quoted""" so the behavior of CSVParseResult.fields is validated when quotes
are empty or touch the field boundary. Keep the new checks alongside the current
preserveQuotes=true test so the boundary-quote collapsing bug in
csv_parse_line_inplace is exercised directly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@csv_parser.c`:
- Around line 122-128: The preserveQuotes handling in the quoted-field path is
feeding boundary enclosure chars into add_quoted_field, which then mis-collapses
them as escaped quotes. Keep field_start/field_len pointed only at the inner
content in the FIELD_START/QUOTED_FIELD logic, and let add_quoted_field wrap the
enclosure chars itself outside its escape-collapsing loop. Update both call
sites that invoke add_quoted_field to pass preserveQuotes so it can decide
whether to re-add the boundary quotes without scanning them as input.

---

Nitpick comments:
In `@tests/test_csv_parser.c`:
- Around line 61-68: Extend the preserveQuotes coverage in the CSV parser tests
to include edge cases for empty and boundary-adjacent quotes, using
csv_parse_line_inplace and the existing preserveQuotes setup in
test_csv_parser.c. Add assertions for inputs like "" and """quoted""" so the
behavior of CSVParseResult.fields is validated when quotes are empty or touch
the field boundary. Keep the new checks alongside the current
preserveQuotes=true test so the boundary-quote collapsing bug in
csv_parse_line_inplace is exercised directly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 91aad80a-c0b4-468e-ad61-f14bc1215b2a

📥 Commits

Reviewing files that changed from the base of the PR and between cc93661 and c677abb.

📒 Files selected for processing (2)
  • csv_parser.c
  • tests/test_csv_parser.c

Comment thread csv_parser.c
Comment on lines +122 to +128
if (!config->preserveQuotes) {
field_start = &line[pos + 1];
field_len = 0;
} else {
field_start = &line[pos];
field_len = 1;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔴 Critical | 🏗️ Heavy lift

Boundary quotes get mis-collapsed by add_quoted_field's escape logic when preserveQuotes is enabled.

Including the opening/closing enclosure chars directly in field_start/field_len means the span later passed to add_quoted_field (lines 179, 214) now contains the boundary quote characters. add_quoted_field's existing escape-collapsing loop (lines 75-82) treats any two consecutive enclosure characters as an escaped pair and collapses them to one — it has no way to distinguish "real" escaped quotes from the artificially-included boundary quotes.

Concrete repro: parsing "",normal with preserveQuotes = true:

  • field_start = &line[0], field_len = 2 (span = "").
  • add_quoted_field sees start[0]=='"' and start[1]=='"' and collapses them into a single ".
  • Result: the empty quoted field becomes " instead of the expected "".

The same mis-collapse happens whenever content immediately adjacent to a boundary quote is itself an escaped quote (e.g. """quoted"""), producing output that is neither the verbatim source nor correctly re-quoted content.

Since add_quoted_field itself wasn't updated for this PR, this is a latent correctness bug not caught by the added test (which uses a quoted field with no adjacent/internal quotes).

🛠 Suggested fix — don't feed boundary quotes into the escape-scan; wrap after unescaping

Keep field_start/field_len scoped to content only (as before), and instead have add_quoted_field write the enclosure chars directly, outside the escape-scanning loop:

-static bool add_quoted_field(FieldArray *arr, const char *start, size_t len, Arena *arena, char enclosure) {
+static bool add_quoted_field(FieldArray *arr, const char *start, size_t len, Arena *arena, char enclosure, bool preserve_quotes) {
     if (arr->count >= arr->capacity) {
         if (!grow_field_array(arr, arena)) {
             return false;
         }
     }
 
     void *ptr;
-    ArenaResult result = arena_alloc(arena, len + 1, &ptr);
+    ArenaResult result = arena_alloc(arena, len + 3, &ptr);
     if (result != ARENA_OK) {
         return false;
     }
     
     char *field = (char*)ptr;
     size_t write_pos = 0;
+    if (preserve_quotes) field[write_pos++] = enclosure;
     
     for (size_t i = 0; i < len; i++) {
         if (start[i] == enclosure && i + 1 < len && start[i + 1] == enclosure) {
             field[write_pos++] = enclosure;
             i++;
         } else {
             field[write_pos++] = start[i];
         }
     }
-    
+    if (preserve_quotes) field[write_pos++] = enclosure;
     field[write_pos] = '\0';

Then revert the state-machine changes to always exclude the boundary quotes from field_start/field_len:

case FIELD_START:
    if (c == config->enclosure) {
        state = QUOTED_FIELD;
        field_start = &line[pos + 1];
        field_len = 0;
    }
case QUOTED_FIELD:
    if (c == config->enclosure) {
        if (pos + 1 < len && line[pos + 1] == config->enclosure) {
            field_len += 2;
            pos++;
        } else {
            state = FIELD_END;
        }
    }

And pass config->preserveQuotes at both call sites (lines 179 and 214):

add_quoted_field(&result.fields, field_start, field_len, arena, config->enclosure, config->preserveQuotes)

Also applies to: 167-169

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@csv_parser.c` around lines 122 - 128, The preserveQuotes handling in the
quoted-field path is feeding boundary enclosure chars into add_quoted_field,
which then mis-collapses them as escaped quotes. Keep field_start/field_len
pointed only at the inner content in the FIELD_START/QUOTED_FIELD logic, and let
add_quoted_field wrap the enclosure chars itself outside its escape-collapsing
loop. Update both call sites that invoke add_quoted_field to pass preserveQuotes
so it can decide whether to re-add the boundary quotes without scanning them as
input.

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.

preserveQuotes config is never used

1 participant