|
| 1 | +# AGENTS.md — `internal/csv` |
| 2 | + |
| 3 | +Read the root [`AGENTS.md`](../../../../AGENTS.md) first. This file covers what |
| 4 | +csv does differently, and why. The staged plan is in [`PLAN.md`](PLAN.md). |
| 5 | + |
| 6 | +## Cells are not elements |
| 7 | + |
| 8 | +The root `AGENTS.md` prescribes an `ElementRegistry`: a flat `std::vector` of |
| 9 | +elements, id = index + 1. **Csv does not use one**, deliberately. |
| 10 | + |
| 11 | +A registry costs an entry per element. A sheet has one per *cell*, so a file |
| 12 | +with a million rows would cost millions of entries before a single one is |
| 13 | +looked at — and `spreadsheet_limit` means the renderer will ask for ten |
| 14 | +thousand rows of them at most. |
| 15 | + |
| 16 | +`ElementIdentifier` is a `std::uint64_t`, which is room to spare: |
| 17 | + |
| 18 | +``` |
| 19 | +63..61 kind root | sheet | cell | text |
| 20 | +60..24 row 37 bits |
| 21 | +23..0 column 24 bits |
| 22 | +``` |
| 23 | + |
| 24 | +So an id *is* the coordinate, and the adapter decodes rather than looks up. |
| 25 | +`null_element_id` is zero, so no kind may be. |
| 26 | + |
| 27 | +The consequence to respect: **a sheet's cells are not reachable by walking**. |
| 28 | +`element_first_child` of a sheet is `null_element_id`; cells come from |
| 29 | +`SheetAdapter::sheet_cell(column, row)`, which is how the renderer asks for |
| 30 | +them anyway (`html/document_element.cpp:163`). |
| 31 | + |
| 32 | +## Everything goes through `cell` and `dimensions` |
| 33 | + |
| 34 | +`CsvDocument` holds the whole file decoded, and the adapter never touches that |
| 35 | +storage — it calls `cell(column, row)` and `dimensions()`. That is the seam for |
| 36 | +the streaming work in `PLAN.md`'s last stage: an index and a window can move in |
| 37 | +behind those two without the adapter noticing. |
| 38 | + |
| 39 | +## Detection rejects; the parser does not |
| 40 | + |
| 41 | +Two jobs, two places, and mixing them is the mistake this module already made |
| 42 | +once. |
| 43 | + |
| 44 | +- `probe` is detection. It scores a bounded sample and may say "not a csv". |
| 45 | + Its rules — at least two columns, no dangling quote in a complete file — are |
| 46 | + *heuristics for recognising an unknown file*, not statements about validity. |
| 47 | +- `RecordReader` is parsing. Given a separator it is total: ragged rows, one |
| 48 | + column, an empty file and a truncated quoted field all read as some csv. |
| 49 | + |
| 50 | +So a one-column csv is perfectly legitimate and `CsvOptions{.separator = ','}` |
| 51 | +reads it. `NoCsvFile` is a detection failure only. An incoherent dialect — a |
| 52 | +separator equal to the quote, a line break as a separator — is |
| 53 | +`std::invalid_argument`, a caller mistake rather than bad input. |
| 54 | + |
| 55 | +## A csv is a document |
| 56 | + |
| 57 | +`FileCategory::document`, `DocumentType::spreadsheet` — so `is_text_file()` is |
| 58 | +false for a csv and `is_document_file()` is true. Reading one as text is still |
| 59 | +available by opening it as `FileType::text_file`, which is also the escape |
| 60 | +hatch when detection was wrong about it being a csv at all. |
| 61 | + |
| 62 | +Text has to be UTF-8 by the time it reaches a cell: `Text::content()` returns |
| 63 | +`std::string` and every binding treats it as UTF-8. That is why an encoding |
| 64 | +`internal/encoding` cannot decode has no document at all, while the *text* |
| 65 | +rendering path stays open to it. |
0 commit comments