Skip to content
Talk to our solutions team

Tables and forms

Three shapes, because documents contain three different things that look like tables:

TypeForEntry
TableA detected table with headers and rowsbbox.Doc().Table(), .TableWith(text)
RowsA column-aligned section that is not a real tablebbox.Doc().Rows(start, end)
FormA named form with labelled fieldsbbox.Doc().Form(name)

All three answer Found(), Missing() and Reason(), so a rule can check before reading and say why when nothing came back.

CallReturnsGives
Cell(row, col)ValueOne cell by numeric position
At(row, header)ValueOne cell by row index and header name
Row(index)RowOne row by position
RowWith(text)RowThe row containing that text
Column(header)list of ValueA whole column — .Len() only
Headers()listThe header names — .Len() only
HasHeader(header)boolWhether a header is present
RowCount()numberHow many rows
ColumnCount()numberHow many columns
Page()numberWhich page the table is on
Records()list of mapsEvery row — hand straight to out.Set
Found() / Missing() / Reason()bool / bool / stringWhether a table was located, and why not
when bbox.Doc().Table().HasHeader("Amount")
then out.Set("line_items", bbox.Doc().Table().Records());
CallReturnsGives
Get(header)ValueThe cell under that header
Cell(col)ValueThe cell at that position
Index()numberWhich row this is
Found() / Reason()bool / stringWhether the row was located, and why not
out.Set("total", bbox.Doc().Table().RowWith("Total").Get("Amount").Currency().Str());

For sections that line up in columns without being detected as a table — the common case in scanned forms. You describe the columns you want, then take the records.

CallReturnsGives
Col(header, name, kind...)RowsTake the column under header, output it as name
ColNth(header, occurrence, name, kind...)RowsThe nth column with that header
ColRightOf(anchor, header, name, kind...)RowsThe column right of an anchor
ColUnder(anchor, header, name, kind...)RowsThe column under an anchor
Describe(name, kind...)RowsType the named output column
Number(name)RowsCoerce that column to a number
SplitOn(delimiter, tailName)RowsSplit a column, putting the tail in tailName
Strip(chars)RowsStrip characters from every cell
Count()numberHow many rows
Records()list of mapsThe rows — hand straight to out.Set
Found() / Reason()bool / stringWhether the section was located, and why not

The kind argument is optional and names the value type for that column.

out.Set("employers",
bbox.Doc().Rows("Employment History", "Income")
.Col("Employer", "employer")
.Col("Start", "start_date")
.Number("years")
.Records());
CallReturnsGives
Get(field)ValueOne field by name
Has(field)boolWhether the field is present
Name()stringThe form’s name
FieldCount()numberHow many fields
Fields()mapEvery field — hand to out.Set, or take .Len()
Found() / Reason()bool / stringWhether the form was located, and why not
when bbox.Doc().HasForm("1003")
then out.Set("borrower", bbox.Doc().Form("1003").Get("Borrower Name").Str());

Records() is the call that matters. GRL cannot iterate a list, so a rule never walks rows itself — it hands the whole collection to out.Set and lets the caller read it:

out.Set("line_items", bbox.Doc().Table().Records());

When the receiving format cannot carry an array, flatten it with delivery.ExplodeColumn instead.

  • Document — the entry points that return these types
  • Values — coercing what a cell or field returned
  • Pluginsdelivery, for flattening rows into numbered fields