---
title: "Extracting tables from documents"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Extracting tables from documents}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>", eval = FALSE)
```

Tables are where document intelligence earns its keep: they carry the numbers,
but they are exactly what naive text extraction mangles. doclingr uses Docling's
table-structure model to recover cells, then hands each table back as a tibble.

## The basics

`docling_tables()` returns a list with one tibble per detected table, in
document order:

```{r}
library(doclingr)

doc <- docling_convert("financials.pdf")
tables <- docling_tables(doc)

length(tables)      # how many tables Docling found
tables[[1]]         # the first table, as a tibble
```

Each tibble carries a `page` attribute recording where the table came from:

```{r}
attr(tables[[1]], "page")
```

## Accurate vs. fast table structure

The table model has two modes. The default `"accurate"` recovers complex
structure (spanning cells, nested headers) at some cost; `"fast"` is quicker and
often enough for clean grids:

```{r}
doc_fast <- docling_convert("financials.pdf", table_mode = "fast")
docling_tables(doc_fast)[[1]]
```

## Working with the extracted tables

Because each table is a tibble, the whole tidyverse is available. For example,
tag every table with its page and stack them into one long frame:

```{r}
library(dplyr)
library(purrr)

all_tables <- docling_tables(doc) |>
  imap(\(tbl, i) mutate(tbl, .table = i, .page = attr(tbl, "page"))) |>
  list_rbind()

all_tables
```

Or write each table to its own CSV:

```{r}
tables <- docling_tables(doc)
iwalk(tables, \(tbl, i) readr::write_csv(tbl, sprintf("table-%02d.csv", i)))
```

## Tips

* Column types come back as character; coerce with `readr::type_convert()` or
  `dplyr::mutate(across(...))` once you know each table's schema.
* If a scanned (image-only) PDF returns empty tables, make sure OCR is on
  (`docling_convert(..., ocr = TRUE)`, the default).
* For very wide tables split across chunks during RAG, `docling_chunk()` can
  repeat the header row -- see `vignette("rag")`.
