# readTable - the two-line path Source: https://truecopy.dev/docs/table/ Module: truecopy/table Rows and cells out of a file with no configuration at all, plus the field that says what the reading could not vouch for. ```ts import { readTable } from 'truecopy/table'; const { rows, warnings, boundaries, document } = await readTable(file, options?); ``` | Field | | | ------------ | ----------------------------------------------------------- | | `rows` | `string[][]`: every row of every page, cut into cells | | `warnings` | `string[]`: what could not be vouched for | | `boundaries` | `number[][]`: the cut used, one list per page | | `document` | the `Document`, for a reader that needs more than the cells | `options` is [`OpenOptions`](https://truecopy.dev/docs/open/): caps, deadline, `workerSrc`, `pdfjs`. ## Whatever was dropped, the same two lines `file` is a PDF, a table pasted out of one, a CSV, a TSV, an OCR page. **One engine reads all of them**, because the cut votes on which left edges come back _row after row_, and that question does not care what measured them. A document only has to say where its fields start. | What was dropped | Where a column starts | What the reading prints | | --------------------------------------- | --------------------------------------------- | --------------------------- | | a PDF | the item's **x**, in points | `cut at 100, 290` | | a table pasted with spaces | the **character** the field starts at | `cut at characters 7, 22` | | a file written with a delimiter | the field's **index**: a CSV lines nothing up | `cut on the delimiter` | | prose, or a file that quotes its fields | nowhere | `every row came back whole` | Tab, semicolon, pipe and comma, in that order, and only when the count **recurs** line after line. What makes a delimiter real is what makes a column real. Two refusals come with that, and both are the point rather than a gap: - **A file that quotes its fields is not split.** A quoted field may hold the delimiter itself, and splitting anyway shifts every column after it. Half-parsing a CSV is exactly the plausible-but-wrong reading this library argues against, so the rows come back whole with a warning. - **An aligned paste is not read as a CSV because its amounts hold commas.** Every French amount does, so counting commas finds exactly one on every line of a pasted statement, and cutting on it would turn `12,40` into two columns. Alignment wins; only a tab overrides it, a tab never being punctuation. ## warnings is the whole point Whoever types _"extract tables from pdf"_ wants rows, and making them write a `kindOf` and choose thresholds before the first result is twenty minutes most people do not spend. So this is two lines. What it does **not** do is return a bare array. A plausible-looking table out of a document that was misread is worse than nothing, because nobody checks a table that looks right. Hand one over with no signal and this library becomes the thing it was written to prevent, and a worse one than the extractors that have spent years tuning their heuristics. > **An empty `warnings` is not a promise.** It says nothing looked wrong from the shape of the page. That is a much smaller claim than "this reading is right", and the distance between the two is what the rest of the library is for. Every warning is computed **without knowing anything about your document**: - `page N carries no text at all`: a blank page, a scan, an image; - `page N shows no column at all`: the page is prose, or the cut failed and every row came back whole; - `column C of page N is filled on only X%`: the cut may have invented that column out of a letterhead; - `the pages disagree on how many columns there are`: usually a different table, and joining them makes a third that is neither. ## boundaries, and why they are not the page's own `readTable` does not cut on `page.columnBoundaries`. That one is the spread of x over **everything printed** (letterhead, address block, footer), and on a real page it proposes columns the table never had: measured, twelve where the table has five. It uses [`boundariesFromRecurrence`](https://truecopy.dev/docs/layout/) instead: keep only the x that come back row after row, because a real column's left edge recurs and a word in the middle of a description does not. `boundaries` is handed back so you can explain or re-cut the same page against the same lines: ```ts import { explainDocument } from 'truecopy/explain'; const { document, boundaries } = await readTable(file); console.log( explainDocument(document, { boundariesOf: (page) => boundaries[page.pageNumber - 1] }) ); ``` Without that, the heading and the cells would tell two different stories. ## When two lines are not enough The moment something downstream **acts** on the rows (a budget, a report, a decision), you want a reading that checks itself rather than one that merely looks fine: - [`findRowAnomalies`](https://truecopy.dev/docs/signature/) drops the rows that break the table's own shape; - [`validate`](https://truecopy.dev/docs/schema/) says whether enough well-formed records came back; - [`readDocument`](https://truecopy.dev/docs/contract/) refuses to return a reading that contradicts its document. Map of this site for a model: https://truecopy.dev/llms.txt Every page in one file: https://truecopy.dev/llms-full.txt