Runs the full engine pipeline against spec and returns the
resolved tabular_grid — the same intermediate object emit()
hands to a backend. Pure function: no files written, no global
state touched. Use this during development to inspect what
emit() will pass downstream, when building a custom backend,
or when piping the resolved grid into a non-file consumer (e.g. an
inline preview chunk in a Quarto notebook).
Arguments
- .spec
The
tabular_specto resolve.<tabular_spec>: required. Built by the verb chain (tabular()->cols()->headers()->sort_rows()->style()->paginate()->preset()).
Value
A tabular_grid S7 object. Two slots:
@pages— a list of one entry per display page. Each entry is a named list with pagination fields (page_index,panel_index,is_continuation,continuation,show_titles,repeat_headers,show_footnotes_here), row + column slice indices (row_indices,col_indices,col_names), the sliced cell text (cells_text— character matrix), sliced inline ASTs (cells_ast— list-matrix ofinline_ast), sliced style nodes (cells_style— list-matrix ofstyle_node), and the column-label ASTs for the visible columns (col_labels_ast).@metadata— per-table information backends consume once per render:format(the resolved backend tag,NA_character_foras_grid()calls),rows_per_page,total_pages,total_panels,nrow_data,ncol_data,col_names,cols(the originalcol_spec()entries keyed by column name),headers(the flattened header band grid),titles,footnotes,titles_ast,footnotes_ast,col_labels_ast,pagehead_ast/pagefoot_ast(resolved page-band content —NULLwhen the active preset declares no band, otherwiselist(left, center, right)of length-N lists ofinline_astwhere N = row count and index 1 is the body-edge row).
Details
Engine pipeline order is load-bearing. Phases run in this fixed order; the order matters because each phase reads the post- previous-phase state of the spec:
engine_sort()— apply the sort spec.engine_headers()— validate the header tree and flatten it to a band grid.engine_style()— evaluate every style predicate against the post-sort data grid. A predicate may reference any column inspec@data.engine_format()— apply per-column formats, substitutena_text, and parse every cell / title / footnote / label throughparse_inline()to itsinline_ast.engine_decimal()— column-wide decimal alignment for any column flaggedcol_spec(align = "decimal"). Operates on the formatted text; output is the same character matrix with NBSP padding inserted so the decimal marks line up.engine_paginate()— split into pages (vertical row chunks + horizontal panel chunks). The plan drives the per-page slicing of cells / styles / ASTs below.
The grid is the backend contract. Every backend
(backend_md, future backend_html, etc.) consumes a
tabular_grid — never a tabular_spec. New backends only need
to walk grid@pages and grid@metadata; the engine pipeline is
a fixed dependency they never re-implement.
No I/O. as_grid() writes nothing to disk and touches no
global state. It is safe to call repeatedly during interactive
exploration; cost is roughly that of one emit() without the
backend write step.
See also
I/O sibling: emit() writes the resolved grid to a file
via a registered backend; as_grid() is the no-I/O entry into
the same pipeline.
Build verbs the pipeline feeds from: tabular(),
cols() / col_spec(), headers(), sort_rows(),
style(), paginate(), preset().
Examples
# ---- Example 1: Demographics — inspect the resolved grid ----
#
# Resolve the canonical safety-pop demographics pipeline into a
# `tabular_grid` and inspect what `emit()` would hand a backend.
# The first page's `cells_text` matrix is the decimal-aligned
# output as the backend would render it; the metadata carries the
# pagination plan + header / title / footnote ASTs.
n <- stats::setNames(cdisc_saf_n$n, cdisc_saf_n$arm_short)
demo <- tabular(
cdisc_saf_demo,
titles = c(
"Table 14.1.1",
"Demographics and Baseline Characteristics",
"Safety Population"
),
footnotes = "Source: ADSL."
) |>
cols(
variable = col_spec(usage = "group", label = "Characteristic"),
stat_label = col_spec(label = "Statistic"),
placebo = col_spec(label = "Placebo\nN={n['placebo']}", align = "decimal"),
drug_50 = col_spec(label = "Drug 50\nN={n['drug_50']}", align = "decimal"),
drug_100 = col_spec(label = "Drug 100\nN={n['drug_100']}", align = "decimal"),
Total = col_spec(label = "Total\nN={n['Total']}", align = "decimal")
) |>
sort_rows(by = c("variable", "stat_label"))
demo_grid <- as_grid(demo)
demo_grid@metadata$total_pages
#> [1] 1
demo_grid@pages[[1]]$cells_text[1:3, c("stat_label", "placebo")]
#> stat_label placebo
#> [1,] "Age (years)" ""
#> [2,] " Mean (SD)" "75.2 (8.59)"
#> [3,] " Median" "76.0 "
# ---- Example 2: AE-by-SOC/PT paginated grid — verify the split ----
#
# Same shape as Example 1 plus pagination protecting the SOC
# grouping. With a tight font size the grid carries multiple page
# entries; concatenating each page's `row_indices` reconstructs
# the full data, and every page carries the full header band grid
# at `grid@metadata$headers` so backends can re-render the header
# on every continuation page.
n <- stats::setNames(cdisc_saf_n$n, cdisc_saf_n$arm_short)
ae_spec <- tabular(
cdisc_saf_aesocpt,
titles = c(
"Table 14.3.1",
"Adverse Events by SOC and Preferred Term",
"Safety Population"
),
footnotes = "Subjects counted once per SOC and once per PT."
) |>
cols(
label = col_spec(label = "SOC / PT", indent = "indent_level"),
soc = col_spec(usage = "group", visible = FALSE,
group_display = "column_repeat"),
row_type = col_spec(visible = FALSE),
soc_n = col_spec(visible = FALSE),
n_total = col_spec(visible = FALSE),
placebo = col_spec(label = "Placebo\nN={n['placebo']}", align = "decimal"),
drug_50 = col_spec(label = "Drug 50\nN={n['drug_50']}", align = "decimal"),
drug_100 = col_spec(label = "Drug 100\nN={n['drug_100']}", align = "decimal"),
Total = col_spec(label = "Total\nN={n['Total']}", align = "decimal")
) |>
sort_rows(by = c("soc_n", "n_total"), descending = c(TRUE, TRUE)) |>
paginate(keep_together = "soc")
ae_grid <- as_grid(ae_spec)
length(ae_grid@pages)
#> [1] 3
# ---- Example 3: Subgroup partition — one page set per group ----
#
# When `subgroup()` is attached, `as_grid()` runs the resolve
# pipeline once per group and concatenates the pages. `cdisc_saf_subgroup`
# carries `sex` as a natural partition axis; inspect
# `@pages[[i]]$subgroup_index` and `@pages[[i]]$subgroup_line_ast`
# to confirm each page knows its group identity and banner text.
# `sex` auto-hides as the partition `by` column; no explicit
# `col_spec(visible = FALSE)` needed.
sg_spec <- tabular(cdisc_saf_subgroup) |>
cols(
sex_n = col_spec(visible = FALSE),
paramcd = col_spec(visible = FALSE),
param = col_spec(usage = "group", label = "Parameter"),
visit = col_spec(usage = "group", label = "Visit"),
stat_label = col_spec(label = "Statistic")
) |>
subgroup("sex")
sg_grid <- as_grid(sg_spec)
length(sg_grid@pages)
#> [1] 2
vapply(sg_grid@pages, function(p) p$subgroup_index %||% NA_integer_, integer(1))
#> [1] 1 2
# ---- Example 4: Pre-flight inspection before emit() ----
#
# Resolve a spec to its grid without writing anywhere. Useful in
# tests, for snapshotting cell text under different presets, or
# for spec-introspection inside higher-level wrappers that need
# to know how many pages a render will produce.
n <- stats::setNames(cdisc_saf_n$n, cdisc_saf_n$arm_short)
demog_spec <- tabular(
cdisc_saf_demo,
titles = "Demographics"
) |>
cols(
variable = col_spec(usage = "group", label = "Characteristic"),
stat_label = col_spec(label = "Statistic"),
placebo = col_spec(
label = "Placebo\nN={n['placebo']}",
align = "decimal"
),
drug_50 = col_spec(
label = "Drug 50\nN={n['drug_50']}",
align = "decimal"
),
drug_100 = col_spec(
label = "Drug 100\nN={n['drug_100']}",
align = "decimal"
),
Total = col_spec(
label = "Total\nN={n['Total']}",
align = "decimal"
)
)
grid <- as_grid(demog_spec)
length(grid@pages)
#> [1] 1
dim(grid@pages[[1]]$cells_text)
#> [1] 16 5